Skip to content
Lesson 12.9Loaders, Kernel Services, and Trust Boundaries

The Kernel Trust Boundary

Kernel drivers have authority beyond a game process; driver inventories and narrow privileges make that boundary visible.

Lesson 12.9
Read or listenPrint chapter or book
About 22 minutes

Keyboard shortcuts

?
Open this guide
Alt + B
Show or hide the lesson list
Alt + O
Show or fold the lesson outline
Alt + N
Open or close notes
Alt + H
Toggle semantic colours
Alt + D
Toggle page brightness
Alt + C
Toggle code brightness
← →
Move through a focused code trace
Home End
First or final step in a focused trace
Escape
Close this guide or an open panel

Reader shortcuts pause while you type in a text field.

Cheatsheet for Lesson 12.9Key ideas, reference and questions

A one-page summary of “The Kernel Trust Boundary” you can keep beside your notes. Plain Markdown.

Lesson 12.9 — The Kernel Trust Boundary: cheatsheet

  • Remember: Kernel code helps enforce system-wide memory and privilege checks, so its failures can reach beyond one game process.
  • Also useful: An inventory is a photograph; a baseline adds labels and comparisons that explain which driver changed and where it came from.

Key terms

  • ring 3: A normal game and the tools in this book run in user mode, often called ring 3.
  • ring 0: A kernel driver runs in kernel mode, often called ring 0.
  • which invariants a component can break: The important model is which invariants a component can break.
  • ReadProcessMemory: Kernel code does catch faults it expects, such as touching a user buffer that another thread may have freed; the copy behind ReadProcessMemory is written to catch exactly those.
  • bug check: That deliberate stop is a bug check, better known as the blue screen.
  • Secure Boot: Secure Boot is the firmware check that allows only signed boot components to start, which also keeps these signature checks from being relaxed before Windows is running.
  • vulnerable driver blocklist: Microsoft's answer is the vulnerable driver blocklist: a list of signed drivers known to be exploitable, which Windows refuses to load.
  • HVCI: Memory integrity, also called HVCI, uses virtualization-based security to enforce kernel code-integrity rules.

Review

  • Q: Why do ordinary game-analysis labs stay in user mode? A: A user-mode mistake is contained more narrowly than a failure in a privileged driver
  • Q: Why is kernel-mode code held to a different standard of trust than ordinary application code? A: It helps enforce system checks, so a flaw can break them everywhere
Download cheatsheet (.md)

The DLL lessons followed identity, loading, and API resolution inside a process. The driver lessons then followed a request through the I/O manager, checked buffers, and device registers. We can now ask what authority those components actually carry. A game tool’s process handle grants limited access; a kernel driver can affect the whole machine. This lesson makes that trust boundary visible with a read-only inventory of the drivers Windows has loaded.

The Windows kernel is the part of the operating system trusted to manage memory, devices, processes, and security boundaries. A normal game and the tools in this book run in user mode, often called ring 3. A kernel driver runs in kernel mode, often called ring 0.

The important model is which invariants a component can break. User-mode code runs with page-table and privilege checks that confine ordinary faults to its allowed address space and handles. Kernel-mode code participates in enforcing those checks; it can change mappings, device state, process state, and security decisions for the whole system.

A user-mode crash normally ends one process. A bad kernel pointer can crash the entire computer or corrupt data belonging to any process. Moving code into a driver therefore enlarges both its authority and the damage caused by a bug. “More privilege” is not the same as “better engineering.”

How far a fault can reachA conceptual boundary picture, not a driver experiment: user-mode faults are usually contained, kernel faults are not.
  1. 1 Three user-mode processes with separate memory

    User mode: separately bounded memory

    Process 1
    process 1 memory
    Process 2
    process 2 memory
    Process 3
    process 3 memory

    Kernel and drivers

    Authority
    mappings
    devices
    processes
    security

    Each process can only touch its own bounded memory.

  2. 2 A user-mode fault usually ends one process

    User mode: separately bounded memory

    Process 1
    process 1 memory
    Process 2
    process 2: fault
    Process 3
    process 3 memory

    Kernel and drivers

    Authority
    mappings
    devices
    processes
    security

    The failing process stops; the others and the kernel carry on.

  3. 3 A kernel fault can reach the whole system

    User mode: separately bounded memory

    Process 1
    process 1: fault
    Process 2
    process 2: fault
    Process 3
    process 3: fault

    Kernel and drivers

    Authority
    mappings
    devices
    processes
    security

    Kernel code has authority over mappings, devices, processes and security, so a fault there is not confined to one process.

A conceptual boundary picture, not a driver experiment: user-mode faults are usually contained, kernel faults are not.

