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

TechniqueStill ViableNotes
Write to CFG bitmapRequires AAW before first checkMust corrupt bitmap entry for target
Use valid-but-exploitable CFG targetYes“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 moduleDependsMany system DLLs still lack CFG
SetProcessValidCallTargetsYes (with privileges)Legitimate API, abused by JIT engines
Use exception-based dispatchDependsExceptions bypass some CFG enforcement
Overwrite __guard_check_icall_fptrIf writableGlobal 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

PropertyCFGXFG
Checks address validityYesYes
Checks function signature typeNoYes
Kernel enforcementkCFG
Deployed inntoskrnl, most user DLLsSelect binaries (Win11+)
Bypass: ROP gadgetsBlockedBlocked
Bypass: valid-but-wrong-type targetsViableBlocked

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:

  1. Call MIDL_user_allocate → leaks return address (low 32 bits via output buffer)
  2. 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)
  3. Call memcpy_s repeatedly (3 bytes at a time, limited by constant arg2=3) to write DLL path into RWX buffer
  4. 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 memory
  • VirtualProtect to 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
  • WRSS instruction needed to write shadow stack — only available in ring 0
  • SAVEPREVSSP, RSTORSSP for setjmp/longjmp support

IBT Details

  • After call [reg] or jmp [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
  • longjmp corruption: _jmp_buf stores shadow stack pointer — corrupt to redirect shadow stack
  • Exception handler paths: _except_handler4 manages 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