AMSI re-armed mid-flight
When you see this
You called amsi.PatchAll(caller) at process start (it returned
nil), but a later Assembly.Load / PowerShell IEX still
triggers Defender on a payload that should now bypass AMSI. The
patch silently became ineffective.
Most likely causes (ranked)
amsi.dllwas re-mapped or reloaded after the patch (≈50%) —LoadLibrary("amsi.dll")post-patch can map a fresh copy if the original handle was closed; the new copy has unpatched bytes.- A new process inherited from the patched one (≈25%) —
AMSI patches are per-process, not per-token. Spawning
PowerShell via
CreateProcessgets a cleanamsi.dll. - Defender pushed a
WerFault.exe-style recovery (≈10%) — modern Defender can re-protect AMSI in monitored processes via a kernel callback writing back the original bytes. - Wrong process was patched (≈10%) — your evasion stack ran
in the launcher, but the
clr.LoadAndExecutespawned a child that did the loading. - AMSI provider chain has more than Defender (≈5%) — third-
party AV registered its own COM provider, untouched by your
AmsiScanBufferpatch.
Diagnostic steps
- Check the patched bytes are still in place. Read the first
3 bytes of
AmsiScanBufferand compare to31 C0 C3.addr, _ := windows.LoadLibrary("amsi.dll") proc, _ := windows.GetProcAddress(addr, "AmsiScanBuffer") var bytes [3]byte // ReadProcessMemory on own PID via wsyscall.Caller if bytes != [3]byte{0x31, 0xC0, 0xC3} { // re-arm detected }- bytes match: AMSI patch is intact; cause is elsewhere (probably step 4). Continue to step 4.
- bytes differ: AMSI was re-armed. Step 2.
- Check
amsi.dllbase address. Has it changed since the original patch? Capture base at patch time, compare now.- same base: the bytes were rewritten in place (Defender kernel-callback or anti-tamper). Mitigation: see step 6.
- different base: a fresh map happened. Patch again now and consider lazy re-patching on every COM call.
- Check for additional providers. Enumerate
HKLM\SOFTWARE\Microsoft\AMSI\Providers\*. Each subkey is a CLSID of another provider DLL.- if there are non-Defender providers: those are bypassing
amsi.dllentirely. Step 5.
- if there are non-Defender providers: those are bypassing
- Capture the loader child PID. When
clr.LoadAndExecutespawns a sub-process, get its PID and check Defender for detection events on that PID, not yours.- match: confirmed cause #2. See mitigation #2.
- Manual patching of provider DLLs (last resort) — same
prologue trick on the registered provider's
Scanfunction. Seeevasion/amsigodoc.
Mitigation
- Re-patch periodically. Wrap suspicious calls with
amsi.PatchAll(caller)immediately before them. Patches are idempotent (ADR-0002). - Patch every child you spawn. If your launcher chains to
PowerShell, inject
evasion/amsi.PatchAllbefore the script runs (PowerShell host process). - Compose
unhookfirst (see ntdll-unhooking). Defender's behavioural counters depend on ntdll hooks; remove them and you reduce the chance of re-arm. - Use
preset.Aggressivewhich adds ACG + BlockDLLs to prevent lateramsi.dllreloads.
Prevention
- Always
defera re-patch after anyLoadLibraryyou make. - Verify the patch is alive before any high-value AMSI call (the cost is 3 bytes of read, negligible).
- Avoid spawning child processes for sensitive operations — prefer in-process injection or reflective load.
Related
- Technique: evasion/amsi.
- Technique: evasion/preset.
- Runbook: Defender catch on dropper.