Arbitrary R/W primitives

Arbitrary R/W primitives

TL;DR: Modern kernel exploitation is mostly primitive engineering — turning a tiny bug (single out-of-bounds qword, freed object, wrong sized write) into reliable “read 8 bytes from address X” and “write 8 bytes to address Y” calls.

What it is

A primitive is a reusable function callable from user mode that performs a memory operation in kernel context. Once you have arbitrary read (AR) + arbitrary write (AW), the exploit is mechanical: leak structures, walk lists, patch tokens, restore state, return cleanly. Without primitives, you have a crash. The art is shaping whatever the bug gives you into AR + AW with as few syscalls as possible.

Preconditions / where it applies

  • Initial bug provides any of: linear OOB read/write, type confusion, UAF, integer underflow on a size, partial pointer overwrite.
  • A “victim object” exists in the same SLAB cache/pool whose fields are read back to user mode (read) or used as a length/pointer (write).
  • Spray is feasible — i.e. the cache is not isolated/randstruct’d to the point of denying placement.

Technique

Common victim objects on Windows:

  • _PIPE_QUEUE_ENTRY / _PIPE_ATTRIBUTE — variable-size, sprayable via NtFsControlFile on named pipes; field read back via NtFsControlFile(FSCTL_PIPE_QUERY_ATTRIBUTE).
  • I/O ring entries (Win11) — _IORING_OBJECT fields including a kernel pointer.
  • _WNF_NAME_INSTANCE / WNF state data — sprayable, readable via NtQueryWnfStateData.

Pattern: AR.

  1. Overwrite a length field in the victim with a controlled large value.
  2. Issue the read syscall; kernel copies length bytes starting at the (still-original) data pointer → adjacent objects leak.

Pattern: AW.

  1. Overwrite the data pointer of a victim (e.g. pipe attribute value pointer) with the target kernel address.
  2. Issue the set/write syscall; kernel copies attacker bytes into the chosen address.

Pattern: data-only escalation. With AR + AW, no ROP needed: locate current EPROCESS, walk ActiveProcessLinks to find the System process, copy its Token over your EPROCESS.Token → instant SYSTEM. Or flip Previous Mode to 0 inside KTHREAD, then use NtReadVirtualMemory/NtWriteVirtualMemory against the kernel directly.

Cleanup. Restore overwritten lengths and pointers before returning; an unstable victim object that the kernel later frees with the wrong size or wrong pool produces a BSOD on a timer.

Detection and defence

  • Pool/freelist hardening, segment heap pool, kernel CFI (cfg-cet-kernel) and hvci-vbs limit what AR/AW can do (e.g. read-only PTEs for kernel code).
  • EDR sensors watching NtFsControlFile flood patterns and inconsistent Token references can flag this.
  • VBS-protected token / Credential Guard isolation makes some classic “copy System token” patterns less universal on 11/2022+.

References

See also: type-confusion-kernel, use-after-free-kernel, token-stealing-payloads.