ordinary fault scope
kernel fault scope
Game and analysis tool
user mode / ring 3
Documented Windows API boundary
checks handles, rights, and buffers
Windows kernel
ring 0 and system-wide authority
Device driver
narrow device-specific contract
Hardware
Usually one process
Potentially the whole system

The boundary is useful because callers do not receive kernel authority merely for asking Windows to do something. The kernel checks the request, and a sound driver checks it again before touching device or privileged memory.

Why a kernel bug takes the whole machine down

Section titled “Why a kernel bug takes the whole machine down”

When user-mode code follows a bad pointer, the kernel finds that nothing valid lives at that address and turns the page fault into an access-violation exception for that one process. The program’s own handler, a debugger, or Windows Error Reporting deals with it, and in the worst case one process ends. The kernel is the safety net under every user-mode program.

There is no separate part of Windows that can repair arbitrary damage to its own kernel state. A hypervisor may isolate a whole guest from the host, as Lesson 13.3 will show, but it cannot resume a guest kernel whose internal records are already corrupted. Kernel code does catch faults it expects, such as touching a user buffer that another thread may have freed; the copy behind ReadProcessMemory is written to catch exactly those. But a driver that follows a bad pointer by mistake can raise an unexpected fault inside the kernel, and kernel code and drivers share an address space and a set of data structures: the scheduler’s lists, the memory manager’s page records, and the locks that guard them. The fault may have struck halfway through updating one of those structures, with a lock still held. Windows cannot generally prove which of its own records remain sound, and carrying on could write corrupted data to disk. For an unrecoverable kernel failure, Windows stops on purpose.

ring 3
ring 0, unexpected
code follows a bad pointer
privilege level
of the faulting code
kernel handles the page fault
access violation for
that one process
the process handles it or ends,
everything else keeps running
no general in-OS repair
for damaged kernel state
shared kernel state
may be half updated
bug check: stop code,
crash dump, restart

That deliberate stop is a bug check, better known as the blue screen. Windows shows a stop code, such as 0x50 PAGE_FAULT_IN_NONPAGED_AREA or 0xD1 DRIVER_IRQL_NOT_LESS_OR_EQUAL, writes a crash dump of the whole machine if it can (Lesson 14.3 will capture a dump of one process), and restarts.

A bad pointer in a ring 3 program ends at most that one process. An unexpected bad pointer inside kernel code can stop the whole machine with a bug check, because kernel code and drivers share state. The interactive version needs JavaScript; the lesson text around it explains the same idea.

Inside a virtual machine, even a bug check stops only that virtual machine (Lesson 13.3), which is one more reason the book’s labs run inside one, in user mode.

The old open-source games in this book do not need a driver. Their offline processes can be studied with documented Windows APIs, a debugger, a user-mode DLL, and reversible patches.

The course deliberately does not build:

  • arbitrary kernel or physical-memory read/write commands;
  • privilege-escalation paths;
  • security-product or anti-cheat bypasses;
  • hidden drivers, callbacks, threads, or processes;
  • loaders for vulnerable or untrusted drivers.

Those capabilities would expand the problem from studying one game process into controlling the operating system and concealing that control.

Microsoft’s own driver security guidance begins with the same design question: confirm that a kernel driver is actually required. If a service or application can do the job, it is the lower-risk boundary.

Loading a driver puts new code into ring 0, so Windows checks where a driver came from before loading it. 64-bit Windows refuses kernel-mode code without a valid digital signature, the kind of signature Lesson 10.8 checked on game files. Since Windows 10 version 1607, on a PC with Secure Boot on, a new driver must be signed by Microsoft itself, through its hardware developer program, which requires the publisher to prove its identity with an extended-validation certificate. Secure Boot is the firmware check that allows only signed boot components to start, which also keeps these signature checks from being relaxed before Windows is running. Developers’ test-signing modes relax them on purpose, so they belong only on a disposable test machine, and this book never uses them.

A signature answers two questions: who published this driver, and has it changed since? It does not say whether the code is safe, as the next section shows. Attackers look for old, legitimately signed drivers with an exploitable bug and load one to borrow it. Microsoft’s answer is the vulnerable driver blocklist: a list of signed drivers known to be exploitable, which Windows refuses to load. Since the Windows 11 2022 update the blocklist is on by default, and it is always enforced when memory integrity is on.

The defensive problem: trusted code can still be vulnerable

Section titled “The defensive problem: trusted code can still be vulnerable”

A signed driver is not automatically a safe driver. If it exposes a badly protected command that can read or write arbitrary privileged memory, another program may abuse that power.

Defenders should treat driver loading as a rare, high-impact event:

new driver appears
identify publisher and signature
compare hash and version
with the approved baseline
check the vulnerable-driver
block policy
investigate the process
and installer that introduced it

Do not “test” a suspicious driver by loading it on your daily computer. Use an isolated disposable virtual machine and vendor-provided analysis guidance.

Some cheats for online games ship as drivers, on the theory that ring 0 sees everything. Whatever such a driver does to a game, here is what it does to the machine running it:

  • It runs with the kernel’s full authority and comes from an anonymous author. Its bugs become bug checks and corrupted files on the user’s own computer, with no vendor, testing, or update process behind it.
  • It often comes with instructions to switch protections off, such as Secure Boot, memory integrity, the vulnerable driver blocklist, or signature checks. Each of those protects the machine against all kernel malware, not only against cheats, and turning one off lowers the defense for everything.
  • Many rely on an old signed driver with a known bug to reach ring 0. The bug cannot tell who is asking: while that driver is loaded, any other program on the machine can send the same control code and read or write kernel memory.
  • A kernel driver is the most powerful place malware can live. Installing one from an anonymous seller hands that seller the whole computer: saved passwords, browser sessions, banking, and the power to hide further software.
  • Anti-cheat systems look for exactly these drivers, and the penalties usually apply to the whole account, sometimes to the hardware.
Windows VM

The inventory calls EnumDeviceDrivers and GetDeviceDriverBaseNameW through the windows crate. It queries visible driver images, prints their base names, and uses no mutation APIs.

enumerate does not know the driver count in advance, so it grows its buffer and asks again until Windows reports the whole list fit:

yes
no
drivers: Vec of 256 null pointers
EnumDeviceDrivers(drivers,
byte_capacity, out param bytes_needed)
did count already fit
in drivers.len()?
truncate to count, return
resize drivers to count,
call EnumDeviceDrivers again
Complete defensive lab source: driver_inventory.rs

driver_inventory.rs

windows-labs/src/bin/ · Rust, 59 lines

Show the codeHide the code
#[cfg(windows)]
fn main() -> anyhow::Result<()> {
    use std::{ffi::c_void, mem::size_of};

    use anyhow::Context;
    use windows::Win32::System::ProcessStatus::{EnumDeviceDrivers, GetDeviceDriverBaseNameW};

    fn enumerate() -> anyhow::Result<Vec<*mut c_void>> {
        let mut drivers = vec![std::ptr::null_mut(); 256];
        loop {
            let byte_capacity = u32::try_from(drivers.len() * size_of::<*mut c_void>())
                .context("driver buffer is too large")?;
            let mut bytes_needed = 0_u32;
            // SAFETY: drivers points to byte_capacity writable bytes and
            // bytes_needed is a valid output pointer for the duration of the call.
            unsafe {
                EnumDeviceDrivers(drivers.as_mut_ptr(), byte_capacity, &mut bytes_needed)?;
            }
            let count = usize::try_from(bytes_needed)?.div_ceil(size_of::<*mut c_void>());
            if count <= drivers.len() {
                drivers.truncate(count);
                return Ok(drivers);
            }
            anyhow::ensure!(count <= 16_384, "unreasonable driver count: {count}");
            drivers.resize(count, std::ptr::null_mut());
        }
    }

    let mut names = Vec::new();
    for image_base in enumerate()? {
        if image_base.is_null() {
            continue;
        }
        let mut buffer = [0_u16; 260];
        // SAFETY: image_base came from EnumDeviceDrivers and buffer is writable.
        let length = unsafe { GetDeviceDriverBaseNameW(image_base, &mut buffer) } as usize;
        if length == 0 || length > buffer.len() {
            continue;
        }
        names.push(String::from_utf16_lossy(&buffer[..length]));
    }

    names.sort_unstable_by_key(|name| name.to_ascii_lowercase());
    names.dedup_by(|left, right| left.eq_ignore_ascii_case(right));
    println!(
        "Loaded kernel drivers visible to this account: {}",
        names.len()
    );
    for name in names {
        println!("  {name}");
    }
    println!("No privileges were enabled and no driver state was changed.");
    Ok(())
}

#[cfg(not(windows))]
fn main() {
    eprintln!("This read-only driver inventory uses the Windows PSAPI.");
}
#[cfg(windows)]
fn main() -> anyhow::Result<()> {
use std::{ffi::c_void, mem::size_of};
use anyhow::Context;
use windows::Win32::System::ProcessStatus::{
EnumDeviceDrivers,
GetDeviceDriverBaseNameW,
};
fn enumerate() -> anyhow::Result<Vec<*mut c_void>> {
let mut drivers = vec![std::ptr::null_mut(); 256];
loop {
let byte_capacity = u32::try_from(drivers.len() * size_of::<*mut c_void>())
.context("driver buffer is too large")?;
let mut bytes_needed = 0_u32;
// SAFETY: the vector owns byte_capacity writable bytes and the
// output count lives until the Windows call returns.
unsafe {
EnumDeviceDrivers(
drivers.as_mut_ptr(),
byte_capacity,
&mut bytes_needed,
)?;
}
let count = usize::try_from(bytes_needed)?
.div_ceil(size_of::<*mut c_void>());
if count <= drivers.len() {
drivers.truncate(count);
return Ok(drivers);
}
anyhow::ensure!(count <= 16_384, "unreasonable driver count: {count}");
drivers.resize(count, std::ptr::null_mut());
}
}
let mut names = Vec::new();
for image_base in enumerate()? {
if image_base.is_null() {
continue;
}
let mut buffer = [0_u16; 260];
// SAFETY: the address came from Windows and the UTF-16 buffer is writable.
let length = unsafe {
GetDeviceDriverBaseNameW(image_base, &mut buffer)
} as usize;
if length == 0 || length > buffer.len() {
continue;
}
names.push(String::from_utf16_lossy(&buffer[..length]));
}
names.sort_unstable_by_key(|name| name.to_ascii_lowercase());
names.dedup_by(|left, right| left.eq_ignore_ascii_case(right));
println!("Loaded kernel drivers visible to this account: {}", names.len());
for name in names {
println!(" {name}");
}
println!("No privileges were enabled and no driver state was changed.");
Ok(())
}

Build and run it from a normal terminal:

Terminal window
cargo run --release --target i686-pc-windows-msvc --bin driver_inventory

The program does not enable debug privileges. On a hardened or newer Windows installation, the account may see fewer details. Treat that as the security boundary working; do not weaken the machine to make the list longer.

One list is only a snapshot. A useful defensive baseline also records:

  • Windows build and capture time;
  • driver file name, version, publisher, and signature result;
  • cryptographic hash;
  • why the driver is installed;
  • the approved update source.

Compare snapshots after installing a game, hardware utility, or anti-cheat component. A new driver is not automatically malicious, but it deserves a reason and a trustworthy origin.

A new driver is not automatically malicious, but it deserves a reason and a trustworthy origin: check publisher and signature, hash against the approved baseline, the block policy, and the installer that brought it. The interactive version needs JavaScript; the lesson text around it explains the same idea.

Memory integrity, also called HVCI, uses virtualization-based security to enforce kernel code-integrity rules. The Microsoft vulnerable-driver blocklist prevents known risky drivers from loading when the relevant Windows protections or application-control policy enforce it.

For a normal learning machine:

  1. keep Windows and driver updates current;
  2. leave Secure Boot and memory integrity enabled when the hardware supports them;
  3. use the Microsoft vulnerable-driver blocklist or an approved application-control policy;
  4. remove abandoned hardware utilities and drivers you no longer need;
  5. download drivers through Windows Update or the hardware vendor;
  6. do not turn off protections merely to run an experiment.

If an old driver conflicts with memory integrity, first look for a supported update or remove the dependent software. Compatibility trouble is not a reason to leave the protection disabled.

Sysmon’s Driver Load event, Event ID 6, can record a loaded driver’s hash and signature information. On a managed lab machine, alert on:

  • an unknown or unexpected publisher;
  • an invalid or missing signature;
  • a hash not present in the approved baseline;
  • a driver introduced outside the normal update process;
  • a driver name associated with a block policy or security advisory.

Detection is not “spot one weird filename and delete it.” Confirm the file, service, signature, source, and owning software before taking action.

Use Microsoft’s Windows Driver Frameworks, a disposable test VM, Driver Verifier, strict device access control, narrow IOCTLs, checked buffer lengths, least privilege, peer review, and the proper test-signing and release-signing workflows. Never expose arbitrary kernel, physical, or device memory to a user-mode caller.

The full read-only inventory is driver_inventory.rs.

References: Microsoft driver security checklist, recommended vulnerable-driver block rules, HVCI compatibility, and Sysmon Driver Load events.

Knowledge check

Respect privilege boundaries

Why do ordinary game-analysis labs stay in user mode?

Finished Lesson 12.9?

Mark it done and the lesson list shows a beside it. A chapter whose lessons are all done is marked done too. Progress is saved in this browser only.

Chapter 12: 10 lessons

This book grew out of the original Game Hacking Academy by attilathedud and has been rewritten as a beginner-first course. Like the original repository, its source is published under the Apache License 2.0.