This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
laboratoare:laborator-08 [2015/12/02 04:34] adrian.bogatu |
laboratoare:laborator-08 [2015/12/02 12:31] (current) vladimir.diaconescu OCD change |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Laborator 8: Interfața în linia de comandă, analiza statică și dinamică ====== | + | ====== Laborator 08: Interfața în linia de comandă, analiza statică și dinamică ====== |
După un scurt breviar care va explica noțiunile introduse în acest laborator, va urma o parte practică care alternează între secțiuni de tip tutorial, cu parcurgere pas cu pas și prezentarea soluției, și exerciții care trebuie să fie rezolvate. | După un scurt breviar care va explica noțiunile introduse în acest laborator, va urma o parte practică care alternează între secțiuni de tip tutorial, cu parcurgere pas cu pas și prezentarea soluției, și exerciții care trebuie să fie rezolvate. | ||
Line 27: | Line 27: | ||
===== Tutoriale și exerciții ===== | ===== Tutoriale și exerciții ===== | ||
- | În cadrul exercițiilor vom folosi arhiva de laborator. | + | În cadrul exercițiilor vom folosi {{laboratoare:lab-08-tasks.zip|arhiva de laborator}}. |
Descărcați arhiva, decomprimați-o și accesați directorul aferent. | Descărcați arhiva, decomprimați-o și accesați directorul aferent. | ||
Line 151: | Line 151: | ||
=== Lansarea în execuție a programului === | === Lansarea în execuție a programului === | ||
- | Pentru a lansa programul urmărit în execuție există două comenzi: | + | Pentru a lansa programul urmărit în execuție există două comenzi disponibile: |
* **run** - această comandă va lansa în execuție programul | * **run** - această comandă va lansa în execuție programul | ||
* **start** - spre deosebire de ''run'', această comandă va începe execuția programului, însă se va opri imediat după intrarea în main | * **start** - spre deosebire de ''run'', această comandă va începe execuția programului, însă se va opri imediat după intrarea în main | ||
Line 181: | Line 181: | ||
Pentru continuarea programului după eventuala sa oprire într-un breakpoint, puteți folosi comanda ''continue''. | Pentru continuarea programului după eventuala sa oprire într-un breakpoint, puteți folosi comanda ''continue''. | ||
+ | |||
+ | Un alt lucru interesant în GDB este comanda ''commands'', care poate asocia unui breakpoint un bloc de comenzi GDB ce vor fi executate la fiecare oprire în breakpoint-ul respectiv. | ||
+ | |||
+ | Exemplu: | ||
+ | <code> | ||
+ | (gdb) break *0x004013af | ||
+ | Breakpoint <n> at 0x4013af | ||
+ | (gdb) commands <n> | ||
+ | Type commands for breakpoint(s) <n>, one per line. | ||
+ | End with a line saying just "end" | ||
+ | > print $eax | ||
+ | > x/i $eip | ||
+ | > end | ||
+ | </code> | ||
+ | |||
+ | Pentru a nu rămâne blocat în breakpoint (spre exemplu dacă scrieți un script de gdb), puteți adăuga în blocul de instrucțiuni și comanda ''continue''. | ||
+ | |||
+ | Haideți să adăugăm un breakpoint la label-ul ''ok''. Dacă dăm ''continue'', vom observa că programul s-a oprit în breakpoint-ul tocmai creat. | ||
+ | <note> | ||
+ | Variaţii:\\ | ||
+ | **break label** - breakpoint la labelul **label**\\ | ||
+ | **break *(label + <offset>)** - breakpoint la **label + offset**\\ | ||
+ | </note> | ||
=== Parcurgerea instrucțiunilor === | === Parcurgerea instrucțiunilor === | ||
Line 188: | Line 211: | ||
* **stepi** - care practic trimite o instrucțiune spre execuție și după execuția acesteia întoarce control-ul la debugger (programul se oprește) | * **stepi** - care practic trimite o instrucțiune spre execuție și după execuția acesteia întoarce control-ul la debugger (programul se oprește) | ||
* **nexti** - comandă similară cu ''stepi'', însă dacă instrucțiunea curentă este un apel de funcție, debugger-ul nu va intra în funcție (va chema funcția și se va opri la următoarea instrucțiune după ''call'') | * **nexti** - comandă similară cu ''stepi'', însă dacă instrucțiunea curentă este un apel de funcție, debugger-ul nu va intra în funcție (va chema funcția și se va opri la următoarea instrucțiune după ''call'') | ||
+ | |||
+ | Dacă emitem comanda ''stepi'', putem observa că se afișează instruction pointer-ul instrucțiunii următoare dupa cea la care am făcut break (prima de la label-ul ''ok''). | ||
=== Dezasamblarea programului === | === Dezasamblarea programului === | ||
Line 195: | Line 220: | ||
<note> | <note> | ||
Default, sintaxa folosită de GDB la dezasamblare este cea "AT&T". Pentru a folosi sintaxa cunoscută vouă (sintaxa intel), executați în GDB comanda ''set disassembly-flavor intel''. | Default, sintaxa folosită de GDB la dezasamblare este cea "AT&T". Pentru a folosi sintaxa cunoscută vouă (sintaxa intel), executați în GDB comanda ''set disassembly-flavor intel''. | ||
+ | </note> | ||
+ | |||
+ | În cadrul exemplului nostru, dacă cerem dezasamblarea funcției curente (folosind ''disassemble'' fără parametri) putem observa că ne aflăm la label-ul ''ok''. Observație: GDB iterpretează label-ul ''ok'' ca o funcție din cauza codului inițial, care este scris în limbaj de asamblare. | ||
+ | |||
+ | Pentru a vedea mai clar efectul ''stepi''/''nexti'' putem rula commanda ''disassemble'' înainte și după stepping. | ||
+ | |||
+ | <note> | ||
+ | Dacă ați intrat într-o funcție lungă și nu vreți să dați de ''nexti'' de foarte multe ori, vă recomandăm instrucțiunea GDB ''finish'', care "termină" o funcție. **Atenție** la funcțiile recursive. | ||
+ | </note> | ||
+ | <note> | ||
+ | **disassemble label, +<length>** - afişează <length> bytes de cod dezasamblat începând de la labelul **label**. | ||
</note> | </note> | ||
=== Inspectarea memoriei și a registrelor === | === Inspectarea memoriei și a registrelor === | ||
- | Pentru a afișa diferite valori accesiblie GDB-ului se folosește comanda ''print''. De exemplu, pentru a afișa valoarea unui registru (de exemplu eax), vom folosi contrucția ''print $eax''. | + | Pentru a afișa diferite valori accesibile GDB-ului se folosește comanda ''print''. De exemplu, pentru a afișa valoarea unui registru (de exemplu eax), vom folosi construcția ''print $eax''. |
Pentru inspectarea memoriei se folosește comanda ''x'' (examine). Modul de folosire al acestei comenzi este următorul: | Pentru inspectarea memoriei se folosește comanda ''x'' (examine). Modul de folosire al acestei comenzi este următorul: | ||
Line 209: | Line 245: | ||
* ''f'' este formatul de afișare (x pentru hexa, d pentru zecimal, s pentru șir de caractere și i pentru instrucțiuni) | * ''f'' este formatul de afișare (x pentru hexa, d pentru zecimal, s pentru șir de caractere și i pentru instrucțiuni) | ||
* ''u'' este dimensiunea unui element (b pentru 1 octet, h pentru 2, w pentru 4 și g pentru 8 octeți) | * ''u'' este dimensiunea unui element (b pentru 1 octet, h pentru 2, w pentru 4 și g pentru 8 octeți) | ||
+ | |||
+ | De exemplu, o funcționalitate similară cu ''disassemble'' o putem obține și folosind ''x'' unde formatul este instrucțiune. Astfel, putem afișa, de exemplu, 10 instrucțiuni începând de la instrucțiunea curentă cu construcția ''x/10i $eip''. | ||
==== [0.5p] 7. Afișarea unor informații la fiecare trecere printr-un breakpoint ==== | ==== [0.5p] 7. Afișarea unor informații la fiecare trecere printr-un breakpoint ==== | ||
- | ==== [1p] 8. Afișarea adresei de retur a unei funcții ==== | + | |
+ | Folosind executabilul creat la exercițiul anterior (''gdb-tutorial.asm''), trebuie să setați un breakpoint la intrare în bucla din program (când se mută în subregistrul ''al'' un caracter din șirul input). În plus, trebuie să adăugați o serie de comenzi astfel încât la fiecare intrare în buclă, GDB să afișeze valoarea subregistrului ''al'' și valoarea counter-ului (în cazul nostru ''ecx''). **Hint!** folosiți comanda ''commands''. | ||
+ | |||
+ | ==== [1p] 8. Afișarea adresei de retur ale unor funcții ==== | ||
+ | |||
+ | Folosind tot executabilul de mai înainte, afișați adresele de return ale tuturor funcțiilor din program (gets, atoi, printf, usage). Pentru cazul funcției usage, trebuie să porniți programul fără parametri. | ||
==== [0.5p] 9. Tutorial: Depanarea unui Segfault folosind GDB ==== | ==== [0.5p] 9. Tutorial: Depanarea unui Segfault folosind GDB ==== | ||
+ | |||
+ | Pentru acest tutorial pornim de la fișierul sursă ''segfault-tutorial.asm''. Înainte de a începe tutorialul, citiți sursa, înțelegeți ce face și apoi asamblați și link-editați programul. | ||
+ | |||
+ | Dacă încercați să rulați programul fără parametri, se poate observa că progamul "crapă". Dacă executăm programul sub gdb, putem observa că programul primește ''SIGSEGV''. Pentru a putea determina problema, executăm comanda ''backtrace'', care arată ultimele stack frame-uri prin care execuția programului a trecut. În cazut nostru, doar două: | ||
+ | <code> | ||
+ | (gdb) backtrace | ||
+ | #0 0x7607d2c3 in strcat () ... | ||
+ | #1 0x00000000 in ?? () | ||
+ | </code> | ||
+ | |||
+ | Ne dăm seama că frame-ul interesant pentru noi este ''#0''. Pentru a schimba frame-ul curent folosim comana ''frame <nr. frame>''. Odată ce suntem pe frame-ul ce ne interesează putem să încercăm dezasamblarea programului pentru a identifica problema. | ||
+ | |||
+ | După instrucțiunea ''disassemble'', putem observa instruction pointer-ul (notat pe dezasamblarea din GDB cu ''=>'' în dreptul unei instrucțiuni) că a rămas la instrucțiunea | ||
+ | <code>mov eax, DWORD PTR [ecx]</code> | ||
+ | |||
+ | Deja putem bănui o posibilă cauză a segmentation fault-ului. Inspectați registrul ecx. Ce valoare are? Ce încearcă să facă instrucțiunea cu probleme? | ||
+ | |||
==== [1.5p] 10. Rezolvarea unui Segfault ==== | ==== [1.5p] 10. Rezolvarea unui Segfault ==== | ||
+ | |||
+ | Pornind de la executabilul ''segfault.exe'', rulat sub gdb, analizați atât backtrace-ul cât și pas cu pas codul pentru a identifica cauza care duce la Segmentation Fault. | ||
+ | |||
==== [1p] 11. Tutorial: IDA ==== | ==== [1p] 11. Tutorial: IDA ==== | ||
- | ==== [1.5p] Bonus: Modificarea control-flow-ului unui program folosind GDB ==== | + | |
+ | În această secțiune vom prezenta foarte pe scurt câteva dintre numeroasele aspecte ale programului IDA. | ||
+ | |||
+ | În primul rând va trebui să descărcați installer-ul de IDA de [[https://out7.hex-rays.com/files/idafree50.exe|aici]] și să-l instalați pe mașinile din laborator. După ce programul s-a instalat, porniți Ida Pro Free. La prompt-ul de welcome alegeți varianta ''[GO] Work on your own''. Din meniul de sus dați "Open..." și deschideți fișierul de la exercițiul 10 (''segfault.exe''). | ||
+ | |||
+ | Dintre numeroasele view-uri posibile din IDA, cele mai importante sunt: | ||
+ | |||
+ | * Control-flow graph-ul ce conține pentru fiecare bloc dezasamblarea sa | ||
+ | * **Names** care este o tabelă cu simbolurile din executabil | ||
+ | * **Functions** care conține toate informațiile despre o funcție (dacă e statică sau dintr-o bibliotecă; tipul funcției etc.) | ||
+ | |||
+ | Deși pot fi multe de spus despre IDA, în cadrul acestui laborator ne vom limita doar la capabilitățile de analiză statică ale sale. | ||
+ | <note> | ||
+ | IDA este destul de avansat încât are posibilitatea de a face tracking şi în Kernelul sistemului de operare. Motiv pentru care trebuie rulat cu drepturi de administrator pentru a putea dreptul la o resursă critică a sistemului. | ||
+ | </note> | ||
+ | ==== [2p] Bonus: Modificarea control-flow-ului unui program folosind GDB ==== | ||
+ | |||
+ | Pornind de la executabilul ''control-flow.exe'' rulat sub gdb, trebuie să găsiți o modalitate să se afișeze un flag. Până nu veți rezolva corect problema, se va afișa "No flag for you." | ||
==== [2p] Bonus: Decompilarea unui program folosind IDA ==== | ==== [2p] Bonus: Decompilarea unui program folosind IDA ==== | ||
+ | Pornind de la fișierul obiect ''decompile.obj'', treaba voastră este să reconstruiți codul sursa (scris în C) din care a provenit binarul. Puteți folosi orice utilitar de analiză statică, însă cel mai indicat pentru acest task este IDA, deoarece reprezintă foarte clar graful de control al execuției. |