22nd Survey Division
AV Evasion AMSI Bypass ETW Blind VULN-195458

HWBP Zero-Write: AMSI + ETW Bypass via Hardware Breakpoints

George Wu June 2026 22nd Survey Division vader-rootkit Phase DELTA+FOXTROT / asf-counterintel
T1562.001
T1562.006
T1620
Plain English
Windows has two surveillance systems watching what runs on the machine. We make both of them return "nothing to see here" before they can look. The method leaves zero trace in memory — it uses a CPU-level mechanism designed for debuggers, not attackers. Microsoft reviewed it and decided it's not their problem to fix.

Validated AMSI bypass: DR0 breakpoint on AmsiScanBuffer, Defender RTP enabled, zero detections.
Validated ETW bypass: DR1 breakpoint on EtwEventWrite, behavioural logging blind.
Validated Zero bytes written to executable memory. Zero NtProtectVirtualMemory calls.
Validated MSRC filed 2026-06-16 as VULN-195458. Response: does not meet security servicing bar.

The Two Watchers

When an attacker runs a payload on a Windows machine, two surveillance systems see it:

AMSI — Antimalware Scan Interface. Every time PowerShell, .NET, VBScript, or VBA executes code, AMSI intercepts the script content and passes it to Defender. This happens after any decoding or decryption — AMSI sees the final, ready-to-run version regardless of how it was obfuscated on disk. The critical function is AmsiScanBuffer in amsi.dll. Every scan passes through it.

ETW — Event Tracing for Windows. The telemetry backbone of every modern EDR. Every process creation, thread injection, image load, and suspicious memory operation generates an ETW event. Defender's behavioural engine, Sysmon, and every commercial EDR reads ETW. The function that emits every event from user-mode code: EtwEventWrite in ntdll.dll.

Why the classic patch is dead

The traditional approach: overwrite the first 6 bytes of AmsiScanBuffer with instructions that return a clean result (0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3 — "mov eax, E_INVALIDARG / ret"). This worked. Then Defender signed the specific byte patterns. Now the patch itself is the signature. Write those bytes to amsi.dll's memory region and Defender flags the write, not the payload. The surveillance system is watching for people trying to blind the surveillance system.

The memory patch also requires calling VirtualProtect to mark executable memory as writable before writing. That call is loud — ETW logs it, behavioural engines flag it, and Sysmon Event ID 10 captures it.

The Hardware Breakpoint Mechanism

Every x86-64 CPU has four debug registers: DR0, DR1, DR2, DR3. They hold memory addresses. When the instruction pointer reaches an address stored in a debug register, the CPU raises a hardware exception — EXCEPTION_SINGLE_STEP (0x80000004). Debuggers use this as a breakpoint mechanism: pause execution without modifying target code.

DEBUG REGISTER LAYOUT — x86-64 ───────────────────────────────────────────────────────────── Register Purpose Our use ──────── ───────────────────────── ────────────────── DR0 Breakpoint address 0 → AmsiScanBuffer DR1 Breakpoint address 1 → EtwEventWrite DR2 Breakpoint address 2 (available) DR3 Breakpoint address 3 (available) DR6 Debug status (which fired) DR7 Enable/disable per register ───────────────────────────────────────────────────────────── Debug registers live in the CPU — not in RAM. Nothing to hash, sign, or integrity-check. Per-thread: one thread's breakpoints don't affect any other.

The Attack: Four Steps

Step 1 — Resolve the target addresses

Load amsi.dll and get the address of AmsiScanBuffer via GetProcAddress. Load ntdll.dll (already in every process) and get the address of EtwEventWrite the same way. These are the addresses we'll arm as breakpoints.

Step 2 — Install the VEH handler

Register a Vectored Exception Handler (VEH) with AddVectoredExceptionHandler. The VEH is a callback function in our code that fires for any exception in our process. We register it first, before arming breakpoints, so it's ready to handle the exceptions the breakpoints will generate.

Step 3 — Arm the debug registers

Call GetThreadContext on the current thread to retrieve the thread's register state. Write the addresses of AmsiScanBuffer and EtwEventWrite into context.Dr0 and context.Dr1. Enable both breakpoints in context.Dr7. Call SetThreadContext to apply the modified context. The CPU is now watching those addresses. No memory was modified.

