Skip to content

Windows Control Flow Guard

Her indirect call'u önceden hesaplanmış geçerli function-entry target kümesine kısıtlayan compiler + OS forward-edge control-flow integrity'si; böylece corrupt olmuş bir function pointer yalnızca yasal bir entry point'e ulaşabilir ya da process'i terminate eder.

Mechanism

Neden çalışır

Klasik bir exploit bir function pointer'ını (ya da C++ vtable entry'sini) corrupt eder ki bir indirect call/jmp attacker-chosen koda düşsün. Control Flow Guard (CFG) bunu forward edge üzerinde bir invariant zorlayarak kırar: bir indirect call yalnızca, build system'in zaten yasal bir indirect-call target olduğunu kanıtladığı bir adrese transfer olabilir.

  • Build zamanında, "kodundaki tüm indirect call'lar, kod doğru çalıştığında ulaşabileceği her konumu bulmak için analiz edilir. Bu bilgi binary'lerinin header'larındaki ekstra structure'larda saklanır." Linker bunları Guard CF Function Table'da (the "FID table"), geçerli target entry point'lerinin bir RVA list'inde toplar ve PE'yi CF Instrumented / FID table present olarak işaretler.
  • Compiler, "her indirect call'dan önce ... target'ın doğrulanmış konumlardan biri olduğundan emin olan bir check inject eder." Check, __guard_check_icall_fptr üzerinden çağırır. CFG'den habersiz bir binary'de bu bir no-op stub'a işaret eder (backward compatibility'yi korur); CFG-aware bir OS'ta loader onu ntdll!LdrpValidateUserCallTarget'a yeniden yazar.
  • Validator, kernel tarafından bakımı yapılan ve "geçerli indirect call target'larını tanımlayan state'i verimli biçimde tutan" bir bitmap'e başvurur. Her bit, address space'in align edilmiş bir dilimine karşılık gelir; bit'i clear olan bir target reddedilir. VirtualAlloc/VirtualProtect, executable committed page'leri default olarak geçerli işaretler ve SetProcessValidCallTargets bir JIT'in target'ları dinamik yönetmesine izin verir.
  • "Runtime'da bir CFG check başarısız olduğunda, Windows programı derhal terminate eder" — başarısız bir LdrpValidateUserCallTarget bir fast-fail (STATUS_STACK_BUFFER_OVERRUN, 0xC0000409) yükseltir.

Invariant: bir indirect call'un hedefi, saldırganın genişletemeyeceği, build-time, OS-protected bir function entry point whitelist'inden çekilir.

Walkthrough

1. CFG ile build et. Compiler'a /guard:cf, linker'a /GUARD:CF geç; /DYNAMICBASE (ASLR) de gereklidir.

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

/guard:cf "compiler'ın compile zamanında indirect call target'ları için control flow'u analiz etmesine ve target'ları doğrulamak için runtime'da kod eklemesine neden olur. Default olarak /guard:cf kapalıdır ve açıkça enable edilmelidir."

2. Binary'nin opt-in olduğunu dumpbin ile doğrula:

dumpbin /headers /loadconfig test.exe
Expected indicators

OPTIONAL HEADER VALUES
    ...
    DLL characteristics:
        Guard

Guard Flags
    CF Instrumented
    FID table present
"CFG-enabled binary'lerin EXE ya da DLL characteristics list'inde Guard bulunur ve Guard Flags CF Instrumented ile FID table present'ı içerir."

3. Eklenen check neye benzer. Her indirect call'dan önce compiler bir __guard_check_icall_fptr load'u emit eder ve onu target rcx'te olacak şekilde çağırır; OS routine'i o target için CFG bitmap'ini indeksler ve bit clear ise fast-fail eder.

Coverage gap'leri whitelist'i zayıflatır

"CFG'yi tüm kod için enable edememek, korumada gap'ler açabilir." /guard:cf ile build edilmemiş bir modül checked olmayan indirect call'lar yapar ve PAGE_TARGETS_INVALID/PAGE_TARGETS_NO_UPDATE olarak oluşturulan JIT region'ları target'larını kendileri register etmeli ya da erişilemez hâle gelmelidir.

Detection

dumpbin /loadconfig, Windows Defender Exploit Guard / process-mitigation policy sorguları (ProcessControlFlowGuardPolicy) ve BinSkim, bir CFG process'ine yüklenen non-CFG modülleri flag'leyebilir. Bir CFG ihlali bir 0xC0000409 fast-fail crash olarak ortaya çıkar; bu, WER/Defender Exploit Protection event'lerinde FAIL_FAST_GUARD_ICALL_CHECK_FAILURE olarak da yüzeye çıkar. EDR açısından sinyal, untrusted-input işlerken CFG-korumalı process'lerde erken crash'ler ya da korunan process'lere karışık yüklenen non-CFG module'lerdir.

Mitigation

(Residual risk / nasıl bypass edilir.) CFG yalnızca bir forward-edge mitigation'dır: return address'leri korumaz, dolayısıyla stack üzerindeki saklanmış bir return address'i overwrite eden klasik ROP tamamen etkilenmez — o, hardware-enforced stack protection'ın (Intel CET shadow stack'leri) işidir. Forward edge'de bile whitelist kabadır: FID table'daki herhangi bir function entry point'i yasal bir target'tır, dolayısıyla bir saldırgan bir call'u farklı ama geçerli, hassas bir fonksiyona yönlendirebilir. Diğer belgelenmiş zayıflıklar arasında bazı layout'larda yine de bitmap-valid olan bir target üzerinden bir fonksiyonun ortasına çağrı yapmak, suppressed/exported call target'larını suistimal etmek ve non-CFG modülleri yer alır. Type-aware halefi eXtended Flow Guard target kümesini sıkılaştırır ve kernel CFG modeli ntoskrnl'e genişletir.

Kaynak yeniden derleme mümkün değilse

/guard:cf ile yeniden derleme mümkün olmadığında, CFG opt-in olmayan ama uyumlu binary'ler için Exploit Protection (process-mitigation policy) üzerinden zorlanabilir; korunan process'lere non-CFG module load etmekten yine de kaçınılmalıdır.

References