- name
- windows-kernel-security
- description
- Guide for Windows kernel internals and security mechanisms used in game protection and low-level research. Use this skill when working with drivers, IRQL-sensitive callbacks, EPROCESS, ETHREAD, MMVAD internals, IOCTL paths, DSE, PatchGuard, HVCI, PiDDBCache, MmUnloadedDrivers, or kernel memory inspection.
# Windows Kernel Security
## Overview
This skill covers Windows kernel internals that matter for game security research: object callbacks, process and image notifications, APC behavior, driver loading, trust enforcement, memory manager structures, and the bookkeeping anti-cheats inspect to detect hostile drivers or hidden executable code.
Treat undocumented structures, offsets, globals, and allocator internals as
build-specific. Verify them against symbols and runtime observations for the
exact Windows build; use [`research-rigor`](../research-rigor/SKILL.md) before
generalizing a PoC or forensic heuristic.
## README Coverage
- `Cheat > PatchGuard-related`
- `Cheat > Driver Signature enforcement`
- `Cheat > Windows Kernel Explorer`
- `Cheat > EFI Driver` (cross-reference with game-hacking skill)
- `Cheat > Vulnerable Driver`
- `Anti Cheat > Detection:Attach`
- `Anti Cheat > Detection:Hide`
- `Anti Cheat > Detection:Vulnerable Driver`
- `Anti Cheat > Detection:Spoof Stack`
- `Anti Cheat > Windows Ring3 Callback`
- `Anti Cheat > Windows Ring0 Callback`
- `Anti Cheat > Information System & Forensics`
- `Some Tricks > Windows Ring0`
- `Windows Security Features`
## Core Kernel Concepts
### Important Structures
- EPROCESS / ETHREAD
- KTHREAD / KAPC / KAPC_STATE
- MMVAD / VAD tree nodes
- PEB / TEB
- DRIVER_OBJECT
- DEVICE_OBJECT
- IRP (I/O Request Packet)
### Key Tables
- SSDT (System Service Descriptor Table)
- IDT (Interrupt Descriptor Table)
- GDT (Global Descriptor Table)
- PspCidTable (Process/Thread handle table)
- PiDDBCacheTable / MmUnloadedDrivers / PoolBigPageTable
## User-Mode Kernel Symbol Walking
### Methodology
```
- Load local ntoskrnl image (typically C:\Windows\System32\ntoskrnl.exe)
- Use dbghelp + symbol server path (srv*cache*https://msdl.microsoft.com/download/symbols)
to resolve exported symbol RVAs and type information
- Build structure-aware field lookup:
- Query field offset directly (e.g., _EPROCESS.Token)
- Enumerate all members of a target struct (_TOKEN, _EPROCESS, etc.)
- Search a field name across all known structs (useful when parent type is unknown)
- Keep symbol path configurable for offline/private symbol repositories
```
### Why It Matters in Game Security
```
- Reduces hardcoded-offset fragility across Windows builds
- Helps map kernel object layouts used by anti-cheat and drivers
- Supports rapid adaptation when anti-cheat-relevant fields shift
(EPROCESS, ETHREAD, token/handle/security-related members)
```
### Gadget Scanning Workflow
```
- Map executable sections of ntoskrnl image in user mode
- Scan for short control-flow gadgets (e.g., pop rcx ; ret, jmp rax)
- Use as a research primitive for:
- ROP chain feasibility analysis
- Kernel exploit mitigation evaluation
- Anti-cheat hardening review against gadget-dependent attack paths
```
## Security Features
### PatchGuard (Kernel Patch Protection)
```
- Protects critical kernel structures
- Periodic verification checks
- BSOD on tampering detection
- Multiple trigger mechanisms
```
### Driver Signature Enforcement (DSE)
```
- Requires signed drivers
- CI.dll verification
- Test signing mode
- WHQL certification
```
### Virtualization-Based Security (VBS)
```
Architecture:
- Uses the Windows hypervisor to create an isolated execution environment
- Splits the system into Virtual Trust Levels (VTLs)
- VTL0: Normal world — standard Windows kernel and user-mode processes
- VTL1: Secure world — Secure Kernel, security policy enforcement
- VTL1 is designed to remain isolated from a compromised VTL0, assuming the
hypervisor, secure kernel, hardware, and configuration path remain trustworthy
- Three main buckets:
- Memory-protection features (HVCI)
- Virtual Trust Levels (VTL0/VTL1 separation)
- VBS enclaves (isolated execution for selected workloads)
```
### Hypervisor-Enforced Code Integrity (HVCI)
```
- Also known as Memory Integrity
- Ensures only trusted, validated code executes in kernel mode
- Combines Windows hypervisor + Secure Kernel (VTL1) for enforcement
- Key mechanism: W→X transition restriction
- Enforced code pages are not intended to be writable from VTL0
- Executability is granted only after the configured code-integrity checks
- Enforcement pipeline:
- Code integrity policy defines what is trusted
- Hypervisor memory enforcement via second-stage address translation (EPT/SLAT)
- Once a kernel page is validated, strict execution rules are enforced
- Driver compatibility requirements: drivers must be HVCI-compatible
```
### Secure Boot
```
- UEFI-based boot verification
- Boot loader chain validation
- Kernel signature checks
- DBX (forbidden signatures)
- Foundation for attestation and DMA-hardening assumptions
```
## Kernel Callbacks
### Process Callbacks
```cpp
PsSetCreateProcessNotifyRoutine
PsSetCreateProcessNotifyRoutineEx
PsSetCreateProcessNotifyRoutineEx2
```
### Thread Callbacks
```cpp
PsSetCreateThreadNotifyRoutine
PsSetCreateThreadNotifyRoutineEx
```
### Image Load Callbacks
```cpp
PsSetLoadImageNotifyRoutine
PsSetLoadImageNotifyRoutineEx
```
### Object Callbacks
```cpp
ObRegisterCallbacks
// OB_OPERATION_HANDLE_CREATE
// OB_OPERATION_HANDLE_DUPLICATE
```
### APC / Execution Context
```cpp
KeInitializeApc
KeInsertQueueApc
KeStackAttachProcess
RtlWalkFrameChain
```
### Registry Callbacks
```cpp
CmRegisterCallback
CmRegisterCallbackEx
```
### Minifilter Callbacks
```cpp
FltRegisterFilter
// IRP_MJ_CREATE, IRP_MJ_READ, etc.
```
## Driver Development
### Basic Structure
```cpp
NTSTATUS DriverEntry(
PDRIVER_OBJECT DriverObject,
PUNICODE_STRING RegistryPath
) {
DriverObject->DriverUnload = DriverUnload;
DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreate;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoctl;
// Create device, symbolic link...
return STATUS_SUCCESS;
}
```
### Communication Methods
- IOCTL (DeviceIoControl)
- Direct I/O
- Buffered I/O
- Shared memory
## Vulnerable Driver Exploitation
### Common Vulnerability Types
- Arbitrary read/write primitives
- IOCTL handler vulnerabilities
- Pool overflow
- Use-after-free
### Notable Vulnerable Drivers
```
- gdrv.sys (Gigabyte)
- iqvw64e.sys (Intel)
- MsIo64.sys
- Mhyprot2.sys (Genshin Impact)
- dbutil_2_3.sys (Dell)
- RTCore64.sys (MSI)
- Capcom.sys
```
### Exploitation Steps
1. Load vulnerable signed driver
2. Trigger vulnerability
3. Achieve kernel read/write
4. Disable DSE or load unsigned driver
5. Execute arbitrary kernel code
## PatchGuard Bypass Techniques
### Timing-Based
- Predict PG timer
- Modify between checks
### Context Manipulation
- Exception handling
- DPC manipulation
- Thread context tampering
### Hypervisor-Based
- EPT manipulation
- Memory virtualization
- Intercept PG checks
## Kernel Hooking
### ETW (Event Tracing for Windows)
```
- InfinityHook technique
- HalPrivateDispatchTable
- System call tracing
```
## ETW Internals
### Provider / Consumer Model
```
Architecture:
- Providers: kernel or user-mode components that emit events
- Manifest-based providers (registered via wevtutil)
- TraceLogging providers (self-describing, no manifest)
- MOF providers (legacy WMI-based)
- Consumers: tools that subscribe to and process events
- Real-time consumers (ETW sessions)
- Log file consumers (.etl files)
- Controllers: manage sessions (xperf, tracelog, logman)
Key kernel providers:
Microsoft-Windows-Kernel-Process (process/thread lifecycle)
Microsoft-Windows-Kernel-File (file I/O)
Microsoft-Windows-Kernel-Audit-API-Calls (security-sensitive APIs)
```
### ThreatIntel ETW Provider
```
- Microsoft-Windows-Threat-Intelligence
- Available to PPL (Protected Process Light) and above
- Events: NtReadVirtualMemory, NtWriteVirtualMemory, NtMapViewOfSection on protected processes
- Used by EDR and anti-cheat for detecting memory access to protected processes
- Attackers target: patch EtwThreatIntProvRegHandle or EtwpEventWriteFull
```
### Common ETW Bypass Patterns
```
- Patch EtwEventWrite in ntdll.dll (user-mode ETW silencing)
- Patch nt!EtwpEventWriteFull in kernel (kernel-mode ETW silencing)
- NtSetInformationThread(ThreadHideFromDebugger) — hides thread from ETW
- Remove provider registration by walking EtwRegistration list
- EPT-based protection can defend ETW structures from tampering
```
## Kernel Segment Heap Architecture
### Timeline
```
Windows NT ~ 1809 : Legacy NT Pool Manager (ExAllocatePoolWithTag)
Windows 10 19H1 : Kernel Segment Heap introduced (March 2019, build 1903)
└─ User-mode Segment Heap ported to the kernel
Windows 10 2004 : ExAllocatePool2 / ExAllocatePool3 added
└─ ExAllocatePoolWithTag officially deprecated
Windows 10 20H2~ : Dynamic KDP (Kernel Data Protection) stabilized
Windows 11 : VBS/HVCI enabled by default; Secure Pool usage expanded
Common misconception: Many sources claim "the Segment Heap was introduced
in Windows 10 2004," but the kernel segment heap was actually introduced
in 19H1 (1903). Windows 10 2004 added the new Pool APIs built on top of it.
```
### Legacy NT Pool Structure (_POOL_HEADER, pre-19H1)
```
_POOL_HEADER (16 bytes, x64):
Offset Field Size Description
0x00 PoolIndex 1 B Pool descriptor index
0x01 PreviousSize 1 B Previous chunk size
0x02 PoolType 1 B Pool type (Paged, NonPaged, etc.)
0x03 BlockSize 1 B Current chunk size (>> 4)
0x04 PoolTag 4 B 4-byte ASCII tag
0x08 ProcessBilled 8 B KPROCESS pointer (valid only with PoolQuota)
Memory layout:
[POOL_HEADER 16B][user data ...][POOL_HEADER 16B][user data ...]
↑ plaintext, predictable ↑ adjacent → overwritable
Security weaknesses:
- Pool Walking: traverse chunks linearly via BlockSize
- Pool Overflow: corrupt adjacent header for arbitrary write on free
- PoolIndex Overwrite: OOB dereference into pool descriptor array
- ProcessBilled Overwrite: arbitrary address dereference on free path
Windows 8 partial mitigation:
ProcessBilled = KPROCESS_PTR ^ ExpPoolQuotaCookie ^ CHUNK_ADDR
But plaintext _POOL_HEADER remained until 19H1.
```
### _SEGMENT_HEAP Core Structure
```
Each pool type is managed by its own independent _SEGMENT_HEAP instance.
_SEGMENT_HEAP (illustrative kernel offsets observed for 20H2; verify symbols):
0x000 EnvHandle (10 B) — heap environment handle
0x010 Signature (4 B) — commonly 0xDDEEDDEE on this layout
0x028 UserContext (8 B)
0x048 AllocatedBase (8 B) — LFH structure allocation base
0x058 SegContexts[2] (0x180 B) — segment context array
0x100 VsContext (0xC0 B) — VS allocator context
0x280 LfhContext (0x4C0 B) — LFH allocator context
higher LargeAllocMetadata — large allocation metadata
higher LargeReservedPages / LargeCommittedPages
Per pool type instances (nt!PoolVector / HEAP_POOL_NODES):
├── NonPagedPool (NP) → _SEGMENT_HEAP instance #1
├── NonPagedPoolNx (NPNx) → _SEGMENT_HEAP instance #2 ← primary target
├── PagedPool (PP) → _SEGMENT_HEAP instance #3
├── PagedPoolSession → _SEGMENT_HEAP stored in current thread
└── (other special pools)
```
### Allocation Routing Flow
```
ExAllocatePoolWithTag / ExAllocatePool2 / ExAllocatePool3
│
▼
ExAllocateHeapPool (internal)
│
├─ size ≤ 0x200 AND LFH activated ──▶ kLFH
│ └─ RtlpHpLfhContextAllocate
│
├─ 0x1e1 ≤ size ≤ 0xfe0 ──▶ VS Allocator
│ └─ RtlpHpVsContextAllocateInternal
│
├─ page-aligned (0x20000~0x7f0000) ──▶ Segment Allocator
│ └─ RtlpHpSegAlloc
│
└─ large (> 0x7f0000) ──▶ Large Allocator
└─ RtlpHpLargeAlloc
```
### kLFH (Low Fragmentation Heap)
```
Size range: ≤ 0x200 bytes (512 B), when LFH activated for that size class
Activation: After 18 consecutive allocations of the same size
Key function: RtlpHpLfhContextAllocate
Chunk header: _POOL_HEADER (16 B, still present)
Metadata: _HEAP_LFH_SUBSEGMENT (isolated, not inline)
Bucket count: 129 (Buckets[129])
Bucket structure:
_HEAP_LFH_CONTEXT
└── Buckets[129]
├── Bucket #0: size 1~8 B
├── Bucket #1: size 9~16 B
├── ...
└── Bucket #128: size ~0x1FF B
(each bucket has AffinitySlots → _HEAP_LFH_SUBSEGMENT)
Security properties:
- Block placement within subsegment is randomized
- Next allocation position managed through FreeHint, encoded with LfhKey
- Adjacent chunk overflow cannot directly corrupt management structure
```
### VS Allocator (Variable Size)
```
Size range: (a) ≤ 0x1e0 && LFH inactive; (b) 0x1e1~0xfe0;
(c) 0x1001~0xffff && non-page-aligned
Key function: RtlpHpVsContextAllocateInternal
Chunk header: _HEAP_VS_CHUNK_HEADER (16 B, HeapKey XOR encoded)
Free management: Red-Black Tree (FreeChunkTree)
Algorithm: Best-fit
_HEAP_VS_CHUNK_HEADER (allocated state):
┌──────────────────────────────────────────────────────────┐
│ Sizes (8 B) — XOR encoded: HeaderBits ^ self_addr ^ HeapKey
│ ├─ UnsafeSize : chunk size / 16
│ ├─ UnsafePrevSize : previous chunk size / 16
│ ├─ MemoryCost : pages occupied
│ └─ UnusedBytes : whether unused bytes exist
│ EncodedSegmentPageOffset (1 B)
│ — (self_addr ^ self ^ HeapKey) & 0xFF
│ — page distance to VS subsegment start
└──────────────────────────────────────────────────────────┘
Memory layout:
[_HEAP_VS_CHUNK_HEADER 16B][_POOL_HEADER 16B][user data ...]
↑ HeapKey XOR ↑ PoolTag etc. still present
VS subsegment structure (_HEAP_VS_SUBSEGMENT):
├── ListEntry — subsegment linked list
├── CommitBitmap — page commit state bitmap
├── CommitLock — lock used during commit
├── Size (2 B) — subsegment size (>> 4)
└── Signature (15 bit) + FullCommit (1 bit) — integrity check
```
### Segment Allocator (Backend)
```
Size range #1: 0x20000 < size ≤ 0x7f000 (128 KB ~ 508 KB)
Size range #2: 0x7f000 < size ≤ 0x7f0000 (508 KB ~ ~7 MB)
Core structure: _HEAP_PAGE_SEGMENT + 256 page descriptors
Segment mask: 0xFFFFFFFFFFF00000
The kernel uses two independent SegContexts (unlike user-mode's single context).
Page segment signature encoding:
check = page_segment ^ page_segment->Signature
^ 0xA2E64EADA2E64EAD ^ RtlpHpHeapGlobals.HeapKey
```
### Large Allocator
```
Size range: > 0x7f0000 (typically page-aligned)
Key function: RtlpHpLargeAlloc
Metadata: _SEGMENT_HEAP.LargeAllocMetadata
Tracking: BigPagePoolTable (PoolTrackTable)
No inline header; metadata recorded externally.
```
### Header Layout Per Allocation Path
```
Path Memory layout (chunk start → user data)
────────────────────────────────────────────────────────────────
kLFH [_POOL_HEADER 16B] [data]
VS [_HEAP_VS_CHUNK_HEADER 16B] [_POOL_HEADER 16B] [data]
Segment [_HEAP_PAGE_SEGMENT header] ... [page descriptors]
Large Metadata in BigPagePoolTable; no inline header
CacheAligned [_POOL_HEADER #1] ... [_POOL_HEADER #2 (CacheAligned)] [data]
```
### Residual _POOL_HEADER Under Segment Heap
```
_POOL_HEADER was not fully removed. Remaining usage:
Field Status under Segment Heap
PoolTag Still recorded (for debugging/tracing)
PoolType Recorded, not used for allocator selection on free
BlockSize Unused in VS path; still present in kLFH
PreviousSize Unused, set to 0
PoolIndex Unused, set to 0
ProcessBilled Valid only with PoolQuota flag (encoded with ExpPoolQuotaCookie)
```
### Pointer Encoding Mechanisms
```
Global key structure: _RTLP_HP_HEAP_GLOBALS (nt!RtlpHpHeapGlobals)
Generated randomly at boot time; global in ntoskrnl.
{
UINT64 HeapKey; // VS Allocator + Segment Allocator header encoding
UINT64 LfhKey; // LFH callback pointer encoding
}
Encoding formulas:
VS chunk header — Sizes field:
encoded = (real Sizes) ^ (address of vs_chunk_header) ^ HeapKey
VS chunk — EncodedSegmentPageOffset:
encoded = ((real page distance) ^ vs_chunk_header ^ HeapKey) & 0xFF
Segment context signature:
check = page_segment ^ page_segment->Signature
^ 0xA2E64EADA2E64EAD ^ HeapKey
LFH callback function pointer:
encoded = real function address ^ HeapKey ^ address of LfhContext
ProcessBilled (POOL_HEADER, Windows 8+):
encoded = KPROCESS_PTR ^ ExpPoolQuotaCookie ^ CHUNK_ADDR
Implications for attackers:
- Must leak HeapKey and LfhKey from RtlpHpHeapGlobals
- Must know chunk's own virtual address (self-referential XOR)
- Failing encoding validation triggers:
BugCheck 0x139 (KERNEL_SECURITY_CHECK_FAILURE) or
BugCheck 0x13A (KERNEL_MODE_HEAP_CORRUPTION)
```
### Dynamic Lookaside and Delay Free
```
Dynamic Lookaside:
_HEAP_VS_CONTEXT
└── Lookaside buckets (_RTL_DYNAMIC_LOOKASIDE)
├── Per-size singly-linked lists
├── Depth (2 B) — current list depth
└── NextEntry (8 B) — pointer to next cached chunk
Rebalancing (every 3 Balance Set Manager scans):
- alloc count < 25 → Depth decreases by 10
- miss ratio ≥ 0.5% → Depth increases
- miss ratio < 0.5% → Depth decreases by 1
- Range: minimum 4 ~ MaximumDepth
Delay Free (VS Allocator):
- size < 1 KB AND Config.Flags bit 4 == 1:
→ stored in DelayFreeContext list
→ batch freed after 32 entries accumulate
- Otherwise: inserted immediately into FreeChunkTree
- Security: disrupts UAF timing (cannot immediately reuse freed chunk)
```
### New Pool APIs: ExAllocatePool2 / ExAllocatePool3
```
Evolution:
ExAllocatePool (legacy, no tag)
ExAllocatePoolWithTag (pre-19H1 standard, deprecated in 2004)
ExAllocatePoolWithTagPriority (priority support)
ExAllocatePoolWithQuotaTag (quota tracking)
↓
ExAllocatePool2 (general case, zero-initialized by default)
ExAllocatePool3 (extended parameters, priority + Secure Pool)
ExAllocatePool2:
PVOID ExAllocatePool2(POOL_FLAGS Flags, SIZE_T NumberOfBytes, ULONG Tag);
- Zero-initialized by default (no RtlZeroMemory needed)
- Returns NULL on failure by default
- POOL_FLAG_RAISE_ON_FAILURE converts to exception
- POOL_FLAG_USE_QUOTA integrates legacy PoolQuota
ExAllocatePool3:
PVOID ExAllocatePool3(POOL_FLAGS Flags, SIZE_T NumberOfBytes, ULONG Tag,
View on GitHub