Laborator 10: Gestiunea bufferelor. Buffer overflow

Acest laborator este destinat înțelegerii modului în care funcționează bufferele și cum pot fi folosite la nivel de început vulnerabilităti de tip buffer overflow, adică depășirea limitelor unui buffer.

Un buffer este o zonă de memorie definită printr-o adresă de start și o dimensiune. Cel mai adesea un buffer este un vector de N elemente. Dimensiunea totală a bufferului este N x dimensiunea unui element. Un șir de caractere (string) este un caz particular de buffer.

În cele ce urmează vom urmări exemple de folosire a bufferelor și vom vedea cum putem trece dincolo de limita unui buffer (de dimensiunea sa), provocând ceea ce se numește buffer overflow.

Pregătire infrastructură

Pentru acest laborator vom folosi arhiva de resurse. Descărcați arhiva și accesați conținutul acesteia.

Pentru desfășurarea acestui laborator vom folosi interfața în linia de comandă.

În general, pe parcursul laboratorului, în linia de comandă, vom folosi:

  • asamblorul nasm
  • comanda gcc pe post de linker
  • objdump pentru dezasamblarea fișierelor obiect și executabile
  • gdb pentru analiza dinamică, investigație și debugging

În general nu va fi nevoie să dați comenzi de compilare. Fiecare director cuprinde un Makefile pe care îl puteți rula pentru a compila în mod automat fișierele cod sursă limbaj de asamblare sau C.

Înainte de a începe laboratorul, alocați-vă 1-2 minute să reparcurgeți diagrama de mai jos ce arată structura stivei în cazul unui apel de funcție.

[1p] 1. Tutorial: Folosirea unui buffer în zona de date

Accesați, în linia de comandă, directorul 1-data-buffer/ din arhiva de resurse a laboratorului și consultați fișierul data_buffer.asm. În acest fișier se găsește un program care populează un buffer cu informații și apoi le afișează.

Consultați cu atenție programul, apoi compilați-l folosind comanda

make

Observați că în urma comenzii de compilare de mai sus au rezultat un fișier obiect și un fișier executabil, prin rularea comenzii

ls

Rulați programul prin intermediul fișierului executabil, adică folosind comanda

./data_buffer

Observați comportamentul programului în funcție de codul său.

[1p] 2. Tutorial: Folosirea unui buffer pe stivă

Accesați directorul 2-3-4-stack-buffer/ din arhiva de resurse a laboratorului și consultați fișierul stack_buffer.asm. În acest fișier se găsește un program care populează un buffer cu informații și apoi le afișează. Este similar celui de mai sus doar că acum buffer-ul este alocat pe stivă.

Consultați cu atenție programul, apoi compilați-l folosind comanda

make

apoi rulați-l folosind comanda

./stack_buffer

Observați comportamentul programului în funcție de codul său.

Pe lângă buffer am mai alocat o variabilă locală pe 4 octeți, accesibilă la adresa ebp-4. Este inițializată la valoarea 0xCAFEBABE. Această variabilă va fi importantă mai târziu. Ce este relevant acum este să știm că această variabilă este în memorie imediat după buffer: când se trece de limita buffer-ului se ajunge la această variabilă.

Care este diferenta intre cele 2 programe inspectate pana acum?

[1p] 3. Citirea de date dincolo de dimensiunea buffer-ului

Acum că am văzut cum arată buffer-ul în memorie și unde este plasată variabila, actualizați programul stack_buffer.asm pentru ca secvența de afișare a buffer-ului (cea din jurul etichetei print_byte) să ducă și la afișarea octeților variabilei. Adică trebuie să citiți date dincolo de dimensiunea buffer-ului (și să le afișați). Este un caz de buffer overflow de citire, cu obiectiv de information leak: aflarea de informații din memorie.

Nu e ceva complicat, trebuie doar să "instruiți" secvența de afișare să folosească altă limită pentru afișare, nu limita curentă de 64 de octeți.

Afișați și alte informații dincolo chiar de variabila locală. Ce informație vine pe stivă după variabila locală (următorii 4 octeți)? Dar următorii 4 octeți după?

[1p] 4. Scrierea de date dincolo de dimensiunea buffer-ului

