September 3, 2026
PatchGuard 101
Welcome to this new Medium post. In this one, we’ll take an introduction to PatchGuard protection. I’m just starting my research into this…

By S12 - 0x12Dark Development
4 min read
Welcome to this new Medium post. In this one, we'll take an introduction to PatchGuard protection. I'm just starting my research into this protection, with two main goals: to enumerate all (or as many as possible) of the kernel modules and structures monitored by PatchGuard
If you've spent any time in offensive Windows development (rootkits, EDR evasion, kernel research) you've run into it eventually: you hook something you shouldn't, wait a couple of minutes, and your VM dies with a blue screen reading CRITICAL_STRUCTURE_CORRUPTION. That's PatchGuard
This post is a gentle on ramp. It won't turn you into a PatchGuard internals expert (that takes actual reverse engineering, not a blog post), but it'll get you oriented enough to understand why it behaves the way it does
PatchGuard
PatchGuard (officially Kernel Patch Protection (KPP)) its job is simple to state and annoying to live with: it periodically verifies that a set of critical kernel structures and code regions haven't been modified, and if it finds a change it didn't expect, it kills the system
Microsoft's own justification, more or less, is that kernel patching is inherently unsupportable, third party code splicing into kernel structures leads to instability, and old rootkits made a habit of hooking things like the System Service Descriptor Table (SSDT) to hide processes, files, and network activity. PatchGuard was Microsoft's answer: if you can't stop people from writing kernel drivers, you can at least make it prohibitively expensive for those drivers to persist by hooking anything PatchGuard cares about
From an attacker's perspective this cuts both ways. It means classic SSDT/IDT hooking techniques are effectively dead on modern Windows unless you deal with PatchGuard first. It also means that legitimate AV/EDR vendors had to abandon undocumented hooking techniques decades ago in favor of documented, Microsoft mechanisms like minifilters, ObRegisterCallbacks, kernel callbacks, etc.
Core
Strip away all the obfuscation and the idea is genuinely simple:
- At boot time, before any third party driver has loaded, PatchGuard computes checksums over a list of structures and code regions it considers sensitive
- Periodically, it recomputes those checksums and compares them against the originals
- If anything doesn't match, it triggers a bugcheck with code
0x109(CRITICAL_STRUCTURE_CORRUPTION), and your box goes down
Microsoft actually documents the corruption-type parameter for bugcheck 0x109, it's a useful map of categories of things PatchGuard watches, even though it tells you nothing about the underlying structures themselves
PatchGuard contexts
Definition: PatchGuard contexts are the combination of a method, being how checks are initialized and triggered, and the structure holding the data used by PatchGuard to perform the checks
Structure in context: Let's start with the structure of this context. There are three main sections:
- Core of PatchGuard: This one contains ntoskrnl API pointers, global variables, system-related variables such as Ntoskrnl and Hal base addresses, runtime variables, and a series of flags
- Data storage: This section stores the original data for later use
- Information about the data to check: To keep track of what structure needs to be checked, PatchGuard uses an array of structures that holds the necessary information for each check
For this last one, we got the following structure:
struct pg_crit_struct_check_data
{
ULONG64 KeBugCheckType_0x0; // 0x2 for IDT, 0x3 for GDT, etc.
ULONG64 pData_0x8;
ULONG32 szData_0x10;
ULONG32 hash_0x14;
ULONG64 specific[3];
};struct pg_crit_struct_check_data
{
ULONG64 KeBugCheckType_0x0; // 0x2 for IDT, 0x3 for GDT, etc.
ULONG64 pData_0x8;
ULONG32 szData_0x10;
ULONG32 hash_0x14;
ULONG64 specific[3];
};The KeBugCheckType is the category, the ones seen in the table above. Then we got the pointer to the data and the size of the data to be checked, and the hash is the checksum computed during the initialization, which will be used as the reference when PatchGuard checks the structure's integrity
PatchGuard context: Initialization
There are 6 different ways used to initialize each PatchGuard context:
- Inserting a timer, linked with a DPC
- Hidden DPC
- System Thread
- Asynchronous Procedure Call
- Hook of a regular DPC
- The new weird one (Yes, it's named like that in the PDF)
Why it's hard to find, not hard to understand
The mechanism above is conceptually trivial. What makes PatchGuard research painful is everything wrapped around it, and this is the part worth actually internalizing, because it explains the shape of basically every bypass write up you'll ever read:
It hides its own initialization. PatchGuard doesn't just call an init function from DriverEntry. It gets bootstrapped through deliberately triggered CPU exceptions, a crafted divide error, for instance, where the exception handler is really a stub into the real init routine. This isn't a bug; it's obfuscation. Anyone setting a breakpoint on the "obvious" function never sees it initialize
It runs on random schedules, via random mechanisms. Rather than one predictable timer, PatchGuard has historically used a whole menu of scheduling primitives: DPCs stashed in obscure PRCB fields, dedicated system threads, APC insertion into unrelated existing threads, hooking a legitimate periodic DPC and hijacking it every N queues, and so on
It encrypts its own context in memory. The structure PatchGuard uses to track what to check and what the correct checksums are doesn't sit around in plaintext. It's XOR encrypted with per boot random keys, decrypted right before a check runs, and re encrypted immediately after. So even if you find it, you're looking at ciphertext most of the time
It anti-debugs itself. Checks for KdDebuggerNotPresent/KdPitchDebugger gate whether PatchGuard even initializes, and there are multiple points where a debugger being attached quietly disables checks rather than crashing
It protects its own protection. Before it triggers the actual bugcheck, PatchGuard restores page table entries and rewrites the critical routines it depends on (KeBugCheckEx, KeBugCheck2, etc.) from a clean copy it saved at init time.
None of these tricks make the core idea (checksum things, compare, crash on mismatch) any harder to understand. What they do is make PatchGuard genuinely hard to locate and disable, because there's no single context, no single timer, and no single code path to intercept
Further reading
If this got you curious about the actual reverse engineering weeds, how the context structure is laid out, the exact decryption layers, how each scheduling method is triggered and torn down: Tetrane's "Updated Analysis of PatchGuard on Microsoft Windows 10 RS4" by Luc Reginato
https://blog.tetrane.com/downloads/Tetrane_PatchGuard_Analysis_RS4_v1.01.pdf
📌 Follow me: 🐦 X | 💬 Discord Server | 📸 Instagram | Newsletter | YouTube
S12.