| 3 min read
Table of contents
Most of us have faced AMSI
(Antimalware Scan Interface) and suffered the constraints it poses whenever we want to load a fancy PowerShell module like PowerView or Invoke-Mimikatz in the middle of a Red Team engagement.
AMSI
is a programmatic resource offered by Microsoft to enable an interface to any application for interacting with any anti-malware product available on the machine. AMSI
is EDR-agnostic and can be integrated with any vendor. When AMSI
appears on stage, something like this should be familiar:
Programmatically, AMSI
can be included in an application using Win32 API functions and the IAmsiStream
COM
interface.
As a result, if an application was built with AMSI
, amsi.dll
will become part of the runtime modules of the application. Hence, it is seen as a DLL
that is loaded at runtime when the application starts. The basic architecture of AMSI
is the following:
Source: Microsoft.
As you can see, the functions responsible for checking the content for malicious content are AmsiScanBuffer()
and AmsiScanString()
. These functions act as the entry point that the application uses to send the suspected tainted input to the underlying antivirus software.
Using a tool like Process Hacker, it is possible to check the runtime modules on any process in the system. Checking the process of our PowerShell session, we can see the AMSI
DLL
loaded in memory:
We can also check the exported symbols, which are the functions provided as the high-level interface with AMSI
. Here we can see all the exported functions that compose AMSI
, including AmsiScanBuffer()
and AmsiScanString()
.
However, these two functions are not really different. In fact, AmsiScanString()
is a small function which uses AmsiScanBuffer()
underneath. This can be seen in WinDBG:
And with a disassembler:
So, if we can bypass the checks performed by AmsiScanBuffer()
, we can also bypass AmsiScanString()
!
Let’s get it done!
Here, we can see the disassembly graph of AmsiScanBuffer()
:
As you can see, it is not a complex function either.
At the end of the function, we can see this:
It seems that the actual anti-malware scanning is performed in the instructions that compose the big box on the left. Also, we notice that several JMP
instructions land in mov eax 0x800700057
and then the function ends. The value 0x80070057
is a standardized error code from Microsoft, which is E_INVALIDARG
. In this case, it’s used by AmsiScanBuffer()
to return when the parameters passed by the caller code are not valid.
So, what would happen if we modify the AmsiScanBuffer()
function in memory to bypass the anti-malware checking instructions altogether and force it always to return 0x80070057
? Let’s check it!
First, let’s examine the instructions using WinDBG:
Here we can see the disassembled instructions of AmsiScanBuffer()
. We can also see the byte-code corresponding to the mov eax,80070057h
instruction: b857000780
.
Using that information, we can modify the very beginning of AmsiScanBuffer()
with the following instructions:
b857000780 mov eax,0x80070057
c3 ret
That would move the E_INVALIDARG
value (0x80070057
) to EAX
, making it the return value of AmsiScanBuffer()
, and then the function ends with a RET
. As can be seen above, the bytes b857000780
and c3
are the byte-codes of those instructions.
We can do the memory patching using WinDBG. The steps are the following:
-
Attach the current PowerShell session to WinDBG.
-
Break the execution.
-
Try to load a common flagged module (e.g.,
PowerView
) to seeAMSI
in action. -
Check the current instructions of the beginning of
AmsiScanBuffer()
. This can be accomplished withu amsi!AmsiScanBuffer
inside WinDBG. -
As we are in a little-endian architecture (
x86_64
), we need to reverse the byte-code of themov eax,0x80070057 | ret
instructions:c380070057b8
. -
Modify the start of
amsi!AmsiScanBuffer
with those bytes. This can be done usingeq amsi!AmsiScanBuffer c380070057b8
. -
Resume the execution.
-
Load again
PowerView
. -
Enjoy an
AMSI
-free PowerShell session!
Let’s check the bypass in action:
However, returning 80070057
is not the only way to bypass AmsiScanBuffer()
. In fact, we can make it return 0
, using something like sub eax,eax | ret
, and the bypass will be successful as well.
Let’s see:
Success! Now we can use PowerView
, Invoke-Mimikatz
or any other Red Team tool!
Conclusion
Memory patching is a nice trick to use to modify the behavior of running applications. Keep in mind that this technique is not persistent. The modification of AmsiScanBuffer()
is performed on the memory of the PowerView process and the original amsi.dll
is never touched on disk.
Table of contents
Share
Recommended blog posts
You might be interested in the following related posts.
Users put their trust in you; they must be protected
Consequential data breaches in the financial sector
Is your financial service as secure as you think?
We need you, but we can't give you any money
Data breaches that left their mark on time
Lessons learned from black swans
A digital infrastructure issue that many still ignore