© GnomixLand
http://www.gnomixland.com/
http://www.gnomixland.com/
![]()
-----
Intro
-----
Ciao, eccomi di nuovo, questa volta vi metterò a conoscenza di cos'e' in effetti
il buffer overflow e con potete scoprire se qualche programma e' vulnerabile agli
exploit di buffer overflow. Questo tutorial a codice sorgente C, quindi se non conoscete
il C potrete avere qualche problema con questo tutorial, inoltre dovete avere qualche
nozione di ASM (ndt Assembler) e su come usare gdb.
Ho provato a farlo il piu' semplice possibile, ma questo tutorial lo stesso non e'
uno di quelli dove non impari niente di niente e quando l'hai finito sai tutto. Questo
tutorial ha bisogno di qualche sforzo per essere compreso, hey mi e' costato molto
lavoro scriverlo!
Una piccola note, come chiunque stia leggendo queste righe a me piace imparare, cosi
alcune settimane fa' mi sono detto "Hey che cacchio, perche' non incomincio a leggere
qualcosa riguardo i buffer overflow, io so come il tutto funziona ma solo superficialmente
", cosi ho semplicemente cominciato ad imparare ed ora cerco di passare le conoscenze
che ho acquisito, a chiunque fosse interessato. Quindi questo non sara' uno di quei testi
dove imparerete tutto, sara' piu' come una prova generale, come dice il titolo un'
Introduzione , (alla fine vi daro' qualche testo interessante). Se avete quanche domanda
su questo tutorial postatela nel nostro message board, se trovate qualche "bug" in
questo tutorial scrivetemi ed io lo correggero'.
Buon divertimento.
Exploit?
--------
Bene, forse tutti sanno cosa e' un exploit. Ma dovete ancora vedere che quelli che
entrano nel mondo della sicurezza (informatica) per la prima volta probabilmente
non hanno la minima idea di cosa sia, per questo ho scritto questa piccola sezione.
Per quelli che non sanno, un exploit e' un programma, di solito scritto in C,
che sfrutta alcuni problemi di altri programmi. L'exploit vi permettera' di eseguire
codice arbitrario che vi permettera' di fare qualcosa che non dovreste poter fare
in condizioni normali del sistema.
Oggi, la maggior parte degli exploit sono cio' che chiamiamo Buffer Overflow Exploits.
What's that you ask. Aspettate un po', perche' ci arriveremo. Dopo tutto, questo e'
l'oggetto di questo tutorial.
Un'altra cosa che dovreste sapere e' che chiunque sa come usarli(come credete
facciano a deturpare la maggior parte dei siti web?), gli "script kiddies" [1*] non
fanno altro che andare in siti come security focus, packetstorm o fyodor's exploit
world, scaricarli e farli partire, per poi essere presi. Ma perche' non tutti
scrivono exploit? Il problema e' che molti non sanno come sfuttare alcune "faglie"
nel codice sorgente, o anche se possono non sanno scrivere un exploit. Allora, ora
che avete un idea di cosa sia un exploit, proseguiamo nella sezione Buffer Overflow.
Dopo tutto, cos'e' il Buffer Overflow?
--------------------------------------
Come ho detto prima, la maggior parte degli exploit sono Exploit di Buffer Overflow.
Starete ora pensando "Bah.. sto tipo sta sparando cose a destra e a manca, ma ancora
non ha detto cosa e' un buffer overflow". Ora ne parliamo.
Un problema da buffer overflow e' situato nella memoria dove il programma conserva
i suoi dati. Perche', direte voi. Perche' cio' che un buffer overflow fa' e'
sovrascrivere specifiche aree delle memoria con cio' che volete, e cio' fara' fare al
programma cio' che volete.
Eheh, ora qualcuno di voi stara' pensando "Uao, So come funziona un buffer overflow",
ma ancora non sapete come sfruttarlo.
Eccovi un programma, cerchiamo di trovare e aggiustare il buffer overflow
------ Inizio del codice --------
main(int argc, char **argv) {
char *somevar;
char *important;
somevar = (char *)malloc(sizeof(char)*4);
important = (char *)malloc(sizeof(char)*14);
strcpy(important, "command"); /*Questa e' la variabile
'importante'*/
stcrpy(somevar, argv[1]);
..... Code here ....
}
.... Other functions here ....
------- Fine ------
La variabile imporant conserva qualche comando di sistema, per esempio "chmod o-r file",
e dal momento che 'file' appartiene a root e il programma viene eseguito da root, questo
significa che se potete inviargli dei comandi potrete eseguire qualsiasi comando di
sistema. Cosi iniziate a pensare. Come posso mettere cio' che voglio nella variabile
'important' . Bisogna mandare la memoria in overflow. Ora vediamo gli indirizzi di memoria
delle variabile. Per fare cio' dovrete riscrivere il codice. Controllate il codice seguente.
--------- Inizio del codice ------------
main (int argc, char **argv) {
char *somevar;
char *important;
somevar=(char *)malloc(sizeof(char)*4);
important=(char *)malloc(sizeof(char)*14);
printf("%pn%p", somevar, important);
exit(0);
segue altro codice....
......
....
}
--------- Fine --------
Non abbiamo fatto altro che aggiungere 2 righe al codice e lasciare intatto il resto.
Vediamo cosa fanno queste due righe.
La riga printf("%pn%p", somevar, important); stampa l'indirizzo di memoria di
'somevar' e 'important'. exit(0); lascera' che il resto del programma continui, in effetti
il nostro scopo era quello di sapere dove le variabili fossero conservate in memoria.
Una volta eseguito il programma otterrete qualcosa del genere, anche se molto
probabilmente gli indirizzi saranno differenti:
0x8049700 <----- Questo e' l'indirizzo di 'somevar'
0x8049710 <----- Questo e' l'indirizzo di 'important'
Come potete vedere, la variabile 'important' e' situata accanto 'somevar', questo ci
permettera' di usare le nostre capacita' con il buffer overflow, visto che 'somevar'
viene prese da argv[1]. Ora che sappiamo che una variabile segue l'altra, controlliamo
ogni indirizzo di memoria in modo da avere un'idea piu' precisa della conservazione
in memoria dei dati. Per fare cio' dovremo riscrivere ancora una volta il codice.
-------- Inizio del codice ---------
main(int argc, char **argv) {
char *somevar;
char *important;
char *temp; /* abbiamo bisogno di quest'altra nuova variabile */
somevar=(char *)malloc(sizeof(char)*4);
important=(char *)malloc(sizeof(char)*14);
strcpy(important, "command"); /*Questa e' la variabile
'importante'*/
strcpy(somevar, argv[1]);
printf("%pn%pn", somevar, important);
printf("Starting To Print memory address:n");
temp = somevar; /* Questo fara puntare temp all'inizio dell'aria di memoria
di 'somevar' */
while(temp < important + 14) {
/* Questo loop sara' interrotto quando avremo raggiunto la fine dell'aria di
memoria riservata alle variabili */
printf("%p: %c (0x%x)n", temp, *temp, *(unsigned int*)temp);
temp++;
}
exit(0);
rest of code here
}
------ Fine ------
Mettiamo per esempio ch e argv[1] in casi normali debba essere send. Digitate al
prompt:
$ nome_programma send
Otterrete qualcosa del genere:
0x8049700
0x8049710
Starting To Print memory address:
0x8049700: s (0x616c62)
0x8049701: e (0x616c)
0x8049702: n (0x61) <---- Ognuna di queste righe rappresenta un indirizzo di memoria
0x8049703: d (0x0)
0x8049704: (0x0)
0x8049705: (0x0)
0x8049706: (0x0)
0x8049707: (0x0)
0x8049708: (0x0)
0x8049709: (0x19000000)
0x804970a: (0x190000)
0x804970b: (0x1900)
0x804970c: (0x19)
0x804970d: (0x63000000)
0x804970e: (0x6f630000)
0x804970f: (0x6d6f6300)
0x8049710: c (0x6d6d6f63)
0x8049711: o (0x616d6d6f)
0x8049712: m (0x6e616d6d)
0x8049713: m (0x646e616d)
0x8049714: a (0x646e61)
0x8049715: n (0x646e)
0x8049716: d (0x64)
0x8049717: (0x0)
0x8049718: (0x0)
0x8049719: (0x0)
0x804971a: (0x0)
0x804971b: (0x0)
0x804971c: (0x0)
0x804971d: (0x0)
$
Bello, vero? Vedete che esistono 12 indirizzi di memoria vuoti tra 'somevar' e
'important' ? Ora mettiamo il caso che eseguiate il programma con un linea di comando
tipo questa:
$ nome_programma send------------newcommand
Avrete qualcosa tipo:
0x8049700
0x8049710
Starting To Print memory address:
0x8049700: s (0x646e6573)
0x8049701: e (0x2d646e65)
0x8049702: n (0x2d2d646e)
0x8049703: d (0x2d2d2d64)
0x8049704: - (0x2d2d2d2d)
0x8049705: - (0x2d2d2d2d)
0x8049706: - (0x2d2d2d2d)
0x8049707: - (0x2d2d2d2d)
0x8049708: - (0x2d2d2d2d)
0x8049709: - (0x2d2d2d2d)
0x804970a: - (0x2d2d2d2d)
0x804970b: - (0x2d2d2d2d)
0x804970c: - (0x2d2d2d2d)
0x804970d: - (0x6e2d2d2d)
0x804970e: - (0x656e2d2d)
0x804970f: - (0x77656e2d)
0x8049710: n (0x6377656e) <--- memory address where important variable starts
0x8049711: e (0x6f637765)
0x8049712: w (0x6d6f6377)
0x8049713: c (0x6d6d6f63)
0x8049714: o (0x616d6d6f)
0x8049715: m (0x6e616d6d)
0x8049716: m (0x646e616d)
0x8049717: a (0x646e61)
0x8049718: n (0x646e)
0x8049719: d (0x64)
0x804971a: (0x0)
0x804971b: (0x0)
0x804971c: (0x0)
0x804971d: (0x0)
newcommand ha sovrascritto command. Ora fa cio' che voi volete, inveci di cio' che
era prestabilito.
NOTA: Ricordate sempre che lo spazio tra 'somevar' e 'important' puo' contenere altre
variabili invece di essere vuoto, percio' controllate il loro valore e fate in modo
che arrivino allo stesso indirizzo, o il programma molto probabilmente si blocchera'
prima di arrivare alla variabile che avete modificato.
Ora riflettiamo un po'. Perche' cio' accade? Come potete vedere nel sorgente 'somevar'
e' dichirato prima di important, e cio' fara' si', la maggior parte delle volte, che
'somevar' stia prima in memoria. Ora controlliamo come ogni variabile viene assegnata.
'somevar' prende il suo valore da argv[1], e 'important' dalla funzione strcpy(), ma
il vero problema e' che il valore di 'important' viene assegnato per primo quindi
quando assegnate un valore a 'somevar' che e' posto prima di 'important', quest'ultimo
viene sovrascritto. Il programma puo' essere corretto da questo buffer overflow invertendo
queste due righe:
strcpy(somevar, argv[1]);
strcpy(important, "command");
Se il programma fosse stato scritto cosi, anche se voi avreste scritto nella linea di
comando un argomento che avrebbe sovrascritto l'area di memoria di 'important', questa
sarebbe stata ri-sovrascritta dal vero comando, visto che dopo l'assegnazione di un
valore a 'somevar', il programma assegna il valore command a 'important'.
Questo tipo e' un buffer overflow di memoria (heap). Come avrete sicuramente notato
in teoria sono veramente semplici ma, nel mondo reale, non e' cosi semplice trovarli,
dopo tutto l'esempio che vi ho data non era un programma stupido? E' davvero difficile
trovare le variabili 'important', ed anche per fare l'overflow di questa variabile
dovrete poter scrivere in una variabile posto in un'area di memoria inferiore, ma la
maggior parte delle volte queste condizioni non capitano contemporaneamente.
Per questo vi introdurro' ai Buffer overflow dello stack (o delle pile).
Una piccola annotazione:
------------------------
Nell'ultimo paragrafo ho parlato di memoria(heap) e stack. Probabilmente vi starete
chiedendo cosi siano. Per la vostra fame di conoscenza, eccovi una breve e semplice
definizione di ognuno di essi:
memoria (heap) - E' lo spazio che riservate alle variabili (accedete a quest'area della
memoria con la funzione malloc() ).
stack - E' il posto dove vengono spinti o dove vengono ritornati valori da una funzione.
Quando provate a fare un overflow su uno stack dovrete provare a cambiare l'indirizzo
di ritorno, facendo saltare il codice fino al posto dove dovete inserire i comandi che
volete siano eseguiti.
Incominciamo a parlare dello stack. Qui incomincia la parte che mi ha dato, e ancora
mi da', molti problemi. Dovete conoscere un po' di ASM, come maneggiare gdb (credetimi
diverra' uno dei vostri migliori amici), e non dovrete arrendervi.
Vediamo ora come "Mandare in frantumi lo stack", un tipo di "attacco" che
cambia l'indirizzo di ritorno (RET). Facendo cio' potrete far ritornare la funzione
ad un indirizzo dove sono gia' allocati dei comandi che volete siano eseguiti.
Come in un overflow della memoria, vediamo un po' di codice sorgente.
------ Inizio del codice ------
/* Stack Overflow example */
exploit(char *this) {
char string[20];
strcpy(string,this);
printf("%sn", string);
}
main(int argc, char *argv[]) {
exploit(argv[1]);
}
------ Fine -----
Ora proveremo a chiamare duo volte la funzione exploit(). Come possiamo farlo?
Per prima cosa abbiamo bisogno di trovare degli indirizzi. Questa volta useremo
gdb. Per prima cosa compiliamo.
$ gcc stack.c -o stack
$ gdb stack
GNU gdb 4.18
Copyright 1998 Free Software Foundation, Inc.
GDB e' un programma libero, coperto dalla GNU General Public License, e siete
liberi di cambiarlo e/o ridistribuirne copie, purche' rispettiate alcune condizioni.
Scrivete "show copying" per vedere le condizioni.
Non ci sono garanzie per GDB. Digitate "show warranty" per i dettagli.
Questo GDB e' stato configurato come "i586-suse-linux-gnu"...
(gdb)
Questo e' il vostro prompt, ora disassembleremo main. Per fare cio' digitare
disassemble (oppure disas) main , difficile vero'?
(gdb) disas main
Dump of assembler code for function main:
0x8048440 |