HAL and Ntoskrnl: The Kernel Core Components

Objective: Understand the architecture and division of labor between hal.dll (the Hardware Abstraction Layer) and ntoskrnl.exe (the NT kernel and Executive), how they are loaded during boot, the structures and routines each exposes, and how defenders inspect, detect tampering against, and harden these Ring 0 core components.


1. HAL and Ntoskrnl Overview

Two binaries sit at the bottom of Windows kernel mode and everything else builds on them. ntoskrnl.exe is the NT kernel plus the Executive — the policy and service layer of the OS. hal.dll is the Hardware Abstraction Layer — a thin platform shim that hides interrupt controllers, bus topology, timers, and DMA behind a uniform interface so the rest of the kernel stays hardware-independent.

BinaryFull nameLoaded byRing
ntoskrnl.exeNT OS Kernel + Executivewinload.efiRing 0
hal.dllHardware Abstraction Layerwinload.efiRing 0

Both reside in %SystemRoot%\System32\. On multiprocessor systems the SMP-aware image ntkrnlmp.exe is selected by the loader and presented as ntoskrnl.exe; modern Windows 10/11 ships only the SMP variant. Verify image identity and signature on a live host with sigcheck, dumpbin /headers, or the WinDbg lm command. The separation exists for portability (HAL absorbs platform differences) and layering (the kernel implements scheduling and policy, not chipset quirks).


2. Boot Handoff: From Bootloader to KiSystemStartup

winload.efi loads ntoskrnl.exe and hal.dll into memory, then transfers control to the kernel entry point KiSystemStartup, passing a pointer to a LOADER_PARAMETER_BLOCK. That structure carries the memory descriptor list, the ARC hardware tree, NLS data, and other boot-time state the kernel needs before it can manage its own memory.

winload.efi
  └─ loads ntoskrnl.exe + hal.dll
       └─ ntoskrnl!KiSystemStartup(PLOADER_PARAMETER_BLOCK)
            ├─ HalInitializeProcessor()    ; HAL brings up per-CPU hardware
            ├─ KiInitializeKernel()        ; KPCR/KPRCB, IDT, GDT
            ├─ Executive phase init:
            │    Mm/Ob/Se/Io/Cm/Ps InitSystem()
            └─ PsInitialSystemProcess()    ; System process (PID 4)
                 └─ Phase 1: smss.exe launched

HAL initializes the processor before the Executive runs a single line of policy code. Secure Boot validates the winload.efi → ntoskrnl.exe / hal.dll chain in firmware, so tampering with either binary on disk breaks the boot chain on a properly configured machine.


Boot sequence flow diagram showing UEFI firmware validating winload.efi which loads hal.dll and ntoskrnl.exe passing a LOADER_PARAMETER_BLOCK before the Executive initializes
Secure Boot validates each link in the chain; winload.efi loads both HAL and the kernel before handing off control to KiSystemStartup.

3. The HAL: Abstracting the Hardware

The HAL translates abstract requests into platform-specific operations: programming the APIC, translating bus-relative addresses, allocating DMA-coherent buffers, and calibrating the stall timer. Drivers and the kernel call HAL routines instead of touching hardware registers directly.

RoutinePurpose
HalGetInterruptVectorTranslate a bus IRQ to a system interrupt vector and required IRQL
HalTranslateBusAddressConvert a bus-relative address to a logical address
HalAllocateCommonBufferAllocate DMA-coherent memory visible to CPU and device
KeStallExecutionProcessorCalibrated busy-wait (HAL-implemented on most platforms)
HalRequestSoftwareInterruptRequest a software interrupt at a given IRQL to trigger DPC delivery

On modern ACPI systems the HAL is far thinner than in the NT 4 era. Many classic Hal* exports such as HalGetInterruptVector are deprecated; the PnP/ACPI stack and IoConnectInterruptEx now handle interrupt wiring. Since Windows 8, HAL Extensions (halextpcat.dll, halextintc.dll, and similar PE images loaded by HAL itself) carry SoC- and OEM-specific code without replacing the whole HAL.


4. IRQL: The Kernel’s Preemption Ladder

Interrupt Request Level (IRQL) is the central arbitration mechanism shared by HAL and the kernel. The HAL programs the interrupt controller to enforce IRQL in hardware; running at an IRQL masks all interrupts at or below that level on the current CPU.

