Executive Summary
zer0matt’s Milan0day 2026 talk and accompanying writeup demonstrate a clean Bring Your Own Vulnerable Driver (BYOVD) chain that turns user-mode access into temporary kernel code patching, killing AV / EDR processes — and, in a second demo, hiding processes via Direct Kernel Object Manipulation — from kernel context. The vulnerable driver is ThrottleStop.sys (CVE-2025-7771); it exposes two IOCTLs that take an attacker-controlled physical address and map it via MmMapIoSpace, yielding arbitrary physical-memory read (0x80006498) and write (0x8000649C) primitives.
With physical-memory R/W on a signed driver, the rest of the chain is a familiar Windows-internals story executed cleanly: translate a chosen syscall’s virtual address into a physical one, overwrite its prologue with a tiny trampoline that calls a useful kernel routine, trigger the syscall from user mode to fire the patched path, and immediately restore the original bytes so that PatchGuard’s kernel-region verification doesn’t catch the modification. zer0matt picks NtAddAtom as the trampoline host because it’s directly callable from user mode through ntdll and almost never invoked by legitimate applications. Phase 1 routes it to PsLookupProcessByProcessId to lift the target _EPROCESS pointer; phase 2 routes it to PsTerminateProcess to kill the AV process from kernel mode — demonstrated against Microsoft Defender’s MsMpEng.exe. The PoC is on GitHub.
Overview
The piece accompanies a Milan0day 2026 conference talk and is structured around a single guiding question: given a vulnerable signed driver that exposes arbitrary physical-memory read and write through MmMapIoSpace, what is the minimum-effort path from “user-mode binary” to “kill an EDR from kernel context”? zer0matt’s answer chains four pieces — the BYOVD primitive itself, virtual-to-physical translation, temporary inline-patching of a chosen syscall, and the choice of useful kernel callees — into a clean and rerunnable exploit.
The Windows Trust Boundary
Windows enforces the standard x86 privilege split through CPU-level ring separation:
- Ring 3 (user mode) — processes live in isolated virtual address spaces, can’t touch physical memory directly, can’t issue privileged CPU instructions, and have to request anything sensitive through the syscall interface.
- Ring 0 (kernel mode) — unrestricted: physical memory, kernel data structures, hardware MMIO regions, privileged instructions, and every other process’s context. Most user-mode security mechanisms (token rights, integrity levels, AppContainer, PPL) only exist because kernel code chooses to enforce them.

The exploitation invariant zer0matt highlights: once arbitrary kernel read/write is in the attacker’s hands, almost no user-mode security boundary stays meaningful — including the protection model EDRs depend on.
IOCTL Dispatch Pipeline
User-mode software talks to a driver through IOCTLs. The data path is the textbook one:
- The driver registers a symbolic link (e.g.
\\.\ThrottleStop) so user-mode processes can open a handle to its device object. - A user-mode process calls
CreateFileon that symbolic link, then issuesDeviceIoControlwith the desired IOCTL code, an input buffer and an output buffer. - The I/O Manager packages the call into an IRP (
IRP_MJ_DEVICE_CONTROL) and forwards it to the driver. - The driver’s dispatch routine reads the IOCTL code, runs a
switchover the codes it supports, and executes the matching handler. - The handler writes results into the output buffer and completes the IRP — user mode wakes up and reads the output.

CreateFile → DeviceIoControl → IRP → driver switch-case. Source: original article (credited to unknowncheats.me).The interesting attack surface lives inside the handlers’ switch arms. A handler that accepts an attacker-supplied address or buffer pointer and passes it through to a privileged kernel routine without validating it — like a handler that wraps MmMapIoSpace — is a BYOVD primitive waiting to be triggered.
Vulnerable Drivers and MmMapIoSpace Abuse
The vulnerable driver in this writeup is ThrottleStop.sys (CVE-2025-7771) — the kernel component of a popular CPU-tuning utility that ships signed and is loadable on current Windows. The driver exposes two IOCTLs that, when properly validated, would be normal hardware-control plumbing. They aren’t validated:
0x80006498— arbitrary physical-memory read viaMmMapIoSpace.0x8000649C— arbitrary physical-memory write viaMmMapIoSpace.
MmMapIoSpace is normally used by drivers to map device-MMIO regions into kernel virtual address space. When the destination physical address is taken straight from a user-supplied IOCTL input buffer with no range checks, “device MMIO” becomes “any physical page in the system” — including pages backing kernel code, the nt image’s read-only sections, and live _EPROCESS structures.

0x8000649C in ThrottleStop.sys calling MmMapIoSpace with an attacker-controlled physical address. Source: original article.Why Physical Memory Matters
Every running thread — including kernel threads — addresses memory through virtual addresses. The MMU translates those into physical addresses on every access using the page tables. MmMapIoSpace doesn’t take a virtual address; it takes a physical one. So a BYOVD chain that wants to overwrite a kernel function’s first instruction has to first answer the question: what physical page does that function’s virtual address sit on?
Historically, exploits walked the page tables directly through self-referencing PTE entries. Windows 10 1803 closed that path with mitigations that hide the PML4 self-reference. Modern BYOVD chains lean on alternative VA-to-PA translation primitives:
- Superfetch / SysMain information leaks — system calls that prefetch surfaces leak physical-address metadata to user mode in some Windows builds.
- PFN database abuse — mapping the kernel’s
MmPfnDatabaseby reading its base from a known symbol and walking entries. - Driver-side translation oracles — some vulnerable drivers expose VA-to-PA helpers (e.g. wrapping
MmGetPhysicalAddress) as IOCTLs in addition to the R/W primitive itself.
Once the physical address of the target kernel routine is known, the 0x8000649C write IOCTL hands the attacker an arbitrary-byte writer into that page.
Why AV/EDR Processes Are Not Special
Every Windows process is represented in the kernel by an _EPROCESS structure — PID, parent PID, token pointer, image-file pointer, the ActiveProcessLinks doubly-linked list entry, the Pcb.DirectoryTableBase (CR3) value, the Protection byte that says “this process is PPL”, and so on. AV / EDR agents are no exception. They have an _EPROCESS; their protection level is just a byte. They have kernel-mode callbacks (process create, image load, registry, object); those callbacks are pointers in arrays. They have IOCTL dispatch routines and worker threads, all reachable through pointers in kernel data structures.
With arbitrary kernel write, every one of those structures and pointers becomes mutable. PPL protection is a comparison against an attacker-writable byte; callback arrays are attacker-writable; process termination is just PsTerminateProcess(target_eproc, status).
From Physical Memory R/W to Kernel Code Execution
The leap from “I can write a byte to any physical address” to “I can execute arbitrary kernel-mode code” is a single page-write: locate a kernel function whose virtual address is known and stable (it’s exported by ntoskrnl.exe and resolvable from user mode via NtQuerySystemInformation(SystemModuleInformation)), translate that VA to a PA, and use the 0x8000649C IOCTL to overwrite the function’s prologue bytes with a short jump into attacker-chosen code.
zer0matt’s chain picks NtAddAtom as the prologue to patch — deliberately, because user-mode code can trigger NtAddAtom directly through ntdll.dll!NtAddAtom, so the attacker doesn’t need any additional execution-redirection trick. The patched syscall fires whenever the user-mode binary calls it.
Note: the original article includes annotated screenshots of the NtAddAtom prologue both before the patch and with the inline-hook trampoline installed (one diagram of the unpatched prologue, one of the trampoline). Those two Blogger-CDN URLs did not survive the import path used here; please view them in the source post for the byte-level detail. The rest of the chain is preserved.
Temporary Kernel Patching
The exploit deliberately avoids leaving any patches in place. The full pattern is Patch → Trigger → Restore:
- Patch. Save the original first N bytes of
NtAddAtom, then overwrite them with a tiny trampoline thatJMPs to the kernel routine the attacker actually wants to call. - Trigger. Call
NtAddAtomfrom user mode. The CPU executes the trampoline; the chosen kernel routine runs with full ring-0 privilege; its return value comes back to the user-mode caller through whatever calling-convention slot the trampoline preserves. - Restore. Immediately write the saved original bytes back over the trampoline. Subsequent
NtAddAtomcalls behave normally.
The Restore step is not optional — it’s the only reason PatchGuard doesn’t notice. Subsections below cover the phase 1 (lift the EPROCESS pointer) and phase 2 (kill the target) variants of this trampoline.
Phase 1 — Obtaining the Target EPROCESS
The kernel routine the attacker calls in phase 1 is PsLookupProcessByProcessId — it takes a PID, returns a pointer to the matching _EPROCESS structure, and adds a reference. The trampoline redirects NtAddAtom into a tiny stub that:
- Loads the target PID from a register slot the user-mode call already populated.
- Calls
PsLookupProcessByProcessId(pid, &peproc). - Stashes
peprocinto a writable kernel location the attacker can read back through the0x80006498physical-read IOCTL. - Returns cleanly so the user-mode caller’s
NtAddAtomdoesn’t blow up.
Original NtAddAtom bytes are restored immediately after the call. The attacker now holds the kernel-virtual _EPROCESS pointer for the target process — for instance, MsMpEng.exe.
Phase 2 — Killing the AV/EDR
Same trampoline structure, different callee. Phase 2 patches NtAddAtom again, this time redirecting to PsTerminateProcess(eproc, status). The leaked _EPROCESS pointer from phase 1 is supplied as the first argument; the second argument is the exit status the attacker wants on the dying process. PsTerminateProcess runs in ring 0, doesn’t honour PPL the way user-mode TerminateProcess does, and the target falls over. Original bytes restored, PatchGuard quiet.
PatchGuard Considerations
PatchGuard periodically hashes critical kernel code regions and compares the result against expected values. A trampoline left sitting at NtAddAtom‘s prologue eventually shows up in a verification pass and the system bugchecks with CRITICAL_STRUCTURE_CORRUPTION (0x109). The Patch → Trigger → Restore pattern keeps the modified state on the order of microseconds — the entire window between writing the trampoline, executing the patched syscall, and writing the saved bytes back — which is too small to land inside any practical PatchGuard verification round.
The same pattern generalises beyond NtAddAtom: any exported kernel routine whose VA is known can be the trampoline host, and any other exported kernel routine can be the callee. Pick the host for “easy to call from user mode, low normal-traffic”, and the callee for “does the thing you want from ring 0”.
Demo Time
Two demos accompany the writeup. The PoC, build instructions, and the demo scripts are at github.com/zer0matt/Milan0day2026.

MsMpEng.exe via the NtAddAtom→PsTerminateProcess trampoline. Source: original article.
_EPROCESS from ActiveProcessLinks removes it from Task Manager’s listing. Source: original article.Beyond Process Termination
The same physical-memory R/W primitive extends well past killing AV. Once the attacker can write any byte anywhere in kernel memory, the catalogue of follow-on moves is essentially the EDR-evasion canon:
- Remove kernel callbacks. Zero the function pointers in
PspCreateProcessNotifyRoutine,PsSetCreateThreadNotifyRoutine, image-load callback arrays, and registry callbacks. The EDR no longer receives the notifications it was relying on. - Disable telemetry hooks. Patch
EtwEventWrite/EtwpEventWriteFullto early-return, neutralising Threat-Intelligence ETW. - Strip PPL. Clear or downgrade the
Protectionbyte in target_EPROCESSstructures so user-mode tooling can open them withPROCESS_ALL_ACCESS. - Token theft / LPE. Locate the
SYSTEMprocess token and copy its pointer into another process’sTokenfield — the classic local privilege escalation primitive. - Arbitrary kernel object overwrite. Anything reachable from a kernel-mode pointer — driver dispatch tables, security descriptors, object headers — is in scope.
Final Thoughts
zer0matt’s closing line is the line everyone working on Windows kernel security should pin to the wall: driver signing does not imply driver safety. ThrottleStop.sys is a fully signed, currently-loadable Microsoft-approved driver. The signature only attests that the publisher is who they say they are. It does not attest that the IOCTL handlers behind that signature validate the addresses, buffer pointers, or operation parameters they were handed. As long as the Windows ecosystem keeps shipping signed drivers whose IOCTL handlers expose unrestricted physical-memory mapping, arbitrary MSR access, raw port I/O, or arbitrary kernel writes, BYOVD will keep being the cheapest reliable path through modern EDR defences.
Key Takeaways
- Signed != safe. A WHQL or vendor-signed driver attests authorship, not validation of IOCTL inputs.
ThrottleStop.sysis fully signed. MmMapIoSpacewith an attacker-controlled address is one of the strongest primitives on Windows. It maps device-MMIO; with no range checks it maps kernel code pages too.- Temporary inline patching dodges PatchGuard. Patch → Trigger → Restore keeps the modified state below the kernel-verification window.
NtAddAtomis an ideal trampoline host: directly callable from user mode viantdll, almost never seen in legitimate workloads, easy to identify by symbol.PsLookupProcessByProcessId+PsTerminateProcesscompose into a kernel-mode AV killer. Phase 1 lifts the EPROCESS, phase 2 terminates — PPL is irrelevant because the call originates in ring 0.- The same R/W primitive trivially extends to callback unhooking, ETW silencing, PPL stripping, token theft, and DKOM-based process hiding.
- BYOVD remains the cheapest reliable kernel-execution path on current Windows. Mitigation has to happen at the loadable-driver gate, not after the driver is in.
Defensive Recommendations
- Enable and update Microsoft’s Vulnerable Driver Blocklist. The HVCI-enforced blocklist (
DriverSiPolicy.p7b) is the canonical mitigation surface; addThrottleStop.sys(CVE-2025-7771) to your fleet’s blocklist if Microsoft has not already. - Run with HVCI / VBS enabled. Hypervisor-Protected Code Integrity prevents some classes of kernel patching even when the driver loads; combined with Memory Integrity it raises the bar substantially.
- Audit signed third-party drivers for
MmMapIoSpace,ZwMapViewOfSection,__readmsr/__writemsrand rawin/outin IOCTL handlers. The pattern “IOCTL input buffer flows into one of these without a range check” is the BYOVD telltale. - Application Allowlisting (WDAC) for drivers. Enforce a policy that names the drivers your fleet actually needs; deny everything else. Stops “user with admin downloads a random signed driver and loads it” cold.
- Hunt for the Patch → Trigger → Restore pattern. A short burst of writes to a kernel-text physical page that immediately revert is anomalous. Memory-integrity audit events (
HypervisorCodeIntegrity) and ETWMicrosoft-Windows-Kernel-Memoryare the canonical sources. - Watch for
NtAddAtomfrom processes that have no business using atom tables — a binary callingNtAddAtomin tight succession with no UI / clipboard activity is a soft indicator the inline-hook trampoline is firing. - Defence in depth at the AV/EDR layer: kernel-resident self-protection that re-arms periodically and rejects user-mode TerminateProcess from any context (not just non-PPL) reduces the blast radius of even a successful phase-2 termination.
- Inventory and remove unused vendor-supplied drivers — CPU-tuning, GPU-overclocking, and motherboard-RGB control drivers are the canonical BYOVD origin for kernel R/W primitives.
Conclusion
The piece is a clean, well-structured demonstration of the modern BYOVD pattern: pick a signed driver whose IOCTL handlers expose unconstrained physical-memory primitives, parlay that into kernel code patching through temporary inline trampolines, and use the ring-0 execution slot to issue any nt export the attacker likes — phase 1 to find the target, phase 2 to act on it. The technique itself is not novel, but the chain composition is unusually elegant and the PoC is public. Defenders should treat the Microsoft Vulnerable Driver Blocklist + HVCI as a single non-optional baseline on every Windows endpoint, and audit any third-party driver allowed to load against the four canonical BYOVD telltales (MmMapIoSpace, MSR access, port I/O, arbitrary kernel writes) in its IOCTL handlers.
Original text: “Whoops! I did it again. I patched Windows Kernel at Milan0day 2026” by zer0matt at zer0matt’s blog. PoC: github.com/zer0matt/Milan0day2026.

