Chapter 41

Kernel Architecture

Ring 0 vs Ring 3, Windows kernel components (ntoskrnl, HAL, win32k), KPCR, IRQL levels, the syscall dispatch mechanism, and Virtualization-Based Security

Scenario

A researcher's analysis shows a suspicious driver loaded at ring 0. From that position it has direct access to every process's address space, can read and write arbitrary memory, can suppress EDR callbacks, modify the SSDT, and hide its own loaded module from all kernel enumeration APIs. The entire user-mode security stack is beneath it. Understanding kernel architecture is the prerequisite for understanding both what a kernel rootkit can do and why Virtualization-Based Security (VBS) changes the threat model fundamentally.

CPU Ring Privilege Model

x86/x64 Privilege Rings (Windows uses only 2):

  Ring 0 (CPL 0) — Kernel Mode
    ntoskrnl.exe, hal.dll, win32k.sys, drivers (.sys)
    Unrestricted: access any physical address, execute privileged
    instructions (CLI, HLT, LIDT, MOV CRx, RDMSR/WRMSR)
    No MMU restrictions on kernel VA space

  Ring 1, 2 — Unused by Windows
    (Used in older VMM designs; x64 doesn't meaningfully support them)

  Ring 3 (CPL 3) — User Mode
    All applications, services, Win32 subsystem DLLs
    MMU enforces: cannot access kernel VA, cannot execute privileged instructions
    Transition to Ring 0 only via syscall/sysenter or exception/interrupt

  CPL stored in the low 2 bits of CS register
  Syscall instruction: CPL 3 → CPL 0 (via MSR_STAR/MSR_LSTAR config)
  IRETQ: return to user mode (CPL 0 → CPL 3)

Windows Kernel Components

ComponentFileRole
NT Executive / Kernel ntoskrnl.exe Core OS: process/thread/memory management, I/O manager, security reference monitor, object manager, configuration manager (registry), plug-and-play manager
Hardware Abstraction Layer hal.dll Abstracts hardware differences (interrupt controllers, DMA, timers) so ntoskrnl doesn't need hardware-specific code
Win32k subsystem win32k.sys Win32 GUI kernel code: window management, GDI, DirectX. Historically a large attack surface (many CVEs). Partially moved to user mode in Win10.
Kernel-mode drivers *.sys in Drivers/ Device drivers, filter drivers, security drivers (EDR, AV). All run at Ring 0 with same privilege as ntoskrnl.
Secure Kernel (VBS) securekernel.exe (VTL1) Runs in Secure World (VTL1), isolated from even Ring 0 code. Manages VSM, Credential Guard, HVCI enforcement.

KPCR and IRQL

The Kernel Processor Control Region (KPCR) is a per-CPU data structure pointed to by the gs segment register in kernel mode (same gs register used for TEB in user mode — the base address changes on syscall entry/exit). It contains per-CPU state critical for scheduling and interrupt handling:

// Key KPCR fields (Windows 10/11 x64, offsets approximate)
// gs:[0] in kernel mode = KPCR base
typedef struct _KPCR {
    union {
        NT_TIB  NtTib;                // +0x000: ExceptionList etc
    };
    PVOID     GdtBase;                // +0x018: Global Descriptor Table
    PVOID     TssBase;                // +0x020: Task State Segment
    ULONG64   UserRsp;                // +0x038: saved user RSP on syscall
    PKPCR     Self;                   // +0x018: self-pointer
    PKPRCB    CurrentPrcb;            // +0x020: processor control block
    KIRQL     Irql;                   // +0x264: current IRQL
    ULONG     IRQLTable[...];
    // KPRCB follows immediately after KPCR
    KPRCB     Prcb;                   // Current thread, DPC queue, etc.
} KPCR;

IRQL (Interrupt Request Level) is a software-defined priority mechanism for Windows kernel code. Code running at a higher IRQL preempts lower-IRQL code:

IRQL LevelValueContext
PASSIVE_LEVEL 0 Normal thread execution (user mode and most kernel code)
APC_LEVEL 1 APC delivery; code at this level can block APCs
DISPATCH_LEVEL 2 DPC (Deferred Procedure Call) and scheduler. Cannot wait or take page faults!
3-26 (device IRQLs) 3-26 Hardware interrupt levels; device drivers responding to IRQs
CLOCK_LEVEL 13 (APIC) System timer clock interrupt (drives scheduler quantum)
IPI_LEVEL 29 Inter-processor interrupt
HIGH_LEVEL 31 Machine check, NMI; interrupts completely disabled
DISPATCH_LEVEL restriction

Code running at DISPATCH_LEVEL cannot: take page faults (all accessed memory must be non-pageable, in NonPagedPool), call any function that might block or wait, access pageable memory. Many kernel bugs and BSODs come from violating this rule — accessing paged pool or calling WaitForXxx at DISPATCH_LEVEL. Rootkits that run at DISPATCH_LEVEL to disable pre-emption for critical sections must follow these same rules.

Syscall Dispatch

The syscall handler is configured via Model-Specific Registers at boot:

; MSR_LSTAR (0xC0000082) = address of KiSystemCall64
; When user-mode "syscall" executes:
;   1. CPL: 3 → 0 (Ring 3 → Ring 0)
;   2. RIP saved to RCX (user return address)
;   3. RSP swapped to kernel stack (from KPCR.UserRsp / KPCR.KernelRsp)
;   4. RIP = MSR_LSTAR (KiSystemCall64)
;
; KiSystemCall64 flow:
KiSystemCall64:
    swapgs                  ; swap GS base: user TEB ↔ kernel KPCR
    mov qword [gs:UserRsp], rsp  ; save user RSP
    mov rsp, [gs:KernelRsp] ; switch to kernel stack
    push rcx                ; save user RIP (return address)
    push r11                ; save user RFLAGS
    ; ... save full register context ...
    ; Dispatch: EAX = syscall number → index into KiServiceTable (SSDT)
    and eax, 0xFFF          ; mask to 12 bits
    call QWORD [KiServiceTable + rax*8]  ; call kernel implementation
    ; ... restore registers ...
    swapgs                  ; swap back to user TEB
    sysretq                 ; return to user mode (RIP from RCX)

Virtualization-Based Security (VBS / VTL)

VBS uses the CPU's hypervisor capabilities (Intel VT-x / AMD-V) to create two "Virtual Trust Levels" (VTLs): VTL0 is the normal world (where Ring 0 kernel runs), and VTL1 is the secure world (where the Secure Kernel runs). VTL1 is more privileged than VTL0's Ring 0:

VBS Trust Level Architecture:

  VTL1 (Secure World) ──────────────── securekernel.exe
    Manages hypervisor page table overrides (SLAT)
    HVCI: marks kernel code pages read-only in VTL0
    Credential Guard: keeps NT hashes in VSM secure memory
    Cannot be compromised by VTL0 kernel code (even Ring 0)

  VTL0 (Normal World)
    Ring 0: ntoskrnl.exe, drivers
    Ring 3: user applications, EDR DLLs
    All ring 0 code is BELOW VTL1 privilege
    A kernel rootkit in VTL0 Ring 0 cannot:
      - Modify page protections that VTL1 has set
      - Access secure memory (VSM regions)
      - Disable HVCI enforcement

  Hypervisor (Type-1 hypervisor built into Windows)
    Manages SLAT (Second Level Address Translation)
    Enforces VTL1 access policy
HVCI and driver signing

Hypervisor-Protected Code Integrity (HVCI, also called Memory Integrity) enforces that all kernel code must be from Authenticode-signed files. The Secure Kernel (VTL1) controls which pages are executable in Ring 0. Even if a kernel driver tries to allocate RWX memory and write shellcode, VTL1 marks all anonymous kernel allocations as non-executable. Only mapped image sections (from signed PEs) get execute permission. This is why exploiting a vulnerable signed driver (BYOVD — Bring Your Own Vulnerable Driver) to achieve unsigned kernel code execution requires disabling HVCI first, which itself is protected by VTL1. HVCI is enabled by default on modern Windows 11 hardware.

Q & A

If a rootkit has Ring 0 code execution, can it disable HVCI to load unsigned code?

Not without also compromising VTL1. HVCI is enforced by the Secure Kernel in VTL1. The enforcement mechanism uses Second Level Address Translation (SLAT / EPT on Intel): the hypervisor maps all kernel code pages as read-only and non-writable at the hardware page table level. Even Ring 0 code in VTL0 cannot write to these pages — the hardware-level page protection from VTL1 overrides software page table entries in VTL0. To disable HVCI, an attacker would need to: (1) Exploit the hypervisor itself (extremely difficult — Windows' hypervisor is small, well-audited, and verified by Secure Boot). (2) Exploit the Secure Kernel (similarly protected). (3) Perform a TOCTOU attack on the Secure Boot chain at boot time (requires physical access or firmware compromise). In practice, kernel code execution in VTL0 Ring 0 on an HVCI-enabled system is much less powerful than on a non-HVCI system: the rootkit can still DKOM, modify kernel data structures, and interfere with callbacks, but it cannot execute arbitrary unsigned code — it's limited to ROP chains through existing kernel code, or must use gadgets from already-loaded signed drivers. This is a significant constraint. The BYOVD (Bring Your Own Vulnerable Driver) technique partially addresses this: load a legitimate but exploitable signed driver, use it to get kernel read/write primitives, then use those primitives to escalate further. But HVCI still blocks injecting new unsigned code pages.

What is the KPRCB and how does it relate to the KPCR?

The Kernel Processor Control Block (KPRCB) is an embedded structure within the KPCR — it immediately follows the KPCR header in memory. While the KPCR contains CPU-global per-processor settings (GDT base, TSS base, IRQL, user RSP), the KPRCB contains more dynamic per-processor state: (1) CurrentThread — pointer to the KTHREAD currently executing on this CPU. This is how the kernel quickly finds the currently running thread: KeGetCurrentThread() accesses gs:[KPCR.Prcb.CurrentThread]. (2) NextThread — thread scheduled to run next. (3) IdleThread — the idle thread for this CPU. (4) DpcListHead — the list of Deferred Procedure Calls pending for this CPU. (5) QuantumEnd — flag indicating if the current thread's quantum has expired. (6) Various counters and profiling data. (7) On NUMA systems: NUMA node information. For rootkit analysis: a rootkit that reads gs:[KPCR.Prcb.CurrentThread] can find the currently executing KTHREAD, and from there the EPROCESS (thread's process), and from there the token. This is how kernel shellcode typically escalates privileges: read the current process's token, find the SYSTEM process's token, overwrite the current process's token with SYSTEM's token. Security implications: KPCR/KPRCB are the foundation of the "token stealing" privilege escalation primitive that appears in countless kernel exploits.