AMSI Bypass Using Memory PatchingDynamic in-memory AMSI bypass
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
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.
AMSI appears on stage,
something like this should be familiar:
As a result,
if an application was built with
amsi.dll will become part of the runtime modules of the application.
Hence, it is seen as a
that is loaded at runtime when the application starts.
The basic architecture of
AMSI is the following:
Figure 1. Source: Microsoft
As you can see,
the functions responsible for checking the content
for malicious content
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
DLL loaded in memory:
We can also check the exported symbols,
which are the functions provided as the high-level interface with
Here we can see all the exported functions that compose
However, these two functions are not really different.
AmsiScanString() is a small function
This can be seen in WinDBG:
And with a disassembler:
So, if we can bypass the checks performed by
we can also bypass
Let’s get it done!
Here, we can see the disassembly graph of
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.
0x80070057 is a standardized error code from Microsoft,
In this case, it’s used by
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
Let’s check it!
First, let’s examine the instructions using WinDBG:
Here we can see the disassembled instructions of
We can also see the byte-code
corresponding to the
mov eax,80070057h instruction:
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 (
making it the return value of
and then the function ends with a
As can be seen above, the bytes
c3 are the byte-codes of
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 see
Check the current instructions of the beginning of
AmsiScanBuffer(). This can be accomplished with
u amsi!AmsiScanBufferinside WinDBG.
As we are in a little-endian architecture (
x86_64), we need to reverse the byte-code of the
mov eax,0x80070057 | retinstructions:
Modify the start of
amsi!AmsiScanBufferwith those bytes. This can be done using
eq amsi!AmsiScanBuffer c380070057b8.
Resume the execution.
AMSI-free PowerShell session!
Let’s check the bypass in action:
80070057 is not the only way to bypass
In fact, we can make it return
using something like
sub eax,eax | ret,
and the bypass will be successful as well.
Success! Now we can use
or any other Red Team tool!
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
is performed on the memory of the PowerView process
and the original
amsi.dll is never touched on disk.