There’s a layer below your kernel. It’s been there since Windows 8 in any meaningful sense and since Windows 10 on most enterprise deployments. It doesn’t appear in WinDbg sessions, doesn’t show up in driver stacks, and the only time most kernel developers think about it is when HVCI stops something from working. But if you’re writing detection tooling, analysing rootkits, or trying to understand why certain categories of kernel exploitation stopped working on modern Windows — you need a working model of what’s actually happening below ntoskrnl.exe.

This is the first post in a three-part series on hypervisor internals and EPT-based hooking. It’s deliberately introductory. If you already know what a VM-exit is and what the VMCS looks like, skip to Part 2 where we hijack Hyper-V’s exit handler. If not, read on.

Hardware virtualisation: VMX root and VMX non-root

Intel added hardware virtualisation support (VT-x) in 2005. AMD followed shortly after with AMD-V. Both add the same fundamental thing: a new CPU operating mode specifically for running guest operating systems with hardware-enforced isolation.

VT-x defines two modes:

VMX root operation — where the hypervisor (Virtual Machine Monitor, VMM) runs. Has access to VMCS (Virtual Machine Control Structure) and special instructions (VMREAD, VMWRITE, VMLAUNCH, VMRESUME, VMPTRLD, etc.).

VMX non-root operation — where the guest runs. This is where Windows’s kernel lives. Ring-0 code in the guest still executes as ring-0 — it has full kernel privilege within the guest’s view — but a subset of instructions and conditions cause the CPU to automatically stop the guest and call the hypervisor’s exit handler.

The critical point: ring-0 in a guest is not the same as ring-0 on bare metal. The guest’s kernel executes most code at native speed, but it cannot prevent the hypervisor from intercepting certain operations. HVCI exploits this.

The VM-exit: when the guest loses control

A VM-exit is the hardware mechanism by which the CPU leaves VMX non-root mode and transfers control to the hypervisor. When a VM-exit fires:

  1. The CPU atomically saves the guest’s complete register state into the VMCS (instruction pointer, registers, stack pointer, control registers, segment descriptors — everything)
  2. Execution switches to VMX root mode
  3. The CPU jumps to the address stored in the VMCS’s VM-exit handler field — the hypervisor’s exit handler
  4. The hypervisor inspects the exit reason and guest state, does whatever it needs to, and either modifies guest state or falls through
  5. The hypervisor executes VMRESUME (or VMLAUNCH for first entry) to resume the guest from where it stopped

The guest doesn’t see any of this. From its perspective, certain instructions appear to execute normally (because the hypervisor emulates a result and resumes), or they complete slightly more slowly. The round-trip to VMX root mode and back typically takes a few hundred nanoseconds — measureable with RDTSC, but rarely significant.

Guest (VMX non-root)Windows NT kernel, drivers, usermodering-0 and ring-3 run hereVM-exit trigger: CPUID / EPT fault / RDMSR / …Hardware (CPU)saves guest state → VMCS guest-state areaRIP, RSP, RCX…R15, CR0, CR3, CR4, RFLAGS, …jump to VMCS host-state RIPVMX root mode — VMM handlervmexit_handler_detour() → original Hyper-V handlerinspect exit reason, read/modify VMCS guest stateVMRESUMErestoresguest stateExit reasonsCPUID (always)EPT violationRDMSR / WRMSRCR3 writeVMCALLThe guest never observes the exit. From its perspective the instruction completes (or the hypervisor emulates a result).

What triggers a VM-exit

The VMM configures which operations exit using fields in the VMCS (specifically the VM-execution control fields). Common exit triggers:

Exit reasonDefault in Hyper-VNotes
CPUIDAlways exitsUnconditional — cannot be suppressed
EPT violationAlways exitsGuest accessed unmapped/protected guest-physical page
RDMSR / WRMSRConfigurable per MSR bitmapExits on specific MSRs
MOV CR3Can be enabledFires on process switches
VMCALLAlways exitsExplicit guest→VMM hypercall instruction
RDTSCOften disabledEnabled to prevent timing-based VM detection
HLTEnabledVP scheduling

