eXtended Flow Guard (XFG)
TL;DR: XFG extends CFG by stamping every indirect-callable function with a hash of its prototype and checking that hash on every indirect call — only call targets with a matching signature are accepted, shrinking the gadget set far below CFG.
What it is
Where CFG validates only that an indirect-call target is “some valid function” in the per-process bitmap, XFG validates that the target’s type signature matches the call site. The compiler emits a hash before each XFG-protected function (the eight bytes preceding the function entry) and, at each XFG call site, generates a check that loads target - 8, compares to the expected hash, and jumps to the call only on match — otherwise terminates the process via __fastfail(FAST_FAIL_CONTROL_FLOW_GUARD).
Preconditions / where it applies
- Microsoft Visual C++ 2019+ with
/guard:xfgand the linker pulling in XFG-aware import thunks. - Process binaries opt in; not enabled on all third-party software.
- Targets where attacker has a vtable/function-pointer overwrite primitive and wants to reach an arbitrary function.
Technique
Call-site shape (x64).
1
2
3
mov rax, [rcx + vtable_offset] ; load target
mov r10, hash_for_signature ; expected hash
call __guard_xfg_dispatch_icall ; verifies [rax-8] == r10, then jmp rax
What changes for the attacker.
- Calling an arbitrary function pointer is no longer enough; the spoofed target’s
-8bytes must equal the expected hash for the call site. - The set of XFG-valid targets for a given call site is far smaller than the CFG bitmap — often single digits per signature.
- Useful “any-function” gadgets (
LoadLibraryA,VirtualProtect) are not callable from arbitrary sites because hashes don’t match.
Bypass strategies (all situational):
- Hash collision / matching-signature target. Find an XFG-valid function with a signature equal to the one the call site expects but that performs an attacker-useful action. Rare but possible for common shapes (
void(void),int(void*)). - Pre-emit the hash. If you have an AW primitive (arbitrary-read-write-primitives), write the expected hash at
target-8for an attacker-controlled code page — but XFG was designed assuming HVCI marks code pages immutable, so this is gated by an HVCI bypass. - Non-XFG modules. Third-party code without
/guard:xfgexposes indirect calls without the check; their function pointers become call gadgets. - Skip the check via call to non-call-site. Jumping directly to the instruction after the check, if you can land there with a
ret/rop-chains gadget — gated by CET shadow stack. - Data-only. Skip indirect calls entirely; corrupt state instead.
Status. XFG shipped in Windows 11 and Server 2022 for selected binaries; widespread roll-out has been slow because compatibility issues forced Microsoft to throttle deployment. Treat it as present-but-spotty in 2025 estates.
Detection and defence
- Build with
/guard:xfgwhere supported and combine with CET (/CETCOMPAT) and HVCI. - Monitor
FAST_FAIL_CONTROL_FLOW_GUARD(subcode 0x29 / 41) terminations — direct evidence of a thwarted XFG check. - Audit dependencies for non-XFG/non-CFG opt-outs.
References
- Microsoft: XFG announcement — design intent
- Connor McGarr — XFG analysis — instruction-level breakdown
- David Weston — Windows mitigations — BlueHat talks summarising deployment
See also: control-flow-guard, cfg-cet-kernel, cet-shadow-stack.