Siccome giorni fa cercavo una guida che trattasse questi argomenti ma ho trovato poco quel poco ve lo condivido.
Vedremo come è facile interrogare i registri nella cpu e capire come viene allocata la memoria
- Objdump serve per esaminare i file binari compilati
- Gdb è un debugger
EAX: accumulatore ECX: contatore EDX: dati EBX: base EIP: puntatore alla istruzione successiva ESP: puntatore allo stack EBP: puntatore base ESI: indice di origine EDI: indice di destinazione
EIP: è il puntatore di istruzione,che punta all'istruzione che
il processore sta leggendo in quel momento
EFLAGS : sono diversi flag a bit che vengono utilizzati per i
confronti e le segmentazioni della memoria
Per effettuare le interrogazioni useremo il comando x, examine, specificando anche come visualizzare l’output del registro esaminato:
x/x = esamina in esadecimale x/o = esamina in ottale x/u = esamina in decimale senza segno x/d = esamina in decimale x/i = esamina in binario
Digitando un numero subito dopo lo slash è possibile specificare quanti byte interrogare.
Analizziamo ora il nostro primo programma in C firstprog.c
Questo programma itera per 10 volte di seguito la stringa “Hello world”. Procediamo con la compilazione:
Ora passiamo all’analisi con gdb:
Proseguiamo disassemblando il main e dando una prima occhiata alla memoria:
la sintassi:
<locazione della memoria> (in esadecimale) ,<operazione>, <destinazione> , <origine>
In particolare analizziamo il registro l’eip, il quale punta alla prossima istruzione da eseguire, analizzando la memoria sulla quale sta puntando. Entrambi i comandi mostrano lo stesso output proprio perchè effettivamente stiamo effettuando la stessa richiesta.
Inoltre aggiugendo il numero 6 prima della i di instruction possiamo vedere le 6 successive istruzioni.
A questo punto si vede come al registro puntato di eip c’è una “DWORD PTR [ebp-0x8],0×0“. Tale operazione significa che il valore zero viene allocato alla locazione ebp – 8 ossia 0xbfff7d10 che al momento contiene 0xb7febf50:
Nella successiva locazione di memoria, ossia all’indirizzo 0x80483dc è presente un salto incondizionato jmp alla locazione 0x80483ee. Verifichiamo, proseguendo di una istruzione nexti, che l’eip successivo sia proprio quello:
Infatti, ora l’eip contiene un salto incondizionato jmp.
Guardiamo adesso le successive 10 istruzioni:
Ill ciclo for : viene fatto un salto incondizionato sulla locazione 0x80483ee, nella quale viene effettuata una compare cmp, la quale dice che se il il numero presente alla locazione ebp – 8, dove precedentemente era stato inizializzato 0, è minore o uguale di 9 allora esegue l’istruzione successiva jle che risulta essere apppunto un salto condizionato, proprio per via della compare, all’istruzione 0x80483de. Siccome risulta vera, 0 è minore o uguale di 9, rinizia il ciclo.
Nel momento in cui risulterà falsa, ossia ebp – 8 vale 10, allora non effettuerà il salto e continuerà con l’istruzione successiva.
Ora controlliamo proprio che ebp – 8 sia 0 e che quindi jle faccia il salto condizionato
Sia con il metodo print che analizzando con x/x direttamente sulla locazione di memoria, che con x/d lo visualizziamo in decimale, controlliamo che ha valore 0 e che quindi il jle ha condizione positiva per effettuare il salto.
Ora invece cerchiamo di capire cosa fanno le altre operazioni. Analizzando la locazione 0x80483de è presente un’istruzione che essenzialmente muove il valore dell’indirizzo 0x80484d0 nell’indirizzo esp. Verifichiamo quindi cosa contiene l’indirizzo 0x80484d0:
Notiamo che contiene il valore 0x6c6c6548 che codificato in ASCII corrisponde ad “Hello,” infatti tramite l’opzione c effettua la codifica di ogni singolo byte in ASCII mentre con s converte proprio tutta la stringa.
Per ultimo l’istruzione alla locazione 0x80483ea non fa altro che incrementare di uno il valore all’interno di ebp – 8, la quale poi viene poi controllata dal salto condizionato.
Giusto per completare il discorso, procediamo con diversi nexti fino alla fine del del ciclo for ossia quando l’ebp – 8 contiene il valore 10. Infine controlliamo che l’eip punta all’uscita del ciclo:
L’eip punta 0x80483f4 e non più 0x80483de come nei precedenti casi, confermando appunto l’uscita dal ciclo for.
Vedremo come è facile interrogare i registri nella cpu e capire come viene allocata la memoria
- Objdump serve per esaminare i file binari compilati
- Gdb è un debugger
EAX: accumulatore ECX: contatore EDX: dati EBX: base EIP: puntatore alla istruzione successiva ESP: puntatore allo stack EBP: puntatore base ESI: indice di origine EDI: indice di destinazione
EIP: è il puntatore di istruzione,che punta all'istruzione che
il processore sta leggendo in quel momento
EFLAGS : sono diversi flag a bit che vengono utilizzati per i
confronti e le segmentazioni della memoria
Per effettuare le interrogazioni useremo il comando x, examine, specificando anche come visualizzare l’output del registro esaminato:
x/x = esamina in esadecimale x/o = esamina in ottale x/u = esamina in decimale senza segno x/d = esamina in decimale x/i = esamina in binario
Digitando un numero subito dopo lo slash è possibile specificare quanti byte interrogare.
Analizziamo ora il nostro primo programma in C firstprog.c
PHP:
#include int main() { int i; for (i=0; i < 10; i++) { printf("Hello, world!\n"); } return 0; }
PHP:
# gcc -g firstprog.c -o firstprog
Ora passiamo all’analisi con gdb:
PHP:
# gdb -q ./firstprog (gdb) set dis intel (gdb) list 1 #include 2 3 int main() 4 { 5 int i; 6 for (i=0; i < 10; i++) 7 { 8 printf("Hello, world!\n"); 9 } 10 return 0; (gdb)
Proseguiamo disassemblando il main e dando una prima occhiata alla memoria:
PHP:
(gdb) disassemble main Dump of assembler code for function main: 0x080483c4 : lea ecx,[esp+0x4] 0x080483c8 : and esp,0xfffffff0 0x080483cb : push DWORD PTR [ecx-0x4] 0x080483ce : push ebp 0x080483cf : mov ebp,esp 0x080483d1 : push ecx 0x080483d2 : sub esp,0x14 0x080483d5 : mov DWORD PTR [ebp-0x8],0x0 0x080483dc : jmp 0x80483ee 0x080483de : mov DWORD PTR [esp],0x80484d0 0x080483e5 : call 0x80482f4 0x080483ea : add DWORD PTR [ebp-0x8],0x1 0x080483ee : cmp DWORD PTR [ebp-0x8],0x9 0x080483f2 : jle 0x80483de 0x080483f4 : mov eax,0x0 0x080483f9 : add esp,0x14 0x080483fc : pop ecx 0x080483fd : pop ebp 0x080483fe : lea esp,[ecx-0x4] 0x08048401 : ret End of assembler dump.
<locazione della memoria> (in esadecimale) ,<operazione>, <destinazione> , <origine>
PHP:
(gdb) break main: Breakpoint 1 at 0x80483d5: file firstprog.c, line 6. (gdb) run Starting program: ./firstprog Breakpoint 1, main () at firstprog.c:6 6 for (i=0; i < 10; i++) (gdb) i r eax 0xbfe3fc04 -1075577852 ecx 0xbfe3fb80 -1075577984 edx 0x1 1 ebx 0xb800bff4 -1207910412 esp 0xbfe3fb50 0xbfe3fb50 ebp 0xbfe3fb68 0xbfe3fb68 esi 0x8048420 134513696 edi 0x8048310 134513424 eip 0x80483d5 0x80483d5 eflags 0x200286 [ PF SF IF ID ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 (gdb) i r eip eip 0x80483d5 0x80483d5
In particolare analizziamo il registro l’eip, il quale punta alla prossima istruzione da eseguire, analizzando la memoria sulla quale sta puntando. Entrambi i comandi mostrano lo stesso output proprio perchè effettivamente stiamo effettuando la stessa richiesta.
PHP:
(gdb) x/x 0x80483d5 0x80483d5 : 0x00f845c7 (gdb) x/x $eip 0x80483d5 : 0x00f845c7 (gdb) x/i $eip 0x80483d5 : mov DWORD PTR [ebp-0x8],0x0
PHP:
(gdb) x/6i $eip 0x80483d5 : mov DWORD PTR [ebp-0x8],0x0 0x80483dc : jmp 0x80483ee 0x80483de : mov DWORD PTR [esp],0x80484d0 0x80483e5 : call 0x80482f4 0x80483ea : add DWORD PTR [ebp-0x8],0x1 0x80483ee : cmp DWORD PTR [ebp-0x8],0x9
PHP:
(gdb) i r ebp ebp 0xbfff7d18 0xbfff7d18 (gdb) x/x $ebp - 8 0xbfff7d10: 0xb7febf50
PHP:
(gdb) nexti 0x080483dc 6 for (i=0; i < 10; i++) (gdb) i r eip eip 0x80483dc 0x80483dc (gdb) x/i $eip 0x80483dc : jmp 0x80483ee
Guardiamo adesso le successive 10 istruzioni:
PHP:
(gdb) x/10i $eip 0x80483dc : jmp 0x80483ee 0x80483de : mov DWORD PTR [esp],0x80484d0 0x80483e5 : call 0x80482f4 0x80483ea : add DWORD PTR [ebp-0x8],0x1 0x80483ee : cmp DWORD PTR [ebp-0x8],0x9 0x80483f2 : jle 0x80483de 0x80483f4 : mov eax,0x0 0x80483f9 : add esp,0x14 0x80483fc : pop ecx 0x80483fd : pop ebp
Nel momento in cui risulterà falsa, ossia ebp – 8 vale 10, allora non effettuerà il salto e continuerà con l’istruzione successiva.
Ora controlliamo proprio che ebp – 8 sia 0 e che quindi jle faccia il salto condizionato
PHP:
(gdb) x/i $ebp - 8 0xbfff7d10: add BYTE PTR [eax],al (gdb) x/x 0xbfff7d10 0xbfff7d10: 0x00000000 (gdb) x/d 0xbfff7d10 0xbfff7d10: 0 (gdb) print $ebp - 8 $2 = (void *) 0xbfff7d10 (gdb) x/x $2 0xbfff7d10: 0x00000000 (gdb) x/d $2 0xbfff7d10: 0
Ora invece cerchiamo di capire cosa fanno le altre operazioni. Analizzando la locazione 0x80483de è presente un’istruzione che essenzialmente muove il valore dell’indirizzo 0x80484d0 nell’indirizzo esp. Verifichiamo quindi cosa contiene l’indirizzo 0x80484d0:
PHP:
(gdb) x/x 0x80484d0 0x80484d0: 0x6c6c6548 (gdb) x/6cb 0x80484d0 0x80484d0: 72 'H' 101 'e' 108 'l' 108 'l' 111 'o' 44 ',' (gdb) x/s 0x80484d0 0x80484d0: "Hello, world!"
Per ultimo l’istruzione alla locazione 0x80483ea non fa altro che incrementare di uno il valore all’interno di ebp – 8, la quale poi viene poi controllata dal salto condizionato.
Giusto per completare il discorso, procediamo con diversi nexti fino alla fine del del ciclo for ossia quando l’ebp – 8 contiene il valore 10. Infine controlliamo che l’eip punta all’uscita del ciclo:
PHP:
(gdb) x/d 0xbfff7d10 0xbfff7d10: 10 (gdb) i r eip eip 0x80483ee 0x80483ee (gdb) x/10i $eip 0x80483ee : cmp DWORD PTR [ebp-0x8],0x9 0x80483f2 : jle 0x80483de 0x80483f4 : mov eax,0x0 0x80483f9 : add esp,0x14 0x80483fc : pop ecx 0x80483fd : pop ebp 0x80483fe : lea esp,[ecx-0x4] 0x8048401 : ret 0x8048402: nop 0x8048403: nop (gdb) nexti 0x080483f2 6 for (i=0; i < 10; i++) (gdb) i r eip eip 0x80483f2 0x80483f2 (gdb) nexti 10 return 0; (gdb) i r eip eip 0x80483f4 0x80483f4