Skip to content

Windows eXtended Flow Guard

Control Flow Guard'ın type-aware bir hardening'i: her indirect call, prototype hash'i compiler'ın call site'ta kaydettiği hash ile eşleşen bir fonksiyona ulaşmalıdır; "herhangi bir geçerli entry point" target kümesini "doğru tipteki entry point'ler"e daraltır.

Mechanism

Neden çalışır

Kaba Control Flow Guard, function table'daki herhangi bir adresi yasal bir indirect-call target olarak kabul eder, dolayısıyla bir saldırgan bir call'u hâlâ farklı — ama bitmap-valid — bir fonksiyona yönlendirebilir. eXtended Flow Guard (XFG) bunu, her indirect call'u hedeflenen callee'sinin type signature'ına bağlayarak sıkılaştırır.

  • "XFG, compile zamanında, bir control flow transfer'da çağrılacak bir fonksiyonun bir 'type-based hash'ini üretir." Hash, fonksiyonun yapısal prototype'ından türetilir — return type, parameter sayısı, per-parameter type hash'leri, variadic flag ve calling convention; Quarkslab'ın analizine göre o input'ların bir SHA-256'sının ilk 8 byte'ıdır, sabit bit mask'leri uygulanmış (ve low bit reserve edilmiş) hâlde, yaygın olarak alıntılanan ~55-bit effective hash'i verir. Fonksiyon isimleri hash'in parçası değildir — yalnızca type shape'tir.
  • Hash, read-only bellekte fonksiyondan hemen önce, "target fonksiyonun 8 byte üzerinde" saklanır. Microsoft onu "immutable" olarak ele alır — "read only state'i nedeniyle bir saldırgan tarafından değiştirilemez."
  • Call site'ta beklenen hash bir register'a (r10) materyalize edilir ve XFG dispatch routine'i "RAX'in (target fonksiyonumuz) 8 byte üzerindeki hash'i R10'da korunan hash ile karşılaştırır." Mismatch'te process terminate edilir; match'te control transfer olur.
  • XFG, /guard:xfg toolchain flag'i ile enable edilir (yalnızca command-line; yeni VS 2019+ ve Windows 10 21H1+ gerektirir). "XFG enabled olduğunda, CFG hâlâ enabled'dır" — XFG, type check'i mevcut bitmap validation'ının üzerine katmanlar.

Invariant: bir indirect call yalnızca, prototype hash'i call site'ta sabitlenen hash'e eşit olan bir fonksiyona ulaşabilir, dolayısıyla type-uyumsuz target'lar — CFG-yasal kümenin büyük kısmı — artık erişilebilir değildir.

Walkthrough

1. XFG ile build et. XFG, Visual Studio GUI'de sunulmaz; flag'i command line'da compiler ve linker'a geç:

cl /guard:xfg test.cpp /link /guard:xfg

2. Kaydedilen hash, her korumalı fonksiyondan önce oturur. Disassembly, function entry'sinin hemen önünde gelen, call site'ta beklenen hash olarak yüklenen 8-byte'lık bir değer gösterir:

; expected callee hash placed 8 bytes before the function body
   <hash:8 bytes>
target_func:
   ...

3. Dispatch check. Indirect call'dan önce, compiler beklenen hash'i r10'a yükler, ardından XFG dispatch routine'i onu gerçek target'ın önündeki 8 byte'a karşı karşılaştırır:

mov     r10, <expected_prototype_hash>   ; expected callee type hash
; XFG dispatch: cmp r10, [rax-8]  -> mismatch => terminate
call    <xfg_dispatch>                   ; rax = target function
Hash inşası (Quarkslab)

MSVC back end'i prototype description'ının truncated SHA-256'sını alır ve sabit mask'ler uygular — hash &= 0xFFFDBFFF7EDFFB70; hash |= 0x8000060010500070 — XFG hash'lerinin neden non-random göründüğünün ve effective entropy'nin neden tam 64 bit'in altında olduğunun nedeni budur.

XFG, CFG'nin bir superset'idir, bir replacement değil

XFG, CFG'nin bitmap check'ini aktif tutar. İkisi de whole-program coverage'a bağlıdır; non-XFG modüllerdeki indirect call'lar kaba CFG'ye (ya da hiçbir check'e) düşer.

Detection

dumpbin/PE tooling'inde pre-function hash byte'larının artı /guard:xfg load-config flag'lerinin varlığı XFG binary'lerini ayırt eder; bir XFG ihlali, CFG gibi, process'i fast-fail eder.

Mitigation

(Residual risk / nasıl bypass edilir.) XFG yalnızca forward edge'i sıkılaştırır; backward-edge koruması sağlamaz, dolayısıyla return-address ROP etkilenmez ve hardware-enforced stack protection ile kapsanmalıdır. Forward edge'de hash, cryptographic per-instance bir sır değildir: sabit, ~55-bit, type-derived bir değerdir, dolayısıyla bir prototype'ı paylaşan farklı fonksiyonlar bir hash paylaşır ve birbirinin yerine geçebilir kalır. Belgelenmiş teorik saldırı, önündeki byte'ları gerekli hash ile zaten eşleşen bir opcode dizisini (ya da mevcut geçerli bir çift üzerinden erişilebilir bir gadget'ı) bulmak, sonra call'u oraya yöneltmek için bir read/write primitive kullanmaktır — zor ama dışlanmış değil. XFG, CFG üzerine inşa edildiği için, her CFG coverage gap'i (non-instrumented modüller, JIT region'ları) miras alınır.

References