Control Flow Guard (CFG)

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 __fastfail instead 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:cf keeps 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, and KiUserCallbackDispatcher paths 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 _setjmp is 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:cf and /CETCOMPAT; enable XFG (/guard:xfg) where toolchain allows
  • Set the process mitigation ProcessControlFlowGuardPolicy.StrictMode = 1 to 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 for GuardCFFunctionTable size

References

Related: cet-shadow-stack, xfg, rop-chains, dep-bypass