CPUID is the one that matters for this series. CPUID exits unconditionally — in VMX non-root mode, the hardware always stops the guest and calls the VMM, regardless of VMCS configuration. This makes it a universal, stable communication channel from guest to hypervisor. No kernel privilege required; user-mode code can fire a CPUID and reach the hypervisor.

In assembly:

nasm
; Guest fires a hypercall by executing CPUID with a packed sentinel value in EAX/RCX
; The CPU immediately exits to the hypervisor
; Inputs in RCX, RDX, R8, R9 are visible to the hypervisor via VMCS guest registers

mov     eax, 0          ; CPUID leaf (ignored by our handler, leaf check skipped)
cpuid                   ; → VM-exit to hypervisor
; hypervisor has now run and we're back; RAX contains the return value

The hypervisor identifies the CPUID exit by reading the exit reason from the VMCS (VMREAD VM_EXIT_REASON), checks our authentication keys in RCX (more on this in Part 2), and handles or falls through.

Windows runs under Hyper-V — always

On any Windows 11 machine (and most Windows 10 enterprise installs), Hyper-V is active. The Windows NT kernel runs in VMX non-root mode as Hyper-V’s “root partition”. WSL2, Windows Sandbox, Credential Guard, HVCI — all depend on this. Hyper-V is not optional; it’s a foundational layer of the modern Windows security model.

You can detect this at any privilege level:

nasm
; CPUID leaf 0x40000000 is the hypervisor identification leaf
; If a hypervisor is running, it intercepts this exit and writes its signature
mov     eax, 40000000h
cpuid
; On Hyper-V: EBX:ECX:EDX contains the string "Microsoft Hv"
; On bare metal (no hypervisor): returns 0 or garbage from hardware
c
// C version
int regs[4];
__cpuid(regs, 0x40000000);
// regs[1] = 'rciM', regs[2] = 'foso', regs[3] = 'vH t'
// together: "Microsoft Hv" in little-endian dword layout

The Hyper-V CPUID interface is documented: leaf 0x40000001 returns supported features, 0x40000003 returns implementation-specific details. These are the same leaves that utilities like coreinfo.exe and cpuid query to detect virtualisation.

HVCI: how the hypervisor enforces code integrity

HVCI (Hypervisor-Protected Code Integrity) uses the hypervisor’s control over EPT (Extended Page Tables) to enforce kernel code signing in a way that the kernel itself cannot bypass, even if fully compromised.

The mechanism:

  1. All kernel-mode page table modifications pass through the hypervisor (EPT grants write access only to approved page table entries)
  2. Before any kernel page is marked executable, the hypervisor verifies the content is signed code
  3. Even with arbitrary kernel write (e.g., via BYOVD), you cannot mark your shellcode executable — the hypervisor intercepts the page table write and refuses

This is why unsigned kernel shellcode injection stopped working on HVCI-enabled systems, why you can’t clear CR0.WP to patch kernel text, and why the interesting exploitation work moved below the hypervisor level. If the hypervisor enforces the constraint, you have to subvert the hypervisor — not the kernel.

The attack surface no one patches

Hyper-V is large, complex, ships in every Windows installation, and runs with more privilege than anything else on the machine. Its attack surface includes:

  • The VM-exit handler that processes every guest exit from every virtual processor
  • The VMCS management code
  • The hypercall interface (Hyper-V exposes VMCALL-based hypercalls of its own)
  • The scheduler, memory manager, and device emulation paths

EPTraitor targets the VM-exit handler directly. If we can install a detour in that function before the system is fully locked down, we intercept every VM-exit and operate below any guest-side security mechanism.

Part 2 covers how to find the VM-exit handler, install the detour from UEFI boot space, and validate the CPUID-based hypercall ABI. Part 3 covers EPT shadow page hooks — invisible kernel function interception using the same SLAT machinery that HVCI depends on.