IRQL (x64)Symbolic nameUsed for
0PASSIVE_LEVELNormal thread execution
1APC_LEVELAPC delivery; paging allowed
2DISPATCH_LEVELScheduler, spin locks; no paging, no blocking
3–12Device IRQLsHardware ISRs
13CLOCK_LEVELClock interrupt
14PROFILE_LEVELProfiling interrupt
15HIGH_LEVELNMI, machine check

The cardinal rule: at DISPATCH_LEVEL or above you may not touch pageable memory or block, because the scheduler and page fault handler cannot run. A driver that dereferences paged-out memory at elevated IRQL produces the classic IRQL_NOT_LESS_OR_EQUAL bug check. Query the current level with KeGetCurrentIrql(). IRQL numeric values are architecture-specific; the table above is the canonical x64 mapping.


Hierarchy diagram of Windows x64 IRQL levels from PASSIVE at 0 up through APC, DISPATCH, CLOCK, IPI, POWER to HIGH at 31 showing preemption priority
Running at DISPATCH_LEVEL or above masks the scheduler and page-fault handler — any pageable memory access at this level triggers an IRQL_NOT_LESS_OR_EQUAL bug check.

5. The Kernel Layer (Ke): Scheduling and Synchronization

The Ke layer sits directly above HAL and implements thread scheduling, interrupt and exception dispatch, and the low-level synchronization primitives the rest of the system depends on.

RoutineWhat it does
KeInitializeSpinLockInitialize a spin-lock object
KeAcquireSpinLockRaise IRQL to DISPATCH_LEVEL and acquire the lock
KeReleaseSpinLockRelease the lock and restore the saved IRQL
KeInsertQueueDpcQueue a Deferred Procedure Call
KeWaitForSingleObjectWait on a dispatcher object (event, mutex, timer, thread)
KeSetEventSet a kernel event to the signaled state

Dispatcher objects — events, mutexes, semaphores, timers, threads — share a common DISPATCHER_HEADER carrying Type, SignalState, and WaitListHead. The wait machinery keys off that header. The synchronization pattern below runs at PASSIVE_LEVEL, where blocking is legal:

KEVENT readyEvent;
KeInitializeEvent(&readyEvent, NotificationEvent, FALSE);

// ... another thread eventually calls KeSetEvent(&readyEvent, IO_NO_INCREMENT, FALSE);

NTSTATUS status = KeWaitForSingleObject(
    &readyEvent,        // dispatcher object
    Executive,          // wait reason
    KernelMode,         // processor mode
    FALSE,              // non-alertable
    NULL);              // no timeout

Per-CPU scheduler state lives in the KPCR (Kernel Processor Control Region), reachable via gs:[0] on x64, with an embedded KPRCB holding CurrentThread, NextThread, IdleThread, and the DPC queue.


6. The Executive Layer (Ex and Friends)

The Executive comprises the higher-level managers, each identified by a two-letter prefix. They build on Ke primitives and HAL services.

ManagerPrefixResponsibilities
Object ManagerObObject lifecycle, handles, reference counting
Process/Thread ManagerPsEPROCESS/ETHREAD creation and teardown
Memory ManagerMmVAD trees, PTEs, page faults, pool
I/O ManagerIoIRP lifecycle, driver loading
Security Reference MonitorSeAccess checks, tokens, privileges
Configuration ManagerCmRegistry hive management
Executive SupportExPool allocation, lookaside lists, callbacks

Correct pool usage on modern Windows uses ExAllocatePool2 (the successor to ExAllocatePoolWithTag, deprecated starting Windows 10 build 19041) paired with ExFreePoolWithTag:

// Allocate non-paged pool with a 4-byte tag (read in WinDbg as 'XgAT').
PVOID buffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, 0x1000, 'TAgX');
if (buffer != NULL) {
    // ... use buffer at IRQL <= DISPATCH_LEVEL ...
    ExFreePoolWithTag(buffer, 'TAgX');
}

The Object Manager exposes ObReferenceObjectByHandle to convert a handle into a referenced kernel object pointer — the gateway every component crosses when validating access.


7. Key Kernel Structures

A handful of structures are the backbone of process, thread, and CPU state. Defenders and rootkit authors alike walk these every day.

