AmsiScanBuffer Bypass - Part 4

As 2018 rapidly comes to an end, I thought I’d close out the year by clearing up some confusions over this AmsiScanBuffer bypass and why it appears to fail under some circumstances.

I’ve had numerous people contact me (particularly those doing RastaLabs) asking about these two specific issues, but I didn’t feel inspired to address them until a very pleasant conversation with confuciussayuhm. So props to them :)

It Just Doesn’t Work

The first issue looks something like this:

It’s visually confusing because the bypass has been executed and allows us to print “AmsiScanBuffer”, where we previously could not. But for some reason, the execution of a stager still fails.

By firing up Process Explorer, we can see the PowerShell process our console is living in (semi-highligted in grey). Then when the IEX cradle is run, a new child PowerShell process is created (highlighted in green) before very quickly being killed off.

Because this AMSI bypass is per-process, this new child is still has amsi.dll perfectly intact. It does not inherit the bypass from its parent, and this is clearly the reason for the detection.

The next question is, why is a child spawned?

Well, by inspecting the two processes in Process Explorer, we see that the path of the child process is c:\Windows\SysWOW64\windowspowershell\v1.0\powershell.exe, where the parent is C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe. So this is the 64-bit binary spawning the 32-bit version.

The obvious explanation is that we’re trying to run a 32-bit stager from a 64-bit process. So this is the part where you really need to know your toolset. For example, the Cobalt Strike Scripted Web Delivery will always create a 32-bit stager (you don’t even get the option to make it 64-bit). Therefore you must ensure that regardless of which toolset you’re using, you create 64-bit payloads.

It doesn’t matter if they’re staged or stageless in the context of this bypass, they both work fine.

It Just Crashes

[Updated 04/01/2019]

I found this one to be a little more interesting.

The second issue comes where the PowerShell process crashes when the bypass is executed, due to some memory corruption. This would only actually happen if it was being run in a 32-bit process.

If you were using the code we commited in Part 2 (which was basically the initial commit), you would have been using:

Byte[] Patch = { 0x31, 0xff, 0x90 };
IntPtr unmanagedPointer = Marshal.AllocHGlobal(3);
Marshal.Copy(Patch, 0, unmanagedPointer, 3);
MoveMemory(ASBPtr + 0x001b, unmanagedPointer, 3);

But at the end of Part 3, we changed the bytes being used to patch the AmsiScanBuffer function, specifically to make it more “resiliant”. We ended up with the following, (but I never updated the repo).

Byte[] Patch = { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 };
IntPtr unmanagedPointer = Marshal.AllocHGlobal(6);
Marshal.Copy(Patch, 0, unmanagedPointer, 6);
MoveMemory(ASBPtr, unmanagedPointer, 6);

And this runs perfectly well in a 32-bit process.

I’ve not tested this, but my theory is that the static byte offset 0x001b is incorrect for a 32-bit process, and so we end up overwriting a completely different portion of memory than what we want.

Coincidently, Francesco Soncina submitted two PR’s to update both the C# and PowerShell versions of the bypass. So if you pull the changes, you should be fine.

I hope this helps anybody out who was facing these problems. Armed with this knowledge, may 2019 bring you many more shells.