GDB, o GNU Project debugger, è un software sviluppato dal progetto GNU, e da la possibilità di analizzare numerosi eseguibili, come C, C++, Go e molti altri.
Esso ti permette di analizzare un programma durante la sua esecuzione e di:
In questo articolo andremo ad analizzare le principali funzioni, cercando anche di capire cosa avviene a livello di istruzioni e codice.
Attenzione: per questo articolo consiglio delle basi di programmazione C e qualche nozione di assembly (registri, puntatori, indirizzi di memoria).
Come primo esempio utilizzerò il classico Ciao Mondo, scritto in C e definito come
#include
int main()
{
int i;
for(i=0;i<10;i++)
{
printf("Ciao mondo!\n");
}
}
Per compilarlo e poter visionare il codice sorgente anche in gdb, il comando da eseguire sarà:
gcc -g -o HelloWorld HelloWorld.c
Ora possiamo eseguire gdb, passando come argomento l’eseguibile appena creato:
gdb HelloWorld
e per eseguire il programma in gbd il comando sarà run.
Le righe iniziali potete sopprimerle passando l’argomento -q (quiet), in modo che non stampi ogni volta la descrizione del software.
Quando compiliamo con l’opzione -g avremo la possibilità di vedere il codice sorgente del programma creato con il comando list. Ovviamente se avremo solo l’eseguibile, non sarà possibile
Ora che abbiamo visto che funziona anche in gdb, iniziamo a decompilare ed estrarre qualche informazione iniziale. Uno dei comandi più utili è il breakpoint, il quale ci darà la possibilità di fermare il programma sull’istruzione o riga prescelta e analizzare i registri presenti al momento del break.
Inserisco il breakpoint sul main (dove il programma sta per iniziare) e eseguo nuovamente il codice.
(gdb) break main
Breakpoint 1 at 0x5ad: file HelloWorld.c, line 6.
(gdb) run
Starting program: /home/mrtouch/Desktop/gdb/HelloWorld
Breakpoint 1, main () at HelloWorld.c:6
6 for(i=0;i<10;i++)
Ed ecco che si è fermato, dandoci la possibilità di analizzare i registri o continuarne l’esecuzione.
Piccola precisazione prima di iniziare a disassemblare. GDB disassembla con la sintassi di AT&T, ma c’è anche la possibilità di utilizzare la sintassi Intel. Personalmente preferisco la seconda, più semplice da capire, ed è quello che utilizzerò nell’articolo. I comandi e le istruzioni non cambiano, saranno solamente stampate in maniera differente. Per impostare la sintassi Intel, basta digitare in gdb
set disassembly-flavor intel
In tutti e due i casi, dove è presente la freccia è posizionato il breakpoint e dove siamo di conseguenza posizionati noi. Le istruzioni precedenti sono definite come prologo di funzione e sono generate dal compilatore per impostare la memoria delle variabili statiche.
Un altro comando essenziale in gdb è _x _(examine), il quale da la possibilità di esaminare un registro, una variabile o qualsiasi altra informazione passata per argomento. È possibile definire il formato con cui ricevere l’informazione:
Nel caso volessimo esaminare più valori della variabile passata per argomenti, possiamo aggiungere il numero degli stessi con lo stesso comando. Proviamo, ad esempio, ad analizzare il registro EIP, il quale contiene l’indirizzo di memoria che punta all’istruzione successiva.
(gdb) x/x $eip
0x800005ad <main+29>: 0xc7 ;punta al breakpoint inserito da noi
(gdb) x/x 0x800005ad ;codice HEX di EIP, non c'è differenza
0x800005ad <main+29>: 0xc7
(gdb) x/i 0x800005ad
=> 0x800005ad <main+29>: mov DWORD PTR [ebp-0xc],0x0
(gdb) x/6x 0x800005ad ;continuo di eip
0x800005ad <main+29>: 0xc7 0x45 0xf4 0x00 0x00 0x00
(gdb) x/xw 0x800005ad ;eip scritto su una singola word
0x800005ad <main+29>: 0x00f445c7
Nell’ultima riga ho inserito un nuovo comando, ossia la stampa della word intera. Nel caso volessimo avere grandezze differenti, le lettere sono:
La penultima e ultima riga sono uguali, ma rappresentate secondo l’ordine _little-endian, _in quanto sono eseguiti su un calcolatore x86. Con questa modalità, viene preso l’ultimo bytes (00), il penultimo (f4), gli viene aggiunto _45 ed infine c7, per formare 00_f445c7, appunto il codice stampato nell’ultima riga. Per una più esaustiva spiegazione, Big & Little Endian Order.
Ora che abbiamo visto come esaminare le variabili, passiamo ad analizzare il codice. Dove abbiamo posizionato il break, è presente l’istruzione mov DWORD PTR [ebp-0xc],0x0, la quale copia il valore 0 in epb-0xc, ossia dove la variabile i del ciclo for sarà posizionata. Esamino quindi ebp con il comando info register , abbreviato in i r
(gdb) i r ebp
ebp 0xbffff2e8 0xbffff2e8
(gdb) x/x $ebp
0xbffff2e8: 0x00000000
(gdb) x/x $ebp-0xc
0xbffff2dc: 0x80000611
(gdb) print $ebp-0xc
$1 = (void *) 0xbffff2dc
L’ultimo comando permette di stampare la locazione di memoria del registro. Visto che per ora, non abbiamo rilevato nessuna informazione utile, passiamo alla successiva istruzione con il comando nexti.
(gdb) i r eip
eip 0x800005b4 0x800005b4 <main+36>
(gdb) x/i $eip
=> 0x800005b4 <main+36>: jmp 0x800005cc <main+60>
(gdb) x/2i main+60
0x800005cc <main+60>: cmp DWORD PTR [ebp-0xc],0x9
0x800005d0 <main+64>: jle 0x800005b6 <main+38>
EIP effettua ora un jump all’istruzione 60, la quale eseguirà una comparazione tra il valore di i e 9. Nel caso sia positivo (riga successiva) tornerà alla riga 38. Proseguo con nexti fino ad arrivare all’istruzione in cui viene stampata la stringa
(gdb) nexti
0x800005bf 8 printf("Ciao mondo!\n");
(gdb) x/4i $eip
=> 0x800005bf <main+47>: push eax
0x800005c0 <main+48>: call 0x800003f0 <puts@plt>
0x800005c5 <main+53>: add esp,0x10
0x800005c8 <main+56>: add DWORD PTR [ebp-0xc],0x1
Noto che viene aggiunto il registro EAX (con push) e poi stampata (puts) la stringa. Visiono quindi il registro ed ecco che contiene l’output del programma
Ricordo che per ogni architettura è diverso. Potreste avere altri registri in uso e le istruzioni potrebbero essere invertite. Serve solo un pò di pazienza (almeno all’inizio) e voglia di imparare.
GDB, come ogni disassemblatore o debugger, ha infinite altre opzioni, e ogni eseguibile è chiaramente un mondo a sè stante. Scriverò altri articoli per capire meglio il software e soprattutto il disassembling, per poi passare al Reverse Engineering con conseguenti esercizi. Per chi volesse approfondire autonomamente, consiglio il riassunto di gdb e le docs online.