StructureKey fields
EPROCESSUniqueProcessId, ActiveProcessLinks, Token, VadRoot, Peb, ImageFileName[15], ThreadListHead
ETHREADCid (CLIENT_ID), ThreadListEntry, Win32StartAddress, embedded KTHREAD
KTHREADHeader (DISPATCHER_HEADER), KernelStack, State, WaitIrql, Teb
KPCRPer-CPU; IRQL, IDT/GDT pointers, pointer to KPRCB
KPRCBCurrentThread, NextThread, IdleThread, DPC queue
KDPCDeferredRoutine, DeferredContext, DpcListEntry

ActiveProcessLinks is a doubly linked LIST_ENTRY chaining every EPROCESS. The Task Manager view of “all processes” is, at bottom, a walk of this list. That makes it a prime DKOM target: unlinking an EPROCESS hides the process from list-based enumeration while it continues to run and be scheduled — covered in Section 10.


8. The SSDT and System Call Dispatch

A user-mode SYSCALL instruction transfers Ring 3 → Ring 0 and lands in ntoskrnl!KiSystemCall64. The dispatcher indexes the System Service Dispatch Table via KeServiceDescriptorTable, which points at KiServiceTable (an array of service routine offsets) and KiArgumentTable (argument byte counts). GUI calls into win32k.sys route through the shadow table KeServiceDescriptorTableShadow.

Patching KiServiceTable so a service index points at attacker code is the classic SSDT hook, historically used by rootkits to intercept NtQuerySystemInformation, NtOpenProcess, and similar. On x64 this is exactly the kind of structure modification PatchGuard validates, so SSDT hooking is loud and largely obsolete on modern systems — but understanding the dispatch path is essential for reading both live disassembly and integrity-check telemetry.


Flow diagram of the Windows system call dispatch path from user-mode SYSCALL instruction through KiSystemCall64 and KeServiceDescriptorTable to the target Nt service routine
The SYSCALL instruction transfers execution to KiSystemCall64, which uses the service index to look up the target routine in KiServiceTable — the structure SSDT hooks manipulate and PatchGuard protects.

9. Live Analysis with WinDbg and Volatility

Load Microsoft symbols and the entire layout becomes navigable. List the core modules and dump structures directly:

0: kd> lm m nt              ; ntoskrnl base, range, symbols
0: kd> lm m hal             ; hal.dll base and range
0: kd> dt nt!_EPROCESS      ; full EPROCESS field layout
0: kd> !process 0 0         ; enumerate processes via ActiveProcessLinks
0: kd> !pcr 0               ; KPCR for CPU 0
0: kd> !prcb 0              ; KPRCB: CurrentThread / IdleThread
0: kd> dps nt!KeServiceDescriptorTable   ; SSDT pointer + service count
0: kd> !idt                 ; IDT vectors (HAL-programmed interrupt routing)

For dead-box memory forensics, Volatility 3 reconstructs the same view from a dump and is the natural cross-check against a possibly compromised live host:

# Enumerate processes and loaded kernel modules from a memory image.
vol -f memory.dmp windows.pslist
vol -f memory.dmp windows.modules

# psscan walks pool tags instead of ActiveProcessLinks; a process that
# appears in psscan but NOT in pslist is a candidate DKOM-unlinked process.
vol -f memory.dmp windows.psscan

A delta between windows.pslist (list-based) and windows.psscan (pool-scan-based) is a high-fidelity indicator of ActiveProcessLinks tampering.


10. Common Attacker Techniques

Kernel-core abuse turns on either modifying ntoskrnl structures from a loaded driver or exploiting a vulnerability to reach Ring 0 in the first place.

TechniqueDescription
SSDT hookingPatch KiServiceTable entries to intercept syscalls
DKOM unlinkingSplice an EPROCESS out of ActiveProcessLinks to hide a process
Kernel callback removalStrip PsSetCreateProcessNotifyRoutine entries to blind EDR
BYOVDLoad a vulnerable signed driver to gain a Ring 0 primitive
Kernel exploitationAbuse an ntoskrnl/HAL bug to escalate Ring 3 → Ring 0
In-memory image patchPatch ntoskrnl.exe code pages at runtime

A malicious driver is still loaded through the documented path — a Services registry key of Type = 1 followed by a load — which is exactly where detection begins. Bring-Your-Own-Vulnerable-Driver remains popular precisely because it sidesteps the need to find a fresh kernel bug.


Graph diagram showing attacker path from BYOVD through Ring 0 code execution branching into DKOM process unlinking, SSDT hooking, and callback removal all leading to hidden process or driver impact
BYOVD is the most common Ring 0 entry point; once there, attackers choose between DKOM, SSDT hooks, or callback removal to achieve persistence and evasion.