Step 4 — The VEH intercepts the call

When PowerShell calls AmsiScanBuffer to scan script content:

  1. The CPU's instruction pointer hits DR0's address
  2. CPU raises EXCEPTION_SINGLE_STEP before the function executes a single instruction
  3. Our VEH handler fires
  4. Handler sets RAX to 0x80070057 (E_INVALIDARG — "AMSI unavailable")
  5. Handler reads the return address from the stack, writes it into RIP, adjusts RSP
  6. Returns EXCEPTION_CONTINUE_EXECUTION — resume at the caller, not at AmsiScanBuffer
  7. PowerShell sees E_INVALIDARG, treats AMSI as broken, executes the script without scanning

Same sequence fires for ETW: DR1 triggers on EtwEventWrite, VEH sets RAX to STATUS_SUCCESS (0 — "event logged successfully"), returns to caller. The caller believes the event was logged. It wasn't. Behavioural engine sees nothing.

Why "zero writes"

The memory patch writes to executable pages. VirtualProtect is called. Integrity checks can verify function preambles. ETW logs the protection change. All of that is detectable.

The HWBP technique writes only to context.Dr0, context.Dr1, and context.Dr7 — CPU register fields in a thread context struct on the stack. No write to executable memory. No VirtualProtect call. No page permission change. Nothing for integrity checking tools to find. PatchGuard doesn't monitor user-mode debug registers on non-protected processes.

The "Dark Room" — Both Watchers Blind Simultaneously

Running both together creates what we call the Dark Room: AMSI can't see what's in the script buffer, ETW can't see what the process is doing. A payload executing inside the Dark Room leaves no telemetry trail. Defender's static scanner checked the file — but there's no file. Defender's AMSI scanner checked the script — but got E_INVALIDARG. Defender's behavioural engine checked ETW — but got silence.

Validated result — vader-rootkit Phase DELTA + FOXTROT

Both bypasses confirmed operational on Windows 11 Home Build 26200 (24H2), Defender RTP enabled, standard user context. AMSI blind: PowerShell executes flagged payloads without alert. ETW blind: process injection, shellcode execution, thread creation — no behavioural events reach Defender's cloud engine. Zero memory writes to executable regions. Zero NtProtectVirtualMemory calls.

VULN-195458 — Filed MSRC 2026-06-16

Submitted to Microsoft Security Response Center with full reproduction steps, proof-of-concept code, and impact analysis.

MSRC response: "does not meet the bar for security servicing." Their reasoning: debug registers doing what debug registers are designed to do is not a security vulnerability in Windows. The bypass is an intended capability of a CPU debugging mechanism being applied to a non-debugging use case.

What this means operationally

Microsoft won't patch this at the OS level because the fix would break every legitimate debugger. The correct defence is detection at the application layer — monitoring for VEH registration on threads that subsequently call into amsi.dll, or EDR instrumentation of SetThreadContext calls that write to DR0-DR3. Most consumer-grade Defender does neither. Enterprise EDRs are better but still inconsistent on this specific technique.

Detection

SignalWhereReliabilityNotes
VEH registration on thread accessing AMSI User-mode EDR hooks on AddVectoredExceptionHandler Medium VEH registration is common for legitimate code. Signal is context — VEH + subsequent AMSI call + suspicious payload = suspicious. Not a standalone indicator.
SetThreadContext writing non-zero DR values Kernel ETW / EDR driver callbacks Medium Legitimate code rarely calls SetThreadContext on its own threads. Kernel-mode EDRs monitoring thread context changes can flag DR0-DR3 writes. Not universal coverage.
AMSI returning E_INVALIDARG repeatedly In-process AMSI monitoring Low Some EDRs instrument AmsiScanBuffer itself. If the hook fires after the HWBP diverts execution, it never fires — the VEH returns to the caller before the function executes.
Memory integrity: AmsiScanBuffer preamble unchanged Periodic integrity check by AV Not applicable This technique makes zero changes to AmsiScanBuffer's code. Integrity checks of the function preamble will pass clean every time.

Defensive Recommendations

Further Reading