Acest laborator este destinat înțelegerii modului în care funcționează bufferele și cum pot fi folosit 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 vector de N elemente. Dimensiunea totală a bufferului este N
x dimensiunea unui element. Un șir de caracter (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.
Pentru acest laborator vom folosi această arhivă 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ă. Întrucât în exercițiile finale avem nevoie și de compilatorul de la Microsoft, este recomandat să deschideți consola de Visual Studio, care are mediul deja configurat pentru compilarea de programe.
Pentru a deschide consola de Visual Studio urmați pașii:
Start
.All apps
.V
.Visual Studio 2015
.VS2015 x86 Native Tools Command Prompt
.
În general, pe parcursul laboratorului, în linia de comandă, vom folosi:
nasm
gcc
pe post de linkerobjdump
pentru dezasamblarea fișierelor obiect și executabilegdb
pentru analiza dinamică, investigație și debugging
În general nu va fi nevoie să dați comenzi de compilare. Fiecare director cuprinde un script batch (build.bat
) pe care îl puteți rula pentru a compila în mod automat fișierele cod sursă limbaj de asamblare sau C.
Înainte de î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, accesibilă în format PDF și original DIA la adresa: http://elf.cs.pub.ro/asm/res/laboratoare/lab-04-img/
Accesați directorul 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
build.bat
apoi rulați-l folosind comanda
.\data_buffer
Observați comportamentul programului funcție de codul său.
Accesați directorul 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
build.bat
apoi rulați-l folosind comanda
.\stack_buffer
Observați comportamentul programului 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ă.
Acum că am văzut cum arată buffer-ul în memorie și un 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.
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ă?
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.
ebx
și un offset ca să scrieți valoarea 0xDEADBEEF
la acea adresă.
Realizați acest lucru după secvența de inițializare a buffer-ului din jurul label-ului fill_byte
.
La o rezolvare corectă a acestui exercițiu, programul va afișa valoarea 0xDEADBEEF
pentru variabila locală.
Accesați directorul 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
build.bat
apoi rulați-l folosind comanda
.\read_stdin
Observați comportamentul programului funcție de codul său.
Funcția gets este o funcție care este practic interzisă în programele în 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
.
Notapad++
pentru editarea fișierului. Avantajul Notepad++
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
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 read-stdin/
într-u fișier cod sursă read_stdin_fgets.asm
în subdirectorul read-stdin-fgets/
. În fișierul cod sursă read_stdin.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 construcția __iob. Va trebui să o marcați externă la modul extern __iob
.
fgets
folosiți construcția
call _fgets
De asemenea, marcați simbolul ca fiind extern folosind construcția
extern _fgets
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.
payload
către program se face folosind o comandă precum
.\read_stdin < payload
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 suprascriere.
Pentru exercițiul curent, accesați directorul 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!.
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 37
; 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.
build.bat
payload
către program se face folosind o comandă precum
.\do_overflow < payload
De multe ori nu avem șansa accesului la codul sursă și vrem să descoperim vulnerabilități în fișiere executabile. În directorul overflow-in-binary
din arhiva de resurse a laboratorului, găsiți un fișier executabil și fișierul modul obiect din care a fost obținut. 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!.
objdump
pe fișierul obiect overflow_in_binary.obj
trebuie să rulați comanda
C:\"Program Files (x86)"\SASM\MinGW\bin\objdump.exe -M intel -d overflow_in_binary.obj