Shadow-Stack-Aware ROP (BOP under shadow stack)¶
Geçerli control-flow yolları boyunca bütün basic block'ları birbirine diken, return address'leri olduğu gibi bırakan ve böylece shadow stack'lerin ve CFI'ın asla ihlal edilmediği data-only / block-oriented code reuse.
Mechanism¶
Note
Bir shadow stack (hardware veya software) tek bir invariant'ı uygular: her
ret, eşleşen bir call'un push ettiği adrese döner. Klasik ROP, kaydedilmiş
return address'leri üzerine yazar, böylece o invariant'ı ihlal eder ve
yakalanır. Shadow-stack-aware code reuse return address'lere hiç dokunmaz.
Bunun yerine data'yı bozar — heap/stack değişkenleri, flag'ler, loop
counter'lar, function pointer'lar — böylece programın kendi meşru control
flow'u attacker'ın seçtiği bir yolu yürür. Alınan hiçbir edge CFI'ı veya
shadow stack'i ihlal etmez, çünkü her edge programın zaten alabileceği bir
edge'dir.
Block Oriented Programming (BOP), Ispoglou ve diğerleri tarafından CCS 2018'de tanıtıldı ve bunu otomatikleştirir. Threat model'i, hedefin R^X (W^X), shadow stack'ler (veya stack canary'ler), ASLR ve CFI ile korunduğunu ve attacker'ın bir arbitrary memory-write primitive'i (artı opsiyonel olarak ASLR'ı yenmek için bir read primitive'i) elinde tuttuğunu varsayar.
Yapı taşları instruction-suffix gadget'ları değil, geçerli yollar boyunca basic block'lardır:
- Functional block'lar — side effect'leri istenen payload'un bir statement'ını implemente eden basic block'lar (ör. bir register set et, bir syscall yap).
- Dispatcher block'lar — yol boyunca ortaya çıkan kısıtları (register/memory side effect'leri) sağlarken execution'ı bir functional block'tan diğerine "büken" bağlayıcı code path'ler.
Her functional ve dispatcher block programın mevcut edge'leri üzerinden erişildiğinden, forward-edge CFI yalnızca equivalence class'ları içindeki legal indirect target'ları görür ve shadow stack yalnızca meşru şekilde eşleşmiş call/return çiftlerini görür. Savunmalar tatmin edilir; computation attacker tarafından tanımlanır. Bu, ROP'un data-only analoğudur: yeterince zincirlenebilir block verildiğinde Turing-complete.
Walkthrough¶
BOPC (BOP compiler'ı) üç input alır ve bir write dizisi üretir:
- Hedefi virtual register'lar kullanarak ifade eden, SPL (küçük, C benzeri,
Turing-complete bir dil) ile yazılmış bir payload, ör. bir
execve:
/* SPL-style payload sketch */
void payload() {
__r0 = "/bin/sh"; /* arg pointer */
__r1 = 0; /* argv */
__r2 = 0; /* envp */
__syscall(59, __r0, __r1, __r2); /* execve */
}
- Bir başlangıç noktası (ör. bir fuzzer crash'i ile ortaya çıkan bir arbitrary-write konumu).
- BOPC'nin aday functional ve dispatcher block'ları topladığı hedef binary.
BOPC sonra SPL statement'larından gerçek basic block'lardan oluşan bir chain'e mapping'i çözer. Bu mapping NP-hard'dır (paper bunu symbolic execution'a benzetir): iki functional block arasında satisfiable bir yol bularak başlar, sonra chain'i iteratif olarak üç, dört, beş statement'a genişletir, infeasible yolları budar ve olası olanlara yönelmek için heuristic'ler kullanır.
Kavramsal olarak attacker'ın write'ları state'i şöyle kurar:
[functional block A] -> sets __r0 (no CFI/shadow-stack violation)
\ dispatcher path satisfying side-effect constraints
[functional block B] -> sets __r1
\ dispatcher path ...
[functional block C] -> performs syscall
Paper, BOPC'yi 13 SPL payload ile bilinen CVE'lere karşı 10 gerçek program
üzerinde değerlendirir; nginx case study'si execve'i, loop'ları ve conditional
branch'leri tamamen data corruption ile sentezler.
Warning
Başarılı bir BOP chain'inde hiçbir instruction bir return address'i üzerine yazmaz veya bir indirect branch'i CFI'ın izin verdiği target set'inin dışına yönlendirmez. Ne bir shadow stack ne de forward-edge CFI alarm vermemesinin nedeni tam olarak budur — bunlar state-corruption / data-only saldırılara karşı doğru savunmalar değildir.
Detection¶
Control flow legal edge'ler içinde kaldığından, geleneksel CFI/shadow-stack ihlal detector'ları sessiz kalır. Detection bunun yerine data-flow anomalisini hedeflemelidir: security açısından önemli değişkenlerde mantıksız değerler (privilege flag'leri, loop bound'ları, atipik-ama-legal target'lara ulaşan function pointer'lar) veya alışılmadık block-dizisi frekansları. Fine-grained data-flow integrity (DFI) ve pointer-validity şemaları kavramsal karşı koymadır, return-address koruması değil.
Mitigation¶
Bu sınıfı gerçekten kısıtlayan savunmalar:
- Data-Flow Integrity / Code-Pointer Integrity. Hassas pointer'ları ve data'yı doğrudan korumak — bkz. code-pointer-integrity — attacker'ın işe yarar şekilde bozabileceği state'i daraltır.
- Memory safety / tagging. Altta yatan write primitive'ini arm-memory-tagging-extension, addresssanitizer tarzı kontroller veya cheri-capabilities ile yakalamak, ön koşul olan arbitrary write'ı ortadan kaldırır.
- Daha ince taneli CFI. Daha sıkı target set'ler (clang-cfi, fineibt), kullanılabilir dispatcher edge'lerinin arzını küçültür, ancak data-only saldırıları yok etmezler.
Bu not, block-oriented-programming'in kardeşi ve sidestep etmek için tasarlandığı shadowcallstack / intel-cet-shadow-stack'in tersidir.