User-Mode Mitigations
Last updated: 2026-04-10
Related: Mitigations, Rop, Heap Internals
Tags:user-mode,aslr,dep,cfg,cet,acg
Summary
User-mode mitigations in Windows have evolved from basic ASLR/DEP (Win7 era) into a sophisticated multi-layer system including CFG, CET hardware shadow stacks, ACG, and Code Integrity Guard. Each mitigation closes a specific exploit technique; understanding the entire stack is required to plan exploitation chains against hardened processes.
DEP / NX (Data Execution Prevention)
Introduced: Windows XP SP2 (software emulation), hardware enforcement with NX-capable CPUs
Mechanism: Pages marked NX (Execute Disable bit in page table entry) cannot be executed
Enforcement: All stack and heap pages are non-executable by default
Bypass: ROP (Return-Oriented Programming)
- Chain
ret-ending gadgets from executable memory to perform arbitrary computation - See Rop for full details
Bypass: JIT Spraying
- JIT-compiled code is executable → spray JIT output with gadget-like sequences
- Largely mitigated by ACG (see below)
Current Status
- DEP is effectively universal in 64-bit Windows; no meaningful bypass without ROP or code injection + ACG bypass
ASLR (Address Space Layout Randomization)
Introduced: Windows Vista
Mechanism: Randomizes base addresses of: executable (if /DYNAMICBASE), heap, stack, and system DLLs
Entropy
- Stack: 9 bits (x64 Vista/7), 17 bits (Win10+)
- Heap: 5 bits (low), improved in Segment Heap era
- EXE/DLL: 8 bits on 32-bit, up to 17-19 bits on 64-bit
- System DLLs: re-randomized at each boot (not per-process — shared)
Bypass: Info Leak
Most exploitation chains require defeating ASLR via a separate information disclosure vulnerability:
- Heap pointer leak: any corrupted pointer read back from heap
- Format string: controlled printf-style read of stack/heap
- Type confusion read: read unintended field that contains a pointer
- Partial overwrite: on 32-bit, overwrite low bytes only → known offset from leaked base
Bypass: Heap Spray (32-bit only)
- Fill address space with NOP sled + shellcode → predictable landing address
- 64-bit address space too large for practical spray to cover unknown base
Force ASLR
Process mitigation policy ForceRelocateImages: forces ASLR even for non-/DYNAMICBASE images. Breaks some old DLLs.
ASLR Entropy (Practical)
Technique Bits Notes
Heap spray (32-bit) ~8 bits Brute force in <256 tries
Partial overwrite ~4 bits Overwrite 1-2 bytes, known segment offset
Info leak 0 bits Exact base address — preferred approach
Stack Cookies (/GS)
Introduced: Windows (MSVC /GS flag, default since VS2003)
Mechanism: Compiler inserts random cookie between local variables and return address. Verified on function return.
Bypass
- Overwrite cookie + return address with correct cookie value (requires leak)
- Use SEH overwrite (before SafeSEH)
- Jump over cookie check via exception
- Use non-stack targets (function pointers, vtables, heap metadata)
SafeSEH
Mechanism: Module-level table of valid exception handler addresses. SEH dispatch validates handler is in table.
Bypass: Use handler in non-SafeSEH module, or use SEH as secondary after gaining control another way.
SEHOP (Structured Exception Handler Overwrite Protection)
Introduced: Windows Vista SP1
Mechanism: Validates SEH chain integrity (final handler must be ntdll!FinalExceptionHandler) before dispatch
Bypass: If you can control the entire SEH chain, not just one entry; or use 64-bit exception handling (no SEH chains in x64 — uses .pdata table instead)
CFG (Control Flow Guard)
Introduced: Windows 8.1 / Windows 10
Mechanism: Compiler + OS enforce that indirect calls go to valid targets per CFG bitmap
Per-Process CFG Policy
- Set via
SetProcessMitigationPolicy(ProcessControlFlowGuardPolicy) NtSetInformationProcess(ProcessEnableReadWriteVmLogging)for CFG export suppression
CFG Bitmap
- Located in each process at fixed virtual address derived from module layout
- 1 bit per 8-byte-aligned address → valid indirect call target
- Checked by
ntdll!LdrpValidateUserCallTarget(inlined by compiler)
CFG Bypasses
| Technique | Still Viable | Notes |
|---|---|---|
| Write to CFG bitmap | Requires AAW before first check | Must corrupt bitmap entry for target |
| Use valid-but-exploitable CFG target | Yes | “CFG-bypass gadgets” — valid targets with useful semantics; e.g., dns!NsecDnsRecordConvert (CVE-2020-1350): one-param, calls Dns_StringCopy(param->pDnsString) → arbitrary read primitive; msvcrt!system as RCE callback |
| Call through non-CFG module | Depends | Many system DLLs still lack CFG |
| SetProcessValidCallTargets | Yes (with privileges) | Legitimate API, abused by JIT engines |
| Use exception-based dispatch | Depends | Exceptions bypass some CFG enforcement |
Overwrite __guard_check_icall_fptr | If writable | Global function pointer for CFG check |
XFG (eXtended Flow Guard)
Introduced: Windows 10 20H1 (preview), broader deployment in Windows 11
Mechanism: XFG extends CFG by adding a type hash check to every indirect call. Each call site has a hash of the expected function signature. Each valid target has a hash stored in the 8 bytes preceding the function entry point. LdrpDispatchUserCallTarget validates both CFG bitmap membership AND type-hash match.
XFG Check Flow
indirect call → _guard_xfg_dispatch_icall_fptr → LdrpDispatchUserCallTarget
1. CFG bitmap check: target is a valid function entry point
2. Type hash check: *(target - 8) == expected_hash_at_call_site
3. PASS → call target
4. FAIL → INT 29 (fast fail, kills process)
XFG vs CFG
| Property | CFG | XFG |
|---|---|---|
| Checks address validity | Yes | Yes |
| Checks function signature type | No | Yes |
| Kernel enforcement | kCFG | — |
| Deployed in | ntoskrnl, most user DLLs | Select binaries (Win11+) |
| Bypass: ROP gadgets | Blocked | Blocked |
| Bypass: valid-but-wrong-type targets | Viable | Blocked |
XFG Bypasses
Approach 1 — Use a valid-same-type target: Find a function whose signature type hash matches the expected hash at the call site AND has exploitable semantics. This is harder than CFG bypass because it requires type-hash compatibility.
Approach 2 — Multi-step chain through benign XFG-compliant functions (CVE-2024-26230 / k0shl technique): When the attacker can invoke a controlled vtable dispatch multiple times, a chain of legitimate API calls can be built up piece by piece to achieve code execution:
- Call
MIDL_user_allocate→ leaks return address (low 32 bits via output buffer) - Call
VirtualAlloc→ allocates RWX memory at predictable 32-bit address- Address prediction trick: with allocation size = 0x40000000, low 32 bits of returned heap addresses increase linearly → predictable
flAllocationType(arg3) is a pointer; exploit the integer overflow: choose offset so(ptr + offset) mod 2^32 = 0x3000(MEM_COMMITMEM_RESERVE)
- Call
memcpy_srepeatedly (3 bytes at a time, limited by constant arg2=3) to write DLL path into RWX buffer - Call
LoadLibraryW→ load attacker DLL
Approach 3 — Type hash corruption: If AAW available, overwrite the type hash stored at target - 8 to match the call site hash. Requires knowing the expected hash value.
Approach 4 — Overwrite _guard_xfg_dispatch_icall_fptr: If writable, replace the XFG dispatch pointer with a no-op → degrades XFG to CFG. Difficult with ASLR.
XFG in Practice
- lsass.exe, browsers, and high-security processes use XFG on Windows 11
- tapisrv.dll (CVE-2024-26230) uses XFG: dispatch via
_guard_xfg_dispatch_icall_fptr → LdrpDispatchUserCallTarget - lsass.exe (CVE-2023-28229 / CNG Key Isolation): uses XFG — exploit bypasses by calling LoadLibraryW directly as a valid type-matching target
ACG (Arbitrary Code Guard)
Introduced: Windows 10 RS1
Mechanism: Prevents creation of new executable pages and prevents making existing pages executable via VirtualProtect. Also prevents mapping of executable pages.
Enforced via: NtSetInformationProcess(ProcessDynamicCodePolicy)
What ACG Prevents
VirtualAlloc(PAGE_EXECUTE_*)on new memoryVirtualProtectto add execute permissions- Map of executable section with write permissions
- JIT: must use explicit JIT policy exemption
ACG Bypasses
- Code reuse: ROP/JOP entirely — ACG doesn’t prevent executing already-mapped executable code
- JIT remote injection (pre-RS2): spawn remote JIT server, inject code there, transfer execution
- Kernel exploit: ACG is user-mode only; bypass via kernel → load/execute arbitrary code
Code Integrity Guard (CIG)
Introduced: Windows 10 RS1
Mechanism: Only Microsoft-signed DLLs can be loaded into the process
Enforced via: ProcessSignaturePolicy mitigation
Bypass: Exploit a signed DLL already loaded (doesn’t prevent exploiting existing code)
CET (Control-flow Enforcement Technology)
Introduced: Intel Tiger Lake; Windows 10 20H1/Win11
Components: Shadow Stack (SS) + Indirect Branch Tracking (IBT)
Shadow Stack Details
- Separate stack for return addresses (ring 3 shadow stack at
THREAD_INFORMATION_BLOCK.ShadowStack) - Shadow stack pages: PTE bit 63 set (shadow stack page), not writable by normal stores
WRSSinstruction needed to write shadow stack — only available in ring 0SAVEPREVSSP,RSTORSSPforsetjmp/longjmpsupport
IBT Details
- After
call [reg]orjmp [reg], CPU enters “wait for ENDBR” state - Valid indirect call target must begin with
ENDBR64 - Any other instruction →
#CP(Control Protection fault)
CET Bypass Research
- Stack pivot + ENDBR: must pivot to
ENDBR64-starting gadget chains longjmpcorruption:_jmp_bufstores shadow stack pointer — corrupt to redirect shadow stack- Exception handler paths:
_except_handler4manages shadow stack; audit for gaps - Kernel CET bypass: kernel shadow stack is separate; exploiting kernel CET requires ring 0
Child Process / Sandbox Mitigations
Win32k Syscall Filter
- Process can restrict Win32k syscalls to prevent exploitation of win32k from sandboxed context
SetProcessMitigationPolicy(ProcessSystemCallDisablePolicy, {DisallowWin32kSystemCalls:1})- Chromium, Firefox, Edge, Acrobat all enable this in their renderers
Restrict Child Process Creation
ProcessChildProcessPolicy— prevents spawning child processes (used in browser sandboxes)
Extension Point Disable
- Prevents loading of known code injection extension points (DLL hijacking via COM, AppInit, etc.)
ProcessExtensionPointDisablePolicy
Job Object Restrictions
JOB_OBJECT_LIMIT_ACTIVE_PROCESS→ limits process spawning- Used by browser sandboxes to prevent sandbox escape via process spawning
Mitigation Query (Current Process)
PROCESS_MITIGATION_DEP_POLICY dep = {0};
GetProcessMitigationPolicy(GetCurrentProcess(), ProcessDEPPolicy, &dep, sizeof(dep));
// Check dep.Enable, dep.ATLThunkShadowStack, dep.Permanent
Use processhacker or SysinternalsProcessExplorer to inspect all mitigations for a target process.
Exploit Relevance
- Modern hardened targets (browser renderers): CFG + ACG + CIG + Win32k filter + child process restrict + CET
- This combination effectively requires: info leak + ROP (no shellcode) + CFG bypass + sandbox escape via kernel
- Understand which mitigations each target enables before designing exploit chain
References
- “Exploit Mitigations in Windows 10” — David Weston, Matt Miller (Microsoft BlueHat 2014)
- “Windows 10 Mitigations Improvements” — various MSRC blog posts
- “CET Shadow Stack” — Yuki Chen, Trend Micro
- “ACG Bypass” — Yuki Chen, Trend Micro (2017)
- “CFG Deep Dive” — Connor McGarr