11. Defensive Strategies & Detection

Detection centers on driver loads, integrity events, and kernel structure cross-checks.

Sysmon Event IDNameRelevance
6Driver LoadedKernel driver load with Signed, Hashes, Signature fields
7Image LoadedModule loads in unusual contexts
13Registry Value SetNew Services driver entries

Pair Sysmon with Windows event sources: System Event ID 7045 (new kernel-mode service installed), Security Event ID 5038 (image hash invalid — DSE failure), and Event ID 6281 (page hash mismatch). The Microsoft-Windows-Kernel-Memory ETW provider surfaces pool allocations useful for hunting pool-based implants.

title: Suspicious Unsigned Kernel Driver Load
logsource:
  product: windows
  service: sysmon
detection:
  selection:
    EventID: 6
    Signed: 'false'
  filter_legit:
    ImageLoaded|startswith:
      - 'C:\Windows\System32\drivers\'
      - 'C:\Windows\System32\DriverStore\'
  condition: selection and not filter_legit
level: high
MechanismDescription
PatchGuard (KPP)Validates SSDT, IDT, GDT, KPCR, and kernel code; bug check 0x109 on tampering
Driver Signature Enforcementci.dll requires Authenticode-signed drivers
HVCIVTL1 enforces signed Ring 0 code; blunts BYOVD and runtime patching
Secure BootValidates the winload → ntoskrnl/hal chain in firmware

Operational hardening: enable HVCI (Core Isolation → Memory Integrity), confirm Secure Boot in msinfo32, audit SeLoadDriverPrivilege use, deploy the Microsoft Vulnerable Driver Blocklist (DriverSiPolicy.p7b), monitor HKLM\SYSTEM\CurrentControlSet\Services\ for new Type = 1 entries, and baseline loaded-module hashes against periodic WinPmem/Volatility snapshots.


12. MITRE ATT&CK Mapping

TechniqueMITRE IDDetection
RootkitT1014Volatility pslist/psscan delta; PatchGuard bug check 0x109
Kernel Modules and ExtensionsT1547.006Sysmon EID 6; Event ID 7045; Services key writes
Exploitation for Privilege EscalationT1068Crash telemetry, anomalous Ring 0 transitions
Impair DefensesT1562.001Missing kernel callbacks; EDR self-protection alerts
Process InjectionT1055Kernel KeStackAttachProcess/MmCopyVirtualMemory use
Modify System ImageT1601.001Code integrity Event ID 5038/6281; PatchGuard

13. Tools for Kernel Analysis

ToolDescriptionLink
WinDbgLive and dump kernel debugging, structure walksmicrosoft.com
Volatility 3Memory forensics, pslist/psscan/modulesvolatilityfoundation.org
WinPmemLive memory acquisitiongithub.com
Process HackerDriver and handle inspectionprocesshacker.sourceforge.io
SysmonDriver-load and registry telemetrysysinternals.com
sigcheckImage signature and hash verificationsysinternals.com
GhidraStatic analysis of drivers and ntoskrnlghidra-sre.org

14. Summary

  • HAL and ntoskrnl are the two Ring 0 binaries every other Windows component is built on — HAL abstracts hardware, ntoskrnl implements the kernel and Executive policy layers.
  • The kernel layer (Ke) supplies scheduling and synchronization; the Executive (Ob, Ps, Mm, Io, Se, Cm, Ex) builds managers on top, all arbitrated by IRQL that the HAL enforces in hardware.
  • Core structures — EPROCESS, ETHREAD, KPCR, the SSDT — are the backbone of process and CPU state and the prime targets for SSDT hooks, DKOM unlinking, and callback removal.
  • Detect kernel tampering via Sysmon Event ID 6, Event IDs 7045/5038/6281, and Volatility pslist-vs-psscan deltas; prevent it with HVCI, DSE, Secure Boot, and the vulnerable-driver blocklist.

Related Tutorials

References

User Mode vs Kernel Mode: Privilege Rings and the Boundary

Objective: Understand the architectural separation between user mode (Ring 3) and kernel mode (Ring 0) on Windows — how Intel hardware enforces it, how the Windows OS layers process isolation and the system call dispatch path on top, and why this boundary is the central battleground for rootkits, EDR, and modern kernel hardening.


1. Why Rings Exist — The Hardware Contract

