Call-Oriented Programming (COP)¶
Indirect CALL instruction'larında (RET değil) sonlanan gadget'ları zincirle; CFG-legal call edge'lerinde kalarak return-address savunmalarını ve coarse-grained CFI'yı bypass et.
Mechanism¶
Note
Return-address savunmaları (shadow stack'ler, kBouncer/ROPGuard "call-preceded" check'leri) yalnızca return edge'ini kısıtlar. Carlini et al. (USENIX 2015) bir shadow stack hakkında belirtir ki "attacker'ın return instruction'ları üzerinde çok sınırlı etkisi vardır … return instruction'larının keyfi başka konumlara dönmesine yol açamaz." COP bunu, control flow'u o savunmaların kısıtlamadığı indirect call'lar üzerinden sürerek aşar. Davi et al. (USENIX 2014) coarse-grained CFI'nın yalnızca "return address'in bir call instruction'ından sonra gelen bir instruction'a işaret etmesini" (herhangi bir call-preceded site) gerektirdiğini ve "indirect call pointer'larının integrity'sinin doğrulanmadığını" gösterir.
İki yapısal parça COP'u işler kılar:
- Call-site gadget'ları — bir indirect
callile biten diziler (örn.mov %esi,(%esp); call *%edi); adresi attacker-controlled memory/register'lardan gelen bir target'ı çağırır. - Dispatcher function — Carlini'nin COP'taki, JOP'un dispatcher gadget'ının
analoğu: "bir dispatcher function, attacker tarafından sağlanan argümanlar
verildiğinde kendi return address'ini overwrite edebilen bir fonksiyondur."
Bir write-what-where sunan herhangi bir fonksiyon (
memcpy,%nüzerindenprintf,strcat) gadget'ları zincirlerken her transfer CFG-legal bir edge olarak kalır.
Warning
COP, return-address korumasından kaçar çünkü asla corrupt olmuş bir return edge'ine dayanmaz — zincirleme, savunmanın izlemediği ya da yalnızca kabaca check ettiği indirect-call edge'leri üzerinden olur. JOP (indirect jump'lar) ve COOP (C++ virtual call'lar) ile ilişkilidir ama onlardan ayrıdır.
COP ≠ PCOP. Bu girdi, Carlini et al.'nin (2015) coarse-grained CFI
bypass argümanını anlatır; dispatcher burada kendi return address'ini
overwrite eden bir fonksiyondur (memcpy/printf %n). Buna karşılık
Pure-Call Oriented Programming (PCOP)
(Sadeghi & Niksefat, 2017) farklı bir akademik modeldir: her fonksiyonel
gadget bir indirect call ile biter ve katkı, push edilen return-address
side effect'lerinin nötralize edilip yalnızca call-terminated glibc
gadget'larıyla Turing-completeness'in gösterilmesidir.
Walkthrough¶
memcpy'yi dispatcher olarak kullanan pattern (Carlini'nin invariant'ı):
// Attacker controls all args to a later call: memcpy(dst, src, n)
dst = &(memcpy's own return address on the stack);
src = attacker_buffer; // holds the chosen "next location"
n = sizeof(void*);
// memcpy() overwrites its own return address, then returns to an
// attacker-chosen call-preceded instruction => an allowed CFG edge.
Beklenen davranış (Carlini'ye göre): "memcpy() kendi return address'ini
overwrite eder ve sonra başka bir konuma döner … Bu başka konum geçerli CFG'de
ise (yani memcpy()'ye yapılan bir call'ı izleyen bir instruction), o izin verilen
bir edge'dir ve CFI return'e izin verir." Bunu iterate etmek — Davi'nin
GOT-overwrite edilmiş indirect call'lar üzerinden Call-Ret-Pair gadget'ları
— coarse-grained call-preceded policy'sini hiç ihlal etmeden ve bir shadow
stack'in yakalayacağı return-address sahteciliği olmadan execve/VirtualAlloc'a
ulaşır. ROP /
JOP gadget discovery'sinin üzerine kurulur.
Detection¶
- Indirect-call target'larını gerçek call signature'ına karşı doğrulayan fine-grained / forward-edge CFI, call-site gadget chain'ini kırar.
- Indirect-branch tracking (Intel CET IBT), her indirect-call target'ında bir
ENDBRlanding pad'i gerektirir ve çoğu mid-function gadget'ını ortadan kaldırır.
Mitigation¶
- Forward-edge CFI (clang
-fsanitize=cfi) + CET IBT, indirect-call destination'larını kısıtlar. - Full RELRO, indirect call'ları yönlendirmek için kullanılan GOT-overwrite ayağını ortadan kaldırır.
- Coarse-grained, yalnızca call-preceded policy'ler yetersizdir — bu, paper'ın merkezi noktasıdır.