==== Fantasy football ==== Od korisnika se zahtjeva da sastavi tim od 11 igrača. Na ponudi su mu 3 opcije: 1 -> dodaj igrača 2 -> obrisi igrača 3 -> završi (samo ako tim ima 11 igrača) Odabir opcija se nalazi u beskonačnoj petlji. Bitne funkcije koje se koriste su printTim, dodajIgraca, urediIgraca i obrisiIgraca. void dodajIgraca(struct igrac **tim){ unsigned int poz = pozicija(); tim[poz] = malloc(sizeof(struct igrac)); urediIgraca(tim,poz); printf("Igrač dodan na poziciji %d\n",poz+1); } Funkcija dodajIgraca interno poziva urediIgraca. void urediIgraca(struct igrac **tim, unsigned int poz){ char a; while((a=getchar())!='\n' && a!=EOF); printf("Dodijeli ime igraču na poziciji %d:\n",poz+1); scanf("%" STR2(NAZIV_L) "s",tim[poz]->naziv); } Dodaj igrača sadrži buffer overflow ranjivost. Duljina naziva jest NAZIV_L što je direktiva za duljinu 32. Scanf kao argument prima "%32s" što znači da se na offsetu 33 postavlja null bajt. struct igrac{ char naziv[NAZIV_L]; }; Veličina strukture igrac jest 32. Zbog toga ova ranjivost nije upotrebiva. Kada se zahtjevana veličina za malloc nalazi unutar prve polovice 16 bajt alignmenta (17-24, 33-40 itd...) (izuzetak su veličine [1-8] jer je minimalna veličina dobivenog chunka 16 bajta) koristi se prev_size polje sljedećeg chunka što bi omogućilo izmjenjivanje prev_in_use bita ako je moguć off by one overflow od najvećeg broja u rasponu prve polovice (24, 40...). Osim overflow-a, dostupan je i memory leak (u smislu zauzimanja memorije) ako se alociraju chunkovi na istoj poziciji. void obrisiIgraca(struct igrac **tim){ unsigned int poz = pozicija(); memset(tim[poz]->naziv,0,NAZIV_L); free(tim[poz]); printf("Igrač na poziciji %d je obrisan\n",poz+1); } Unutar obrisiIgraca se nalazi use after free ranjivost jer tim[poz] nije postavljen na NULL. Jer se opcije nalaze u beskonačnoj petlji time je moguć i double-free. Osim toga, pojavlja se dodatna ranjivost unutar printTim funkcije. void printTim(struct igrac **tim){ printf("\n\nTim:\n"); for (int i = 0;i<11;i++){ printf("\t%d. %s\n",i+1,tim[i]!=NULL?tim[i]->naziv:""); } printf("\n\n"); } Ako je tim[i] != NULL printa se naziv, a ako nije printa se "", tj. prazan string. Unutar funkcije obrisiIgraca struktura se memseta na \0 što znači da će na prvom free-u printTim isprintati "" u oba slučaja. Zbog verzije libc-a ovaj leak je donekle zamaskiran jer je tcache tail (s obzirom da je veličina strukture 32+16 -> tcache raspon) uvijek NULL jer je zadnji element. Na novijim verzijama bi se zbog safe linkage-a odmah dobio semi heap leak s 1/16 randomiziranosti. Kada se u tcacheu nalazi više chunkova output funkcije više nije isti jer se na next_chunk polju nalazi pointer na sljedeći chunk (osim na zadnjem). Dakle primitivi su UAF->double free i heap leak (te memory leak). Double free je ranjivost koja je moguća samo na single linked listama. Ideja je sljedeće: Tcache bin -> NULL (1. free) Tcache bin -> chunk a (2. free) Tcache bin -> chunk a -> chunk a -> chunk a ... (beskonačno) Kada chunk pointa na samog sebe dobije se beskonačna petlja. To znači da je moguće alocirati chunk "a" sa sljedećim malloc-om, a da tcache bin i dalje pointa na njega. Zatim, ako je moguće arbitrarno izmijeniti next_chunk pointer dobivenog chunka (što je u zadatku moguće unosom naziva igraća) tcache bin će pokazivati na chunk "a" koji pokazuje na proizvoljnu adresu. To znači da nakon 2 poziva malloca s veličinom chunka "a" dobiti će se chunk na proizvoljnoj adresi. S obzirom da je moguće upisivati arbitrarnu vrijednost u taj chunk, tok programa se može izmijeniti (got, malloc hook itd...). Ovaj zadatak moguće je riješiti na više načina s obzirom na koji način će se tok programa eksploitati. Datoteka nije PIE, što znači da je adresa got-a dostupna. To bi bio najlakši način jer adresa libc-a nije potrebna. U ovome writeupu riješenje će ići težim putem radi učenja. S obzirom na verziju libc-a, dostupan je primitiv izmjene flow-a preko malloc hooka. Također je moguće izmijeniti flow prepisivanjem got-a libc-a. Najprije je potrebno dobiti libc leak. Obično se dobije preko unsorted bina ili neke druge double-linked liste. Kako bi se dobio chunk koji nakon free-a ide u unsorted bin mora se osloboditi chunk koji nije unutar tcache raspona (veličina s headerom > 0x410). U teoriji bi se moglo i da je u rasponu tcache-a, ali da nije u rasponu fastbina, no za to bi bilo potrebno alocirati barem 8 chunkova s tom veličinom te je zbog toga prva opcija lakša. Kako bi se krivotvorio chunk s veličinom 0x420 potrebno je alocirati 22 chunka, izmijeniti size header prvog chunka i zatim ga osloboditi. Također, da završi u unsorted binu ne smije se konsolidirati s top chunkom zbog čega je potrebno staviti jedan chunk između njih (alociranje 23 chunka umjesto 22). Dakle, na nekoj poziciji tima (npr. 1.) se alocira chunk, a na drugoj poziciji tima (npr. 2.) se 22 puta ponovno alocira chunk. Zatim se iskoristi double-free ranjivost oslobađanjem chunka na prvoj poziciji 2 puta (potrebno je 3 puta zbog internog brojanja chunkova za tcache binove, tj. ako je broj negativan smatra se da se tcache ne koristi zbog čega se gleda fastbin -> free inkrementacija, malloc dekrementacija). Pošto je chunk i dalje unutar tima, na ispisu je dostupan heap leak. Na trećoj poziciji se alocira chunk i za naziv stavi adresa prev_size headera prvog chunka. Ponovno se na trećoj poziciji dva puta alocira chunk kako bi se dobio chunk na adresi prev_size headera prvog chunka. Za naziv se na offsetu 8 doda 0x421 (prev_in_use bit mora biti postavljen na 1 kako bi se izbjegla konsolidacija). Zatim se prvi chunk oslobodi i adresa main arene je na ispisu. Ponovno se iskoristi double free ranjivost (npr. chunk na drugoj poziciji) te alocira chunk s nazivom postavljenim na adresu malloc hooka. Dva puta se alocira chunk te se za naziv stavi adresa one gadgeta. Na sljedećem pozivu malloc-a pozvati će se shell.