The Kernel Trust Boundary
Kernel drivers have authority beyond a game process; driver inventories and narrow privileges make that boundary visible.
Cheatsheet for Lesson 12.9
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 behindReadProcessMemoryis 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
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.
Ring 3 and ring 0 in plain English
Section titled “Ring 3 and ring 0 in plain English”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.”
1 Three user-mode processes with separate memory
User mode: separately bounded memory
Process 1process 1 memoryProcess 2process 2 memoryProcess 3process 3 memoryKernel and drivers
AuthoritymappingsdevicesprocessessecurityEach process can only touch its own bounded memory.
2 A user-mode fault usually ends one process
User mode: separately bounded memory
Process 1process 1 memoryProcess 2process 2: faultProcess 3process 3 memoryKernel and drivers
AuthoritymappingsdevicesprocessessecurityThe failing process stops; the others and the kernel carry on.
3 A kernel fault can reach the whole system
User mode: separately bounded memory
Process 1process 1: faultProcess 2process 2: faultProcess 3process 3: faultKernel and drivers
AuthoritymappingsdevicesprocessessecurityKernel 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.
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.
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.
Why the course labs stay in user mode
Section titled “Why the course labs stay 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.
Why Windows insists on signed drivers
Section titled “Why Windows insists on signed drivers”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:
Do not “test” a suspicious driver by loading it on your daily computer. Use an isolated disposable virtual machine and vendor-provided analysis guidance.
Why a cheat driver endangers its own user
Section titled “Why a cheat driver endangers its own user”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.
Build a read-only driver inventory
Section titled “Build a read-only driver inventory”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:
Complete defensive lab source: driver_inventory.rs
driver_inventory.rs
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:
cargo run --release --target i686-pc-windows-msvc --bin driver_inventoryThe 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.
Turn an inventory into a baseline
Section titled “Turn an inventory into a baseline”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.
Windows protections worth keeping on
Section titled “Windows protections worth keeping on”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:
- keep Windows and driver updates current;
- leave Secure Boot and memory integrity enabled when the hardware supports them;
- use the Microsoft vulnerable-driver blocklist or an approved application-control policy;
- remove abandoned hardware utilities and drivers you no longer need;
- download drivers through Windows Update or the hardware vendor;
- 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.
Detect driver changes
Section titled “Detect driver changes”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.
If you genuinely need to develop a driver
Section titled “If you genuinely need to develop a driver”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.
Respect privilege boundaries
Why do ordinary game-analysis labs stay in user mode?
Kernel code shares system-wide privilege and failure impact. User mode provides a safer boundary for learning, testing, and recovery.
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.
Plain Markdown, saved in this browser.
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.