Loop-Oriented Programming (LOP)¶
Tüm meşru function'ları attacker-controlled bir dispatcher loop üzerinden zincirleyen bir code-reuse saldırısı; call/return deseni normal göründüğü için ROP/JOP'u durduran coarse-grained CFI ve shadow-stack savunmalarını bypass eder.
Mechanism¶
Suistimal edilen invariant
Coarse-grained Control-Flow Integrity (CFI) ve shadow stack'ler, indirect branch'lerin ve return'lerin nereye gidebileceğini kısıtlayarak klasik Return-Oriented Programming'i etkisiz bırakır: return'ler call'dan-önceki geçerli site'lara, indirect call'lar geçerli function entry'lerine düşmek zorundadır. LOP (Lan, Li, Sun, Su, Liu, Zeng — "Loop-Oriented Programming: A New Code Reuse Attack to Bypass Modern Defenses", IEEE TrustCom/BigDataSE/ISPA 2015), tüm function'ları meşru entry point'lerinden, bir loop ile sürülerek çağırmanın bu kuralların hepsini sağladığını gözlemler. Attacker-controlled loop'un her iterasyonu gerçek bir function entry'sine indirect call yapar — her coarse CFI politikasının zaten izin verdiği bir hedef — ve her function loop gövdesine normal şekilde döner, yani shadow stack dengede kalır. "Gadget"ler kısa ROP parçaları yerine tam function'lardır ve tutkal ret zinciri yerine sıradan bir loop olduğu için ne return-address kontrolü ne de entry-point CFI bunu işaretler. Orijinal PoC, 32-bit x86 üzerinde Internet Explorer 8'i hedefler.
Walkthrough¶
LOP, tek bir çalıştırılabilir PoC'den ziyade kavramsaldır; yapısı, attacker-controlled bir (function pointer, argümanlar) tablosu üzerinde dönen bir dispatcher loop'tur:
// Attacker-controlled data, reached via a memory-corruption write primitive.
struct invocation { void *fn; void *args; };
struct invocation chain[] = {
{ &VirtualProtect_thunk, &vp_args }, // make shellcode page +X
{ &memcpy_thunk, &cpy_args }, // stage payload
{ &shellcode_entry, 0 }, // pivot
};
// A legitimate loop already present in the target (the "loop gadget"):
for (i = 0; i < n; i++) {
invoke = table[i].fn; // indirect CALL to a real function ENTRY
invoke(table[i].args); // returns normally -> shadow stack balanced
}
Saldırı şunlara ihtiyaç duyar: (1) table'ı / loop'un iterasyon state'ini doldurmak için bir memory-corruption bug, (2) bir loop gadget — her iterasyonda attacker-controlled bellekten okuduğu bir pointer'ı indirect olarak çağıran, binary içindeki gerçek bir loop — ve (3) meşru entry'lerinden erişilebilen kullanışlı tam-function "macro-gadget"ler (örn. VirtualProtect, memcpy).
Klasik savunmalar neden kaçırır
- Coarse CFI: her indirect call geçerli bir function entry'sine düşer, dolayısıyla politika sağlanır.
- Shadow stack / return-address kontrolleri: her callee kendi caller'ına (loop gövdesine) döner, yani stack asla desenkronize olmaz — her gadget'in zincirin ortasına
retile bittiği ROP'un aksine. - Gadget yoğunluğu heuristic'leri: sayılacak kısa
pop;pop;retdizileri yoktur; yapı taşları tam function'lardır.
Çıtayı asıl yükselten şeyler fine-grained CFI, call-graph- ve argüman-farkında politikalar, ya da hangi function'ın hangi site'tan (ve hangi context ile) çağrılabileceğini kısıtlayan hardware'dir.
Detection¶
Hedef pointer'ı writable veriden okunan ve loop iterasyonları boyunca çılgınca değişen bir indirect-call site'ının davranışsal olarak izlenmesi güçlü bir sinyaldir. Call-stack/argüman anomali tespiti (örn. zararsız bir loop'un aniden RWX argümanlarla VirtualProtect çağırması), her bir transfer CFI açısından geçerli olsa bile suistimali yakalar.
Mitigation¶
Fine-grained / context-sensitive CFI, her call site'ını statik olarak amaçlanan callee kümesine bağlayan call-graph integrity, loop'un okuduğu function-pointer tablosunu koruyan Code-Pointer Integrity (CPI) ve tehlikeli function'lar (VirtualProtect/mprotect) etrafında W^X artı API-call sentinel'leri — hepsi LOP'u köreltir. Dispatcher tablosunu tohumlayan altta yatan memory-corruption primitive'i, kaldırılması gereken önkoşul olarak kalır.