Il buffer overflow è forse l'incubo di ogni programmatore C, e come se non bastasse negli ultimi tempi è salito alla ribalta per decine di virus che sfruttano questa vulnerabilità. Ma perchè si verifica?
Il motivo è molto semplice: il C alloca tutte le variabili in cima allo stack, e al di sotto di esse vengono allocate le serie di istruzioni. Durante un attacco di stack smashing determinate variabili vengono "prese di mira" e riempite oltre la loro capacità effettiva, per sconfinare nella parte dello stack riservata alle istruzioni; a questo punto è facile intuire che una volta sconfinati in questi settori si può inseire del codice maligno che verrà eseguito normalmente dalla macchina.
Faccio un esempio:
#include
main()
{
char str[20];
cout << "Immetti il tuo nome: ";
cin >> str;
cout << "Benvenuto: " << str;
return 0;
}
gets(), strcpy(), scanf() e cin sono solo tre delle istruzioni che sono soggette ad un buffer overflow: nel nostro esempio si fa uso proprio di cin. Vediamo cosa succede: compilando il listato ed eseguendolo vediamo che inserendo il proprio nome il programma si comporta normalmente, restituendo esattamente ciò che ci aspettiamo; ma provate ad inserire una stringa lunga circa 20 caratteri ed ammirate il risultato... Il programma termina senza restituire nessuna stringa. Bravi, avete appena effettuato il vostro primo stack smashing.
Essendo questo un buffer overflow a tutti gli effetti, se inserirete delle istruzioni dopo la stringa queste verranno eseguite.
Avrete anche notato che praticamente tutti i programmi possono essere soggetti a questi tipi di attacchi; ma immaginate cosa potrebbe accadere se il problema si verificasse su un server FTP o un server web...
A questo punto molti di voi si staranno chiedendo se esiste un modo per difendersi da uno stack smashing. Certo che c'è, e non ne esiste solo uno.
Il primo metodo, rivolto ai programmatori, è usare dei compilatori che implementino un controllo dello stack, come SSP o Stackguard per GCC; se invece non siamo programmatori o il sorgente non è disponibile possiamo optare per delle soluzioni a runtime dinamico, come Libsafe.
Tutte le soluzioni tramite compilatore operano quasi nella stessa maniera, utilizzando cioè un "canarino". No, no... niente a che fare con i piccoli volatili gialli... Normalmente è un valore casuale (per intenderci, una sorta di mappatura) che viene inserito nello stack; si chiama anche flag, ma "canarino" è molto più poetico. Se durante l'esecuzione del programma il flag viene modificato o è mancante il programma termina. Inutile dire che questa soluzione colpisce al cuore del problema, ma come abbiamo detto non sempre è possibile.
In questi casi viene in nostro aiuto Libsafe, sostituendo molte funzioni standard che presentano questo problema. Libsafe inoltre tenta di calcolare la dimensione massima di un buffer allocato staticamente, utilizzando una funzione inclusa in GCC che restituisce l'indirizzo del puntatore del frame: solitamente è la prima porzione dello stack che segue le variabili, quindi se si tenta di scrivere oltre la dimensione prevista il programma viene terminato.
Ma, ahimè, Libsafe non può fare miracoli, dato che non può lavorare su binari SUID senza un collegamento statico, nè su programmi che vengono compilati con l'opzione "-fomit-frame-pointer", senza contare che non è possibile calcolare esattamente la porzione di un buffer, ma al massimo limitare la differenza fra l'inizio del buffer e il puntatore del frame.
In ogni caso, almeno per i sistemi Unix, prendete in considerazione l'idea di installare Grsecurity, che fra le altre cose rileva gli attacchi di tipo buffer overflow.