StackDefiler (Losing Control CFI bypass)¶
Writable stack memory'de spill edilmiş, zaten doğrulanmış control-flow pointer'larını bozan bir CFI bypass'ı — check geçtikten sonra onu yenerek.
Mechanism¶
StackDefiler, Losing Control: On the Effectiveness of Control-Flow Integrity under Stack Attacks (Conti, Crane, Davi, Franz, Larsen, Liebchen, Negro, Qunaibit, Sadeghi; ACM CCS 2015) makalesinin merkezi tekniğidir. Forward-edge, fine-grained CFI implementasyonlarını hedefler — özellikle LLVM'deki Google IFCC (Indirect Function-Call Checks) ve GCC'deki VTV (Virtual-Table Verification).
Neden işe yarar
Bir CFI check'inin iki parçası vardır: (1) bir indirect target'ın izinli olduğunu compute/validate et, sonra (2) gerçekten ona branch et. Bu iki adım arasında, doğrulanmış pointer — ve bazı implementasyonlarda CFI runtime metadata'sı — bir yerde yaşamalıdır. Register pressure altında compiler bu register değerlerini stack'e spill eder; stack ise attacker-writable'dır. Bu spill klasik bir time-of-check-to-time-of-use (TOCTTOU) penceresi yaratır: değer check edildiğinde doğruydu, ama writable memory'den check'ten sonra reload edilir. Bir saldırgan spill edilmiş slot'u reload'dan önce overwrite edebilirse, branch saldırganın pointer'ını kullanır ama verification zaten "geçmiş"tir. CFI'nin dayandığı invariant — doğrulanmış pointer check ile use arasında değişmezdir — o pointer saldırganın kontrol ettiği writable memory'ye dokunduğu an kırılır.
Kritik nokta: stack-tabanlı bir vulnerability gerekmez. Makale, corruption'ı stack'le ilgisiz sıradan heap/memory-corruption primitive'leriyle gösterir: saldırgan bir stack adresini disclose eder, sonra ilgisiz bir bug üzerinden yazıp spill edilmiş CFI pointer'ını overwrite eder. Bu, StackDefiler'ı trusted control data'sını attacker-writable storage'a materialize eden her CFI tasarımının genel bir zayıflığı yapar.
Walkthrough¶
Kavramsal olarak, IFCC-korumalı bir indirect call kabaca şuna açılır (temsili pseudo-assembly, birebir compiler dump'ı değil):
; rax = target function pointer (attacker-influenced)
call __cfi_check ; rax'ı allowed-target set'ine karşı validate et
; ---- register pressure: rax bir stack slot'a spill ----
mov [rbp-0x18], rax ; SPILL: doğrulanmış pointer'ı writable stack'e
... ; başka iş rax'ı clobber eder
mov rax, [rbp-0x18] ; RELOAD pointer <-- TOCTTOU reload
call rax ; (artık attacker-controlled) target'a branch
Exploit sekansı:
1. Bir memory-corruption bug tetikle (heap, stack değil).
2. Spill edilmiş slot [rbp-0x18]'in adresini disclose et
(o slot için info-hiding / ASLR'yi yen).
3. __cfi_check orijinal değeri onayladıktan SONRA
[rbp-0x18]'i kötü niyetli bir code pointer'la overwrite et.
4. Reload + indirect call saldırgan koduna dispatch eder,
CFI'nin kendi bookkeeping'i ise check'in geçtiğini gösterir.
O-CFI neden kurtarmaz
Makale ayrıca O-CFI'yi (Opaque CFI) de kırar; O-CFI coarse bounds check'leri code randomization ile birleştirir ve bounds metadata'sını gizli tutmak için information hiding'a dayanır. StackDefiler, aynı spill-and-reload pattern'inin trusted bounds data'sını writable stack'e sızdırdığını/taşıdığını gösterir; böylece o bölgeyi okuyup yazabilen bir saldırgan, aksi halde gizli metadata'yı okur ve (artık bilinen) izinli bounds içinde kalan bir branch forge eder. Randomization artı info-hiding, secret attacker-writable memory'den geçtiği an yeterli değildir.
Kapsam ve uyarılar
- StackDefiler, CFI'nin bir kavram olarak değil, CFI state'inin nasıl saklandığının bir özelliğidir. Saldırganın yazamadığı, doğru implement edilmiş bir shadow stack'i bypass etmez.
- Spill, register allocation/optimization'ın emergent bir etkisidir; bu yüzden tam gadget compiler version'ına ve optimization level'ına göre değişir; zayıflığın sınıfı kalıcı sonuçtur.
- Saldırı, spill edilmiş slot'u bulup overwrite etmeye yeten bir arbitrary read/write primitive'i varsayar.
Mitigation¶
StackDefiler'ı durduran şey, doğrulanmış control-flow state'ini attacker-writable memory'den uzak tutmaktır:
- CFI-kritik değerleri register'larda pin'le / register'ları dedike et ki check-to-use penceresinde sıradan stack'e asla spill olmasınlar.
- Return/control data için hardware-protected shadow stack kullan (bkz. intel-cet-shadow-stack.md ve supervisor varyantı intel-cet-supervisor-shadow-stack.md) ki trusted kopya saldırganın bozabileceği writable memory'de olmasın.
- Branch'te yeniden valide eden hardware-assisted CFI (fineibt.md), software-spill'lenmiş "zaten checked" pointer'a bağımlılığı kaldırır.
- CFI metadata'sı için information hiding'a güvenme; saldırganın bir spill'in ulaşabileceği herhangi bir writable bölgeyi okuyabileceğini varsay.
Kalan ders tüm software CFI'ye genellenir (clang-cfi.md, kernel-control-flow-integrity.md): bir check'in güvenliği, ancak verification ile use arasında check ettiği değerin integrity'si kadar güçlüdür.
References¶
- Conti, Crane, Davi, Franz, Larsen, Liebchen, Negro, Qunaibit, Sadeghi, "Losing Control: On the Effectiveness of Control-Flow Integrity under Stack Attacks," ACM CCS 2015 — DOI 10.1145/2810103.2813671. https://dl.acm.org/doi/10.1145/2810103.2813671
- Yazar yayın sayfası (ics.uci.edu/~perl). https://ics.uci.edu/~perl/publication/losing_control/