Intel x86/x64 CPUs define four hardware privilege levels — called rings — numbered 0 (most privileged) through 3 (least privileged). The currently executing privilege level is encoded in the low two bits of the CS segment register and is referred to as the Current Privilege Level (CPL). Every memory access, every instruction fetch, and every attempt at a privileged instruction is checked against this value by the CPU itself, before any OS code runs.

Windows collapses Intel’s four rings into two:

FeatureUser ModeKernel Mode
Ring / CPLRing 3 (CPL = 3)Ring 0 (CPL = 0)
Memory accessUser VA onlyFull kernel + user VA
Privileged instructionsFaults with #GPAllowed
Address space isolationPer-process privateSingle shared VA across all drivers
Crash blast radiusProcess terminationBug check (BSOD)
Entry mechanismNative executionSYSCALL / interrupt / exception

Rings 1 and 2 exist in hardware but are unused by Windows — using only Ring 0 and Ring 3 maps cleanly to the “supervisor vs. user” model and is portable to architectures (ARM64, older RISC) that don’t expose intermediate levels. The instant Ring 3 code attempts to execute LGDT, LIDT, RDMSR, WRMSR, HLT, CLI, STI, or any I/O instruction outside its IOPB, the CPU raises a General Protection Fault (#GP) and the kernel terminates the offending thread.

This single hardware guarantee — CPL is checked by silicon, not software — is what makes the user/kernel boundary trustworthy in the first place.


Hierarchy diagram showing Intel's four privilege rings with Ring 0 (kernel) and Ring 3 (user) used by Windows, Rings 1 and 2 unused, and the CPU's CPL enforcing the boundary.
Windows collapses Intel’s four hardware rings into two; the CPU’s Current Privilege Level field in CS enforces the boundary in silicon.

2. User Mode: The Sandboxed World

When Windows launches an application, it creates a process with its own private virtual address space, its own handle table, and a security token. On x64, the user-mode half of the address space spans 0x00000000000000000x00007FFFFFFFFFFF (128 TB). Anything above that canonical boundary is kernel territory and is unmapped from user mode page tables (especially under KVA Shadow).

User-mode code can:

  • Allocate memory in its own VA via VirtualAlloc.
  • Open handles to kernel objects through documented APIs.
  • Spawn threads and processes via the Win32 subsystem (csrss.exe).

User-mode code cannot:

  • Read or write another process’s memory without explicit handle access.
  • Touch kernel VA, modify page tables, or read MSRs directly.
  • Service interrupts, install drivers, or hook the IDT/GDT.

Every meaningful operation that touches hardware, files, networking, or kernel objects must therefore traverse the user/kernel boundary through a system call.


3. Kernel Mode: The Shared Kingdom

In contrast to user mode’s per-process isolation, all kernel-mode code shares a single virtual address space. ntoskrnl.exe, the HAL, file system drivers, network stack drivers, and every third-party driver loaded on the system all coexist in the same address space, on the same privilege level, with no memory protection between them.

RegionPurpose
Non-paged poolKernel allocations that must remain resident (DPC/ISR code, kernel objects)
Paged poolKernel allocations that can be paged out
System PTE regionKernel-managed page table entries for I/O mapping
HAL / driver image rangeLoaded driver .text/.data sections

A buggy driver writing to the wrong pointer can corrupt another driver’s structures or the kernel’s own state. A crash in any kernel component triggers a bug check (BSOD) because, unlike user mode, there is no isolation boundary to contain the damage. This is also exactly why attackers want Ring 0: once executing in kernel mode, malicious code has the same authority over the OS as the OS itself.


4. Crossing the Boundary — The SYSCALL Path

Every Win32 API that touches the kernel eventually reaches an ntdll.dll stub. On x64 those stubs all have the same shape:

; ntdll!NtReadFile (representative)
mov   r10, rcx              ; preserve arg1 (RCX is clobbered by SYSCALL)
mov   eax, 0x06             ; syscall number (build-specific; illustrative)
syscall                     ; user -> kernel transition
ret

The SYSCALL instruction is the choreography of the boundary crossing. The CPU performs all of the following atomically:

StepCPU action
1Saves user RIP into RCX
2Saves user RFLAGS into R11
3Masks RFLAGS per IA32_FMASK (MSR 0xC0000084) — clears IF so interrupts are off at kernel entry
4Loads new CS/SS selectors from IA32_STAR (MSR 0xC0000081)
5Loads RIP from IA32_LSTAR (MSR 0xC0000082) — points to nt!KiSystemCall64
6Transitions CPL from 3 to 0

From here, control is in Windows. The kernel-side dispatch chain is:

FunctionRole
nt!KiSystemCall64Entry point loaded from IA32_LSTAR. Executes swapgs to swap user GS for kernel GS, switches to the kernel stack, allocates and populates a _KTRAP_FRAME with the saved user-mode register state. With KVA Shadow (KPTI) enabled, the variant KiSystemCall64Shadow is used to swap page tables first.
nt!KiSystemServiceUserLocates the current _KTHREAD via GS:[0x188] and sets KTHREAD.PreviousMode = UserMode (1) so the kernel knows arguments came from Ring 3 and must be probed.
nt!KiSystemServiceStartSplits the syscall number in EAX into a table identifier (high bits) and a service index (low bits).
nt!KiSystemServiceRepeatSelects KeServiceDescriptorTable (Nt* executive calls) or KeServiceDescriptorTableShadow (Win32k GUI calls), validates the argument count, and dispatches.
Service routine (e.g. nt!NtReadFile)Validates user pointers (ProbeForRead / ProbeForWrite) and performs the work.
SYSRETRestores RIP from RCX, RFLAGS from R11, transitions CPL from 0 back to 3, and the caller returns from ntdll.

The key takeaway for defenders: every user-mode action eventually appears in EAX as a syscall number — and EDR products that hook only in user space (in ntdll) can be bypassed by re-implementing this exact stub in attacker code (direct/indirect syscalls).


Flow diagram tracing a system call from Win32 API through ntdll stub, SYSCALL instruction, IA32_LSTAR MSR, KiSystemCall64, SSDT lookup, and finally the kernel service routine.
Every user-mode kernel request follows this exact dispatch chain — EAX carries the syscall number across the Ring 3 to Ring 0 boundary.

5. The SSDT — Routing Calls Inside the Kernel

The System Service Descriptor Table (SSDT) is the array of function pointers that turns EAX into a kernel routine address.

SymbolDescription
KeServiceDescriptorTableExported; primary SSDT for Nt* executive system calls
KeServiceDescriptorTableShadowNot exported; adds the Win32k.sys GUI calls used by threads with a Win32 subsystem context
ServiceTableField inside each descriptor — pointer to an array of encoded function offsets (on x64 these are relative offsets right-shifted by 4)
NumberOfServicesCount of valid entries

Patching SSDT entries to redirect kernel calls was the classic 32-bit rootkit technique (and the canonical kernel hook for early HIPS products). On x64, PatchGuard (KPP) periodically verifies the SSDT and several other critical structures; modification triggers Bug Check 0x109CRITICAL_STRUCTURE_CORRUPTION.


6. Key Kernel Structures at the Boundary

The kernel maintains per-CPU and per-thread state that defenders inspect to understand mode transitions.

// Conceptual layout — verify offsets against your build's symbols.
typedef struct _KPCR {
    // ...
    struct _KPRCB Prcb;        // at +0x180 on x64; embedded
} KPCR, *PKPCR;

typedef struct _KPRCB {
    // ...
    struct _KTHREAD *CurrentThread;   // GS:[0x188] in kernel mode
} KPRCB, *PKPRCB;

typedef struct _KTHREAD {
    // ...
    UCHAR           PreviousMode;     // 0 = KernelMode, 1 = UserMode
    PKTRAP_FRAME    TrapFrame;        // saved register state from SYSCALL
} KTHREAD, *PKTHREAD;

PreviousMode is one of the most consequential bytes in the system: kernel routines branch on it to decide whether to probe and capture caller-supplied pointers (user mode) or trust them directly (kernel mode). Bugs in that check have been the root cause of multiple Windows LPE CVEs.

Inspect any of these live in WinDbg on a kernel debug target:

0: kd> rdmsr 0xC0000082          ; IA32_LSTAR -> KiSystemCall64
0: kd> dg cs                     ; show CS selector + CPL
0: kd> dt nt!_KPCR @$pcr
0: kd> dt nt!_KTHREAD @$thread PreviousMode TrapFrame
0: kd> dt nt!_KTRAP_FRAME @$thread->TrapFrame
0: kd> dps KeServiceDescriptorTable L4

7. Hardening the Boundary

Microsoft has spent two decades hardening the user/kernel boundary in layers. Each mechanism closes a class of attacks against Ring 0.

MechanismWhat it enforces
PatchGuard (KPP)Periodic integrity checks on SSDT, IDT, GDT, KPCR, MSRs, and kernel code sections. Tampering triggers Bug Check 0x109.
Driver Signature Enforcement (DSE)All kernel drivers must be signed. Enforced by ci.dll. Disabling DSE (bcdedit /set testsigning on) is a strong adversary indicator.
Secure BootUEFI-rooted trust chain prevents unsigned bootloaders/drivers from loading before Windows starts.
HVCI (Memory Integrity)A VTL1 hypervisor enforces W^X on kernel pages — unsigned code cannot execute even from Ring 0.
KVA Shadow (KPTI)User page tables contain only minimal kernel mappings; full mapping is installed only while CPL = 0. Mitigates Meltdown-class speculative leaks.
Microsoft Vulnerable Driver BlocklistMaintained list of known-abused drivers; enforced by HVCI/CI.

Together these turn Ring 0 from “anything goes once you’re in” into a far more constrained environment — and explain why modern attackers gravitate toward Bring Your Own Vulnerable Driver (BYOVD) as their cleanest path to kernel code execution.


Hierarchy diagram showing five Windows hardening mechanisms — HVCI, PatchGuard, DSE, KVA Shadow, and the Vulnerable Driver Blocklist — each targeting the Ring 0 attack surface.
Microsoft’s layered kernel hardening forces modern attackers toward BYOVD as the remaining practical path to Ring 0 code execution.

8. Common Attacker Techniques

The boundary is a target precisely because Ring 0 sits underneath every defensive product. Attackers care about three categories of abuse:

TechniqueDescription
Direct / indirect syscallsRebuild the ntdll stub (mov r10, rcx; mov eax, <N>; syscall) inside the implant to bypass user-mode hooks placed by EDR.
BYOVDLoad a legitimately signed but vulnerable driver, then exploit it to gain arbitrary Ring 0 read/write — used to disable EDR, blank tokens, or clear callbacks.
Kernel exploitation (LPE)Exploit a kernel vulnerability (write-what-where, type confusion, double-fetch on user pointers when PreviousMode == UserMode) to escalate Ring 3 → Ring 0.
SSDT hooking (legacy)Patch entries in KeServiceDescriptorTable to intercept syscalls — blocked on x64 by PatchGuard but still relevant for 32-bit forensics.
DKOM (Direct Kernel Object Manipulation)Unlink _EPROCESS entries from ActiveProcessLinks to hide processes; clear PsActiveProcessHead linkages.
Callback removalWalk PsSetCreateProcessNotifyRoutine / PsSetLoadImageNotifyRoutine arrays and null EDR callbacks.
PreviousMode overwriteSet KTHREAD.PreviousMode = KernelMode (0) to make subsequent Nt* calls skip user-pointer validation.

9. Defensive Strategies & Detection

The fact that all roads cross the boundary is a defender’s leverage: even attackers using direct syscalls leave telemetry at driver load, privilege use, and kernel object access layers.

Sysmon coverage

Event IDNameRelevance
1Process CreateParent/child + command line; catches bcdedit, sc.exe create … type= kernel
6Driver LoadedFires on every kernel driver load; fields include ImageLoaded, Hashes, Signed, Signature — primary BYOVD signal
7Image LoadedDLL loads; detect ntdll.dll loaded from non-standard paths
10Process AccessCross-process handle opens with sensitive GrantedAccess masks (precursor to injection)
255Sysmon ErrorTampering with the Sysmon kernel driver may surface here

Windows audit policies

PolicyEvent IDsDetects
Audit Sensitive Privilege Use4673Use of SeLoadDriverPrivilege — required to load any kernel driver
Audit Security System Extension4697, 7045New service / kernel driver installed
Audit Kernel Object4656, 4663Access to kernel objects via SACL-tagged handles
Audit Policy Change4719Audit-policy tampering (a common pre-attack step)

High-value ETW providers

  • Microsoft-Windows-Kernel-Process — process/thread/image events at the kernel boundary.
  • Microsoft-Windows-Kernel-File / Microsoft-Windows-Kernel-Registry — kernel-side file and registry ops, useful for catching driver-stage persistence.
  • Microsoft-Windows-Threat-Intelligence (ETWTI) — emits high-fidelity events for ReadProcessMemory, WriteProcessMemory, MapViewOfSection, QueueUserApc. Consumption requires a PPL or kernel consumer; verify provider availability on your build with logman query providers.

Sigma — BYOVD pattern

title: Suspicious Kernel Driver Load - BYOVD Pattern
logsource:
  product: windows
  category: driver_load
detection:
  selection:
    EventID: 6
    Signed: 'false'
  filter_legit_path:
    ImageLoaded|startswith:
      - 'C:\Windows\System32\drivers\'
      - 'C:\Windows\SysWOW64\drivers\'
  condition: selection and not filter_legit_path
fields:
  - ImageLoaded
  - Hashes
  - Signature
  - SignatureStatus
level: high

Sigma — SeLoadDriverPrivilege exercised by non-system principal

title: SeLoadDriverPrivilege Use by Non-System Account
logsource:
  product: windows
  service: security
detection:
  selection:
    EventID: 4673
    PrivilegeList|contains: 'SeLoadDriverPrivilege'
  filter_machine_accounts:
    SubjectUserName|endswith: '$'
  condition: selection and not filter_machine_accounts
level: medium

Hardening checklist

  • Enable HVCI / Memory Integrity (HKLM\SYSTEM\CurrentControlSet\Control\DeviceGuard).
  • Enable Secure Boot in UEFI.
  • Apply the Microsoft Vulnerable Driver Blocklist (HVCI-enforced).
  • Verify Meltdown mitigations / KVA Shadow via Get-SpeculationControlSettings.
  • Alert on bcdedit /set testsigning on and on driver loads where Signed=false or hashes match loldrivers.io.
  • Enable Kernel DMA Protection for laptops with Thunderbolt/USB4.
  • Limit SeLoadDriverPrivilege assignment and monitor every use via Event 4673.

10. Tools for Boundary Analysis

ToolDescriptionLink
WinDbgKernel debugger; inspect _KPCR, _KTHREAD, _KTRAP_FRAME, MSRs, SSDTaka.ms/windbg
SysmonProcess/driver/handle telemetry — EIDs 1/6/7/10sysinternals.com
Process HackerView loaded drivers, handles, tokens, KPP-safe inspectionprocesshacker.sourceforge.io
Process MonitorFile/registry/thread activity at the boundarysysinternals.com
Volatility 3Memory forensics; walk _EPROCESS, hidden processes via DKOMvolatilityfoundation.org
DriverView / DriverQueryEnumerate loaded kernel drivers and signing statenirsoft.net
ETW / logmanEnumerate and capture kernel-mode ETW providersbuilt-in
loldrivers.ioCatalog of known-vulnerable signed driversloldrivers.io

11. MITRE ATT&CK Mapping

TechniqueMITRE IDDetection
RootkitT1014Volatility scans for unlinked _EPROCESS; PatchGuard bug checks 0x109
Process InjectionT1055Sysmon EID 8/10; ETWTI WriteProcessMemory / QueueUserApc
Exploitation for Privilege EscalationT1068Bug check telemetry, unusual PreviousMode transitions, EDR kernel callbacks
Create or Modify System Process: ServiceT1543.003Security EID 4697, System EID 7045, Sysmon EID 6
Impair DefensesT1562.001Driver loads correlated with subsequent loss of EDR telemetry; EID 4673 with SeLoadDriverPrivilege
Exploitation for Defense Evasion (BYOVD)T1211Sysmon EID 6 with unsigned driver or known-vulnerable hash; loldrivers.io match

Summary

  • The user/kernel boundary is enforced by silicon — CPL in CS — not by software, which is what makes it trustworthy.
  • Windows uses only Ring 0 and Ring 3; user mode runs in a per-process private VA, kernel mode runs in a single shared VA where any bug is a BSOD.
  • Every user→kernel transition flows through SYSCALLIA32_LSTARKiSystemCall64 → SSDT dispatch, leaving EAX and KTHREAD.PreviousMode as the canonical fingerprints.
  • Modern hardening — PatchGuard, DSE, HVCI, KVA Shadow, and the vulnerable driver blocklist — has pushed attackers toward BYOVD and direct syscalls.
  • Defenders watch the boundary through Sysmon EID 6, Security EID 4673 (SeLoadDriverPrivilege), ETWTI, and kernel-callback EDR telemetry — every Ring 0 attack eventually touches one of them.

Related Tutorials

References