Pe baza experienței de mai sus, realizați modificări pentru ca valoarea variabilei să fie 0xDEADBEEF (în loc de 0xCAFEBABE cum este la început) fără a modifica însă explicit valoarea variabilei. Folosiți-vă de modificarea buffer-ului și de registrul ebx în care am stocat adresa de început a buffer-ului.

Din nou, nu este ceva complicat. Trebuie să vă folosiți de valoarea ebx și un offset ca să scrieți valoarea 0xDEADBEEF la acea adresă. Adică folosiți o construcție de forma:

    mov byte [ebx+TODO], TODO

Realizați acest lucru după secvența de inițializare a buffer-ului (după instrucțiunea jl fill_byte).

La o rezolvare corectă a acestui exercițiu, programul va afișa valoarea 0xDEADBEEF pentru variabila locală.

[1p] 5. Tutorial: Citirea de date de la intrarea standard

Accesați directorul 5-6-read-stdin/ din arhiva de resurse a laboratorului și consultați fișierul read_stdin.asm. În acest fișier se găsește un program care folosește apelul gets ca să citească informații de la intrarea standard într-un buffer de pe stivă. La fel ca în cazul precedent am alocat o variabilă locală pe 4 octeți imediat după buffer-ul de pe stivă.

Consultați cu atenție programul, apoi compilați-l folosind comanda

make

apoi rulați-l folosind comanda

./read_stdin

Observați comportamentul programului funcție de input-ul primit.

[1.5p] 6. Buffer overflow cu date de la intrarea standard

Funcția gets este o funcție care este practic interzisă în programele C din cauza vulnerabilității mari a acesteia: nu verifică limitele buffer-ului în care se face citirea, putând fi ușor folosită pentru buffer overflow.

Pentru aceasta transmiteți șirul de intrare corespunzător pentru ca valoarea afișată pentru variabila locală să nu mai fie 0xCAFEBABE, ci să fie 0x574F4C46 (valorile ASCII în hexazecimal pentru FLOW).

Nu modificați codul în limbaj de asamblare. Transmiteți șirul de intrare în format corespunzător la intrarea standard pentru a genera un buffer overflow și pentru a obține rezultatul cerut.

Nu scrieți șirul "574F4C46". Acesta e un șir care ocupă 8 octeți.

Trebuie să scrieți reprezentarea ASCII a numărului 0x574F4C46 adică FLOW: 0x57 este W, 0x4F este O, 0x4C este L iar 0x46 este F.

Ca să transmiteți șirul de intrare, e recomandat să-l scrieți într-un fișier și apoi să redirectați acel fișier către comanda aferentă programului. Puteți folosi un editor precum gedit sau vim pentru editarea fișierului. Avantajul acestora este că vă afișează și coloana pe care vă aflați și puteți să știți câte caractere ați scris în fișier.

E recomandat să numiți fișierul payload. Redirectarea fișierului payload către program se face folosind o comandă precum

./read_stdin < payload

[1.5p] 7. Buffer overflow cu date de la intrarea standard și fgets()

Așa cum am precizat mai sus, funcția gets este interzisă în programele curente. În locul acesteia se poate folosi funcția fgets. Creați o copie a fișierului cod sursă read_stdin.asm din subdirectorul 5-6-read-stdin/ într-un fișier cod sursă read_stdin_fgets.asm în subdirectorul 7-read-stdin-fgets/. În fișierul cod sursă read_stdin_fgets.asm schimbați apelul funcției gets() cu apelul funcției fgets.

Pentru apelul fgets() citiți de la intrarea standard. Ca argument pentru al treilea parametru al fgets() (de tipul FILE *) veți folosi intrarea standard. Pentru a specifica intrarea standard folosiți stream-ul stdin. Va trebui să îl marcați ca extern folosind, la începutul fișierului în limbaj de asamblare, construcția

extern stdin

stdin este o adresă; pentru a apela fgets() cu intrarea standard, este suficient să transmitem pe stivă adresa valorii de la adresa stdin, adică folosind construcția

push dword [stdin]

Urmăriți pagina de manual a funcției fgets() pentru a afla ce parametri primește.

Pentru apelul funcției fgets() folosiți construcția

call fgets

De asemenea, marcați simbolul ca fiind extern folosind construcția

extern fgets

Întrucât funcția fgets() are 3 parametri (care ocupă 3×4=12 octeți) va trebui ca după apelul funcției, în restaurarea stivei, să folosiți add esp, 12 (în loc de add esp, 4 ca în cazul programul de mai sus care folosea gets()).

Să păstrați posibilitatea unui buffer overflow și să demonstrați acest lucru prin afișarea valorii 0x574F4C46 pentru variabila locală. Adică să folosiți ca al doilea argument pentru fgets() (dimensiunea) o valoare suficient de mare cât să permită realizarea unui buffer overflow.

La fel ca mai sus, ca să transmiteți șirul de intrare pentru program, e recomandat să-l scrieți într-un fișier și apoi să redirectați acel fișier către comanda aferentă programului. Redirectarea fișierului payload către program se face folosind o comandă precum

./read_stdin_fgets < payload

Nu modificați codul în limbaj de asamblare. Transmiteți șirul de intrare în format corespunzător la intrarea standard pentru a genera un buffer overflow și pentru a obține rezultatul cerut.

[2p] 8. Buffer overflow pentru program scris în cod C

De cele mai multe ori vom identifica vulnerabilități de tip buffer overflow în programe scrise în C. Acolo trebuie să vedem ce buffere sunt și care este distanța de la buffer la variabila dorită pentru a putea face suprascrierea.

Este important de avut în vedere că distanța între un buffer și o altă variabilă în C poate nu corespunde cu cea "din teren"; compilatorul poate face actualizări, reordonări, poate lăsa spații libere între variabile etc.

Pentru exercițiul curent, accesați directorul 8-c-buffer-overflow/ din arhiva de resurse a laboratorului și observați codul sursă aferent în C. Pentru cazul în care doriți să nu mai compilați voi codul aveți în arhivă și fișierul limbaj de asamblare echivalent și fișierul în cod obiect și fișierul executabil.

Descoperiți diferența între adresa buffer-ului și adresa variabilei, creați un fișier de intrare (numit și payload) cu care să declanșați overflow-ul și faceți în așa fel încât să fie afișat mesajul Full of win!.

Ca să vedeți realitatea "din teren", adică să aflați care este diferența dintre buffer și variabila pe care dorim să o suprascriem, consultați fișierul în limbaj de asamblare echivalent (do_overflow.asm), obținut prin asamblarea codului C.

În acest fișier puteți afla adresa relativă a buffer-ului față de ebp și a variabilei față de ebp; urmăriți secvența cuprinsă între liniile 32 și 41; aveți o mapare între numele variabilei și offset-ul relativ față de ebp. Cu aceste informații puteți crea șirul pe care să îl transmiteți ca payload către intrarea standard a programului.

Dacă doriți să recompilați fișierele rulați

make clean && make

La fel ca mai sus, ca să transmiteți șirul de intrare pentru program, e recomandat să-l scrieți într-un fișier și apoi să redirectați acel fișier către comanda aferentă programului. Redirectarea fișierului payload către program se face folosind o comandă precum

./do_overflow < payload

Doar ca exemplu: x86 este o arhitectură little endian. Adică șirul "FRED", având corespondența caracter-cod ASCII F: 0x46, R: 0x52, E: 0x45, D: 0x44 va fi stocat în memorie pe 4 octeți ca 0x44455246.

Ce trebuie să faceți: Va trebui ca, în cadrul buffer overflow-ului, să obțineți valoarea în memorie 0x5541494d. Obțineți șirul ASCII corespondent valorii în memorie 0x5541494d pe care trebuie să îl furnizați la intrarea standard a programului vulnerabil.

[2p] 9. Bonus: Buffer overflow pentru binar

De multe ori nu avem șansa accesului la codul sursă și vrem să descoperim vulnerabilități în fișiere executabile. În directorul 9-overflow-in-binary din arhiva de resurse a laboratorului, găsiți un fișier executabil. Folosind objdump sau gdb pentru investigație descoperiți cum puteți exploata vulnerabilitatea de tip buffer overflow, pentru ca programul să afișeze mesajul Great success!.

Pentru a rula objdump pe fișierul executabil overflow_in_binary trebuie să rulați comanda

objdump -M intel -d overflow_in_binary

Identificați în codul dezasamblat cum se transmite intrare către program. Identificați unde este buffer overflow-ul. Identificați condiția de comparație pe care doriți să o declanșați. Apoi construiți payload-ul corespunzător și transmiteți-l în forma adecvată programului.

iocla/laboratoare/laborator-10.txt · Last modified: 2018/12/06 16:37 by ioana_elena.ciornei
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0