Wednesday, September 23, 2026

Turning a WRMSR primitive into kernel execution: inside MSRKit

Turning a WRMSR primitive into kernel execution: inside MSRKit

MSRKit is a Windows kernel research toolkit that converts an arbitrary MSR write in a vulnerable driver into arbitrary kernel function calls and unsigned driver mapping, intended for authorized exploitation research.

ToolRurixis/MSRKit — C++ toolkit for calling kernel functions and mapping unsigned drivers via a WRMSR ROP chain through AmdTools64.sys
CategoryWindows kernel exploitation / driver mapping research
Primary UseDemonstrating, in an authorized lab, how an MSR read/write IOCTL primitive in a signed-but-vulnerable driver can be escalated to arbitrary kernel code execution and ExAllocatePoolWithTag-backed driver mapping
Safe UseFor kernel-exploitation education, driver-hardening research, and authorized red-team assessments on systems you own or have written permission to test; HVCI must be disabled, which itself signals research-lab usage
Telemetry NoteLoads AmdTools64.sys via NtLoadDriver or a PnP devnode, issues characteristic MSR IOCTL 0xFFF02804 traffic, writes to IA32_LSTAR, and pins threads to CPU 0 — all observable via driver-load telemetry, EDR kernel callbacks, and hypervisor-vantage monitoring

MSRKit sits at the intersection of two long-running themes in Windows offensive security research: bring-your-own-vulnerable-driver (BYOVD) abuse and control-register hijacking. The repository, written in C++ by Rurixis, demonstrates a concept that is academically interesting even to defenders: given only a driver that exposes model-specific register (MSR) reads and writes to unprivileged callers, the tool escalates that single primitive into full arbitrary kernel function invocation and, from there, into mapping and executing an entirely unsigned driver. It is a compact, readable reference implementation of a modern escalation chain, which is exactly why it belongs in an authorized research context rather than a production one.

The target primitive lives in AmdTools64.sys, a signed AMD utility driver whose IOCTL interface includes direct RDMSR/WRMSR passthrough. MSRKit first loads the driver via NtLoadDriver, and the README notes a thoughtful fallback: if the classic device symlink is unavailable, it enumerates the PnP devnode and locates the device interface by GUID. That dual-path loader tells you the author cared about compatibility across driver versions and installation states, which is a hallmark of a serious research tool rather than a throwaway proof of concept.

The core trick revolves around IA32_LSTAR, the MSR that holds the target RIP of the syscall instruction on x64 Windows. MSRKit reads the current value through the driver's MSR read IOCTL (0xFFF02804), then scans ntoskrnl.exe in memory for a small set of ROP gadgets — the README explicitly names pop rcx; ret, mov cr4, rcx; ret, and wbinvd; ret. The scan target is the loaded kernel image itself, so gadget addresses are computed at runtime rather than hardcoded, which keeps the chain portable across builds without needing symbol offsets shipped in the binary.

Execution then proceeds by hijacking IA32_LSTAR itself. The entry gadget overwrites the MSR, the tool clears the alignment-check bit in IA32_FMASK, and issues a user-mode syscall. Control lands in the gadget chain running in kernel context, which toggles the SMEP and SMAP bits in CR4, dispatches the attacker-chosen function on the kernel stack, and then — critically — restores CR4 and LSTAR before returning to user mode via sysretq. That restore discipline matters: a sloppy implementation would leave the system in a corrupted state and bluescreen on the next legitimate syscall, so the cleanup logic is a design point worth studying, not a footnote.

Kernel function calling is exposed two ways. The call verb resolves a named ntoskrnl export by name, parses integer arguments (0x prefix accepted for hex), invokes it, and prints the return value; call_at does the same for an arbitrary kernel virtual address. The README's honest limitation here is instructive: Zw/Nt syscall stubs cannot be invoked from the LSTAR context because they re-enter KiSystemService, recursing into the very mechanism being abused. Encountering that kind of documented dead-end tells you the author actually exercised the tool rather than sketching it.

Driver mapping is the second capability, and it reuses the same call machinery. MSRKit allocates nonpaged kernel pool via ExAllocatePoolWithTag, copies a prepared PE image into it — relocations applied, imports resolved, and notably the security cookie patched — then calls the mapped driver's entry point with caller-supplied arguments. The unmap verb frees a previously mapped image by base address. In other words, once you can call any kernel function, a manual mapper is a straightforward composition, and this repo shows that composition explicitly.

The build system and library interface reinforce the research framing. Compilation requires Visual Studio with MSVC v143 toolset and ml64.exe for MASM, driven through standard cmake generation, and produces both a CLI executable (msrkit.exe) and a static library (msrkit.lib). The library API is clean: MSRK::INIT takes the vulnerable driver path (or an already-open handle to skip loading), MSRK::CALL and MSRK::MAP wrap the primitives, and MSRK::CLEANUP tears down. Control Flow Guard is explicitly disabled (/guard:cf-) while stack cookies remain enabled — a telling detail, since indirect dispatch through a ROP chain fights CFG instrumentation.

Compatibility claims are broad: all Windows 10 and 11 builds, KPTI-compatible, and both PnP and non-PnP driver loading paths. The glaring exception is HVCI — hypervisor-protected code integrity must be disabled for the technique to work at all. That is not a small caveat; it is the entire defensive countermeasure story. On any modern hardened deployment with HVCI/memory integrity enabled, the SMEP/CR4 games and unsigned mapping described here simply do not fly, which makes MSRKit as much an argument for enabling VBS as it is an attack demonstration.

Operational limitations are documented candidly. Execution is single-core: the calling thread is pinned to CPU 0 for the duration of the kernel interaction, because IA32_LSTAR is per-processor and a syscall on the wrong core would not hit the hijacked value. Concurrent LSTAR hijacks from separate processes are called out as a guaranteed BSOD — one caller at a time, enforced by architecture rather than by any lock in the code.

From a blue-team perspective, MSRKit is a useful mental model for detection engineering. The telemetry footprint is distinctive: a load of AmdTools64.sys on systems with no legitimate AMD tooling reason, IOCTL traffic on the known code, writes to IA32_LSTAR observable from hypervisor vantage points, thread affinity suddenly pinned to a single core, and ExAllocatePoolWithTag allocations followed by execution from nonpaged pool. Microsoft's vulnerable-driver blocklist and attack-surface-reduction driver-load rules exist precisely to interdict the first link in this chain.

For the authorized practitioner — the exploit developer studying kernel escalation, the driver author auditing whether your IOCTL surface exposes raw MSR access, or the red team member demonstrating BYOVD impact to a client with written permission — MSRKit reads as a well-documented, compact reference. Clone it from github.com/Rurixis/MSRKit, build it in a lab VM with HVCI disabled and snapshots taken, and treat every limitation the README admits to as a lesson in how fragile and how powerful a single misdirected WRMSR can be.

Official project repository for Rurixis/MSRKit.
Download Tool

Educational analysis for authorized security professionals. Use only in controlled, authorized environments.

Share articleFacebookXLinkedIn

Continue exploring

Browse all articles →

0 comentários:

Post a Comment

Note: Only a member of this blog may post a comment.