22nd Survey Division
Kernel Security BYOVD Driver Exploitation EDR Evasion

BYOVD Ring 0: Kernel Callback Zeroing via RTCore64.sys

George Wu July 2026 22nd Survey Division ring-stack Phase LIMA / vader-rootkit
T1014
T1068
T1562.001
T1543.003
Plain English
Windows trusts certain signed drivers completely. We load one that has a known vulnerability, use it as a master key into the kernel, then erase the entries that tell Defender when a new process starts. Defender goes blind to process creation — no alert fires. PatchGuard catches the modification and BSODs the machine, but by then the damage is done.

Validated Arbitrary kernel read/write via RTCore64.sys, Windows 11, Defender RTP enabled.
Validated 10 callbacks zeroed in PspCreateProcessNotifyRoutine — confirmed 2026-07-06.
Validated Zero Defender alerts across all phases of kill chain.
Research EDR blind without BSOD (pool reads + RET-patch still in progress).

Background: The Two Worlds Inside Windows

Windows splits the processor into two privilege levels. Your browser, your game, your AV agent — they all live in Ring 3 (user mode). The kernel — the part that manages memory, processes, drivers, and the hardware — lives in Ring 0. A user mode program can't just read or write kernel memory. The CPU enforces that boundary in silicon.

EDRs (Endpoint Detection and Response tools like Defender, CrowdStrike, SentinelOne) register callbacks inside the kernel. Every time a new process starts, the kernel walks a private list of registered callbacks and calls each one — that's how the security product sees what's happening. It's a phone-tap installed directly into the OS nervous system.

The structure in question: PspCreateProcessNotifyRoutine

A private kernel array — not exported, not documented — that holds up to 64 callback pointers. When any process starts, ntoskrnl.exe loops through this array and calls each registered function. EDRs register here. Zero their entry and they stop getting called. They go blind to new processes.

The problem: to write to kernel memory, you need to be running in Ring 0. An attacker in user mode shouldn't be able to do that. This is the wall BYOVD is designed to break.

The BYOVD Concept: The Traitor Knight

BYOVD — Bring Your Own Vulnerable Driver — is the technique of loading a legitimately signed, Windows-trusted kernel driver that contains a vulnerability allowing arbitrary memory access. You exploit the driver's own bug from user mode. The driver, running in Ring 0, performs the read or write on your behalf.

USER MODE (Ring 3) KERNEL (Ring 0) ───────────────────────────────────────────────────────────────────── NORMAL PATH: Your program ───── syscall ────────────────→ ntoskrnl.exe (controlled, validated) (CPU enforces boundary) BYOVD PATH: Your program ── DeviceIoControl ──→ RTCore64.sys (signed, trusted) │ └─→ arbitrary kernel R/W (driver does it, CPU satisfied) ───────────────────────────────────────────────────────────────────── Result: attacker writes to PspCreateProcessNotifyRoutine from Ring 3. No exploit of Windows itself. Windows trusts the driver.

Windows has no way to distinguish "this driver is being used legitimately by MSI Afterburner" from "this driver is being weaponised." A signed driver is a signed driver. The kernel trusts the signature. The exploit lives inside the driver.

The Driver: RTCore64.sys (CVE-2019-16098)

RTCore64.sys is a kernel driver shipped with MSI Afterburner — the GPU overclocking tool used by millions of PC gamers. It exposes arbitrary kernel memory read and write from user mode via a device control interface (IOCTL). That's not a bug introduced by attackers. That's literally what the driver does, by design, to let the overclocking software touch hardware registers.

CVE-2019-16098 — the vulnerability

The driver does not validate that the memory addresses passed via IOCTL are safe to access. Any process can open the device \\.\RTCore64 and send read/write requests targeting arbitrary kernel addresses. The certificate on the driver is valid. Windows loads it without complaint.

The driver supports two operations via IOCTL:

Important: the struct layout matters

Public PoCs for CVE-2019-16098 describe struct layouts based on older driver versions. Our binary (hash 01aa278b) has a different layout. Sending the wrong struct returns Error 87 — The parameter is incorrect. The driver silently rejects it. We had to reverse our exact binary in Ghidra to get the correct field offsets before anything worked.

Reversing the struct in Ghidra

The IOCTL dispatch handler lives at the driver's IRP_MJ_DEVICE_CONTROL major function. In DRIVER_OBJECT, the MajorFunction array starts at offset 0x70, each pointer is 8 bytes, so MajorFunction[14] (IOCTL handler) = 0x70 + (14 × 8) = 0xE0. The handler was FUN_00011450.

Inside the handler, the read primitive wrote its result to puVar11[7] — offset 28 in the struct. Reconstructed struct:

// RTCore64 read request — reversed from binary hash 01aa278b
typedef struct _RTCore64Read {
    ULONG64  Address;    // offset  0 — kernel address to read from
    ULONG32  Offset;     // offset  8 — added to Address
    ULONG32  _pad1;      // offset 12
    ULONG64  _pad2;      // offset 16
    ULONG32  Value;      // offset 28 — result placed here by driver
} RTCore64Read;

The write struct is identical with Value as the input instead of output, sent via IOCTL 0x8000204c.

Five Steps to Kernel Write

Step 1
Load the driver

Requires SYSTEM or Administrator. Use sc.exe to register and start the driver as a kernel service. Windows checks the Authenticode signature — valid, loads without question. The device \\.\RTCore64 becomes available to any process that opens it.

Step 2
Confirm the read primitive works — MZ header test

Load ntoskrnl.exe as a flat file to find its base address (the loaded image VA). Read 4 bytes from that address via IOCTL. A valid kernel read returns 0x00905A4D — that's MZ\x90\x00, the PE magic bytes at the start of every Windows executable. If you get MZ, the read works. There is no other acceptable proof.

Kernel read confirmed — validated output

Read at ntoskrnl.exe base returned value=905a4d — little-endian MZ header. Defender RTP on. No alert. No log. The CPU boundary crossed silently through a trusted driver.

Step 3
Find PspCreateProcessNotifyRoutine

The callback array is private — not exported, no public symbol. But PsSetCreateProcessNotifyRoutine IS exported, and it references the private array via a RIP-relative LEA instruction. Load ntoskrnl as a flat file, scan its bytes for the LEA pattern in PsSetCreateProcessNotifyRoutine, compute the array's kernel virtual address. No hardcoded addresses — works across kernel updates.

Step 4
Enumerate callback entries

Each entry in the array is an EX_FAST_REF structure — a pointer with the low 4 bits used as a reference counter. Read each slot to find registered callbacks. On a standard Windows 11 box: 5 process callbacks registered at PspCreateProcessNotifyRoutine.

Step 5
Zero the callback array — EDR goes blind

Use the write primitive (IOCTL 0x8000204c) to write zero to each callback slot in the array. After zeroing, when a new process starts, ntoskrnl walks the array and finds nothing registered. Defender's callback never fires. The process appears on disk — but no notification reaches the AV engine.

PatchGuard Detonation: Why the BSOD Is a Feature

Windows has a second layer of kernel self-defence called PatchGuard (Kernel Patch Protection). It runs in encrypted, randomly-timed kernel threads. Periodically — the timing is intentionally unpredictable — it checksums critical kernel structures. If any checksum fails, it calls KeBugCheckEx(0x109) immediately. The system dies. Blue screen.

PspCreateProcessNotifyRoutine is one of the structures PatchGuard monitors. Zero its entries and PatchGuard will eventually notice the checksum mismatch and fire a BSOD.

0x109 CRITICAL_STRUCTURE_CORRUPTION — the proof the write worked

PatchGuard only triggers if the modification landed. If the write had failed, PatchGuard would have found the array intact and done nothing. So a BSOD with bugcheck 0x109 after zeroing callbacks is not a failure — it is confirmation the write primitive works exactly as expected.

Validated result — 2026-07-06

strike.exe zeroed 10 callbacks at fffff801240ec1e0. PatchGuard fired 0x109 CRITICAL_STRUCTURE_CORRUPTION. VM BSODed. Exfiltration and encryption had already completed before the BSOD detonated. Zero Defender alerts across all phases. The crash interrupted forensics and covered tracks — exactly the design.

The operational pattern: BYOVD + callback zeroing is not used to keep the machine running indefinitely. It's used to create a window of EDR blindness long enough to complete the objective (exfil, encryption, lateral movement). The BSOD lands afterward. It looks like a driver crash, not an intrusion.

What the Full Kill Chain Can Do

The validated BYOVD primitive (vader-rootkit Phase LIMA) exposes three separate capabilities via the same RTCore64.sys write primitive:

Command What it does Status
callbacks Zero PspCreateProcessNotifyRoutine — EDR blind to process creation Validated
token <PID> Walk EPROCESS list, steal SYSTEM token, inject into target process — instant SYSTEM Built
dse Disable Driver Signature Enforcement — load unsigned drivers Built
all <PID> Full chain: token steal + callback removal in one pass Built

Two drivers are supported. RTCore64.sys (CVE-2019-16098, MSI Afterburner) and dbutil_2_3.sys (CVE-2021-21551, Dell BIOS Utility). Same primitives, different IOCTL structs. The codebase handles both.

The Next Wall: Staying Alive After the Window

PatchGuard monitors the array data — the pointer values in each callback slot. Zeroing the data triggers the checksum failure. The next evolution is to leave the array intact and instead patch the callback function itself.

RET-patch approach (in progress)

Instead of zeroing the EX_FAST_REF entry in the array, find the actual callback function the entry points to (which lives in kernel pool memory, not kernel image space). Write a single byte 0xC3 (RET instruction) at its entry point. The callback gets called — PatchGuard's checksum of the array is intact — but the function immediately returns without executing. EDR blind, no checksum failure, no BSOD.

The difficulty: the EX_CALLBACK_ROUTINE_BLOCK lives in kernel pool memory (ffffc40b... addresses). RTCore64's primitives work cleanly against kernel image space (fffff801...) but crash the system when targeting pool addresses — the driver dereferences them in kernel mode with no exception handling. Pool reads require a different approach.

The RTCore64 + dbutil BYOVD approach is also supported by dbutil_2_3.sys (Dell, CVE-2021-21551), which uses the same general IOCTL pattern but may have different pool access behaviour. This is the current research direction.

Detection

Detection signal Where Reliability Notes
Sysmon Event ID 6 — driver load Sysmon + Windows Event Log Medium RTCore64.sys load will be logged. Check SHA256 hash against known vulnerable driver lists (LOLDrivers project).
Certificate revocation check Network / proxy Low Driver certificate is valid. Revocation requires vendor to revoke it. MSI has not revoked CVE-2019-16098 certs as of 2026.
sc.exe create + sc.exe start for kernel-type service Process creation logs Medium Unusual for a user to manually load a kernel driver via sc.exe. Alert on kernel service creation outside standard install paths.
0x109 BSOD crash dump Windows Event Log, crash dump High (post-mortem) CRITICAL_STRUCTURE_CORRUPTION after the fact. Confirms callback modification happened. Dump will show the zeroed array if captured before reboot.
Microsoft Vulnerable Driver Blocklist Windows Defender Credential Guard / HVCI High HVCI (Hypervisor-Protected Code Integrity) with the blocklist enabled blocks RTCore64.sys entirely. Most enterprise deployments have this. Consumer Windows 11 Home does not enable it by default.

The effective defence: HVCI + blocklist

Hypervisor-Protected Code Integrity (HVCI) with Microsoft's Vulnerable Driver Blocklist prevents any driver on the list from loading. RTCore64.sys (CVE-2019-16098) is on the blocklist. On a system with HVCI enabled, this entire attack chain fails at Step 1 — the driver load is blocked before it reaches the kernel. The gap: HVCI is off by default on Windows Home. Enterprise is largely covered. Consumer is not.

Defensive Recommendations

What's Not Covered Here

The BYOVD loader, the IOCTL structs, and the callback enumeration logic are from rainfantry's private research (vader-rootkit, ring-stack). No source code is published. The validated results and technique description are shared to enable detection and defence.

The next phase of this research — pool-safe R/W for RET-patching callback functions — is ongoing. Results will be published when validated.

Further Reading