eXtended Flow Guard (XFG)

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:xfg and 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 -8 bytes 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):

  1. 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*)).
  2. Pre-emit the hash. If you have an AW primitive (arbitrary-read-write-primitives), write the expected hash at target-8 for an attacker-controlled code page — but XFG was designed assuming HVCI marks code pages immutable, so this is gated by an HVCI bypass.
  3. Non-XFG modules. Third-party code without /guard:xfg exposes indirect calls without the check; their function pointers become call gadgets.
  4. 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.
  5. 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:xfg where 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

See also: control-flow-guard, cfg-cet-kernel, cet-shadow-stack.