Control Flow Guard (CFG)
TL;DR: CFG instruments every indirect call site to check that the target address sits in a compiler-emitted bitmap of legal call targets; corrupted vtables and function pointers now
__fastfailinstead of executing arbitrary gadgets.
What it is
Control Flow Guard, shipped in Visual Studio 2015 / Windows 8.1, is Microsoft’s forward-edge Control Flow Integrity. The linker emits an array of valid indirect-call targets into the PE’s load config; the loader allocates a sparse bitmap covering the whole user address space and marks bits for every valid target across loaded modules. The compiler wraps every indirect call with call __guard_check_icall_fptr, which fails fast (int 29h) if the target’s bit is clear.
Preconditions / where it applies
- PE built with
/guard:cf(linker/GUARD:CF); CFG is per-image opt-in - Windows 8.1+ loader populates the bitmap; ntdll exports
LdrSystemDllInitBlock - Only protects forward edges (indirect call / jmp via register/memory) — backward edges need CET shadow stack
Technique
Bypasses focus on staying inside CFG-legal targets, or disabling CFG entirely:
- Call to non-instrumented module. A DLL loaded into the process without
/guard:cfkeeps its targets bit-set permissively; any function in it is a legal call. - CFG-legal gadget functions. Lots of Windows API entry points are valid targets. Researchers have catalogued “CFG-bypass” gadgets — e.g.
VirtualProtect,RtlGuardLoadConfig, andKiUserCallbackDispatcherpaths whose argument shape can be coerced. - Overwrite the function pointer to a CFG-legal trampoline that pivots. Combine with a stack pivot to reach a ROP chain that runs after the check.
- Read-write the CFG bitmap directly. Memory-corruption with arbitrary write can flip bits in the bitmap; on x64 the bitmap base is leaked via
LdrSystemDllInitBlock.CfgBitMap. - Disable CFG via
SetProcessValidCallTargets. Requires existing code execution but trivially neuters CFG for new targets. - Long jumps / SEH /
longjmp. Historically not covered until xLFG/RFG; modern_setjmpis CFG-instrumented but legacy code may still expose it.
Mitigation that supersedes plain CFG: XFG (see xfg) hashes the function prototype so any-target-in-bitmap reuse no longer works.
Detection and defence
- Compile with
/guard:cfand/CETCOMPAT; enable XFG (/guard:xfg) where toolchain allows - Set the process mitigation
ProcessControlFlowGuardPolicy.StrictMode = 1to refuse loading non-CFG modules - Pair with CET shadow stack (cet-shadow-stack) for backward edges and ACG to block JIT-generated targets
- Audit indirect-call gadgets with WinCheckSec or
dumpbin /loadconfig; look forGuardCFFunctionTablesize
References
- Microsoft: Control Flow Guard — reference and APIs
- Trail of Bits: CFG bypasses — bitmap structure and weaknesses
- Connor McGarr: CFG internals — practical bypass walkthroughs
Related: cet-shadow-stack, xfg, rop-chains, dep-bypass