Last update Ottobre 17, 2018 10:59 AM

 

drmlog  


Personal Website


Articoli / Articles
     

Pagina Iniziale Home Page

Informazioni About Me

Curriculum Vitae Resume
Didattica Teaching
Guide Tutorials
Articoli Articles
Libri Books

Contatti Contacts
     
 
     
 

Introduzione alla tecnica del Buffer Overflow
di Roberto Saia

Tratto dal libro "Reti e sicurezza per sistemi Windows e Linux/Unix" edizioni FAG Milano, collana Pro DigitalLifeStyle

La tecnica denominata Buffer Overflow, che nella nostra lingua potrebbe essere tradotta come trabocco del Buffer, è basata sullo sfruttamento di un certo tipo di vulnerabilità presente in alcuni Software: la vulnerabilità in questione è costituita dalla mancata verifica della dimensione dei dati in ingresso.

In altre parole, i Software potenzialmente vulnerabili a questa tecnica non operano un corretto controllo sui dati in arrivo, limitandosi semplicemente a trasferirli in un apposito Buffer: in questo caso, qualora il mittente delle informazioni non operi un preventivo controllo su ciò che invia, le dimensioni dei dati potrebbero essere superiori alle dimensioni del Buffer allocato, ingenerando, appunto, una situazione di Buffer Overflow.

Nota: il termine Buffer identifica una particolare area di memoria usata per la custodia temporanea dei dati o, più dettagliatamente, un insieme di locazioni contigue di memoria contenenti più istanze relative allo stesso tipo di dato.

Nel seguente esempio minimale, realizzato in linguaggio C, è possibile osservare, in pratica, il significato di quanto appena detto:

int main()
{
int x;
char mybuffer2[5];
char mybuffer1[10];
for(x=0; x<15; x++) mybuffer1[x] = 'Y';
return 0;
}

In questo semplice esempio, il Buffer mybuffer1, dimensionato in modo da contenere al massimo dieci elementi, ne riceverà quindici e quello che si verrà a creare sarà, appunto, un Buffer Overflow che causerà uno sconfinamento di cinque elementi nello spazio riservato al Buffer mybuffer2.

img1
Figura 1 - Sconfinamento nell'area riservata a Mybuffer2.

La riscrittura di altre aree di memoria, almeno quando essa è causata da un evento casuale, genera al programma e/o al sistema intero degli effetti imprevedibili. In questo caso, dato che ci troviamo d'innanzi ad un preciso disegno messo in atto da qualcuno, qualcuno che conosce bene l'ambiente nel quale sta operando, le cose sono ben differenti e gli effetti che ne conseguiranno non saranno per nulla imprevedibili.

Se l'attacco messo in atto avrà successo, il risultato sarà la conquista parziale o totale al sistema che ospita il Software nel quale è stato causato l'Overflow: l'esito dell'operazione è strettamente subordinato ai privilegi posseduti da questo Software al momento dell'attacco, in quanto, saranno i privilegi che verranno trasferiti all'aggressore; è ovvio che per una completa riuscita è necessario assicurarsi i privilegi di amministratore (quelli attribuiti a Root in Linux o Administrator in ambiente Windows) o equivalenti.

Con modalità simili a quelle appena viste per il Buffer Overflow opera la tecnica denominata Heap Overflow. L'unica sostanziale differenza è rappresentata dal fatto che, a differenza di quando avveniva nel Buffer Overflow, in questo caso, l'area interessata dall'attacco è quella dello Heap, una particolare area di memoria ad allocazione dinamica a tempo di Run Time dedicata all'immagazzinamento di dati importanti (in precedenza, invece, era interessata un'area di memoria ad allocazione statica).

Nota: il termine Run Time identifica il periodo di esecuzione di un certo programma.

Anche in questo caso il risultato è identico, se il Software non è in grado di gestire correttamente i dati in ingresso è possibile procurare alla macchina un Heap Overflow, operazione che in molti casi può portare al controllo completo del programma e/o del sistema. L'utilizzo di questo tipo di tecniche, un tempo appannaggio esclusivo di gente molto esperta, si è oggi diffuso anche tra coloro che di conoscenze in materia ne hanno ben poche.

La ragione di questo allargamento è dovuto al fatto che sulla rete Internet sono facilmente reperibili sia istruzioni dettagliate su come condurre questo genere di attacchi che degli Exploit, liberamente scaricabili, già pronti all'uso. Risulta evidente, comunque, che non tutti i Software sono afflitti da questo genere di problema, ma solo quelli nei quali non è effettuato un corretto controllo preventivo sui dati in ingresso (cioè, in pratica, quelli che contengono errori di programmazione).

Per quest'ultima ragione, l'adozione di Software da tempo sul mercato, quindi, sufficientemente testati, può offrirci una ragionevole certezza di non essere vulnerabili verso questo genere di attacchi e, quindi, specialmente per gli ambienti di produzione, l'utilizzo esclusivo di questi Software rappresenta l'unica scelta da prendere in considerazione.

Per quanto concerne i possibili interventi da eseguire in fase di stesura del codice al fine di evitare che questo sia vulnerabile alle tecniche di Buffer Overflow, essi devono, fondamentalmente, porre rimedio a due tipi di problema: il Checking Boundaries ed il Format String.

Con il termine Checking Boundaries (controllo dei limiti) si identifica, appunto, quel controllo preventivo sui dati che ha lo scopo di evitare quel tipo di sconfinamento che sta alla base delle tecniche di Overflow. Sebbene alcuni tipi di linguaggi di programmazione prevedano un controllo implicito su questo (come, ad esempio, avviene in Java), altri linguaggi, solitamente di basso livello come, ad esempio, il C, non lo prevedono e, quindi, è necessario un intervento manuale del programmatore durante la scrittura del codice sorgente.

Un secondo problema è quello che fa capo ad una ben nota vulnerabilità denominata Format String la cui causa è da identificare nel cosiddetto Channeling Problem: quest'ultimo è un problema che si verifica quando sia le informazioni di controllo che i dati utilizzano lo stesso canale. Al fine di chiarire quanto appena detto, nel successivo esempio è possibile osservare il codice sorgente, scritto in C, relativo ad una funzione minimale afflitta dal problema del Format String:

int funzione(char *data)
{
printf(data);
}

La totale assenza di specificatori di formato nell'istruzione printf rende vulnerabile al problema questa porzione di codice che, per essere sicura, dovrebbe essere riscritta nel seguente modo:

int funzione(char *data)
{
printf("%s", data);
}

In questo caso, l'utilizzo dello specificatore di formato %s ci mette al riparo da un possibile utilizzo anomalo della funzione. Nel primo esempio, infatti, i dati in ingresso non sarebbero stati in alcun modo validati e questo avrebbe consentito ad un malintenzionato l'invio di una serie di specificatori di formato che avrebbero ingenerato un Buffer Overflow mirato ad assumere il controllo del sistema. Anche in questo caso, a seconda del linguaggio di programmazione adoperato, è richiesto un attento intervento dell'operatore per scongiurare questa vulnerabilità.

Nota: l'uso dell'istruzione Printf è qui fatto in modo didattico esclusivamente al fine di evidenziare la necessità di trattare in modo corretto i dati.

 

Sebbene il contenuto delle guide, seppur in forma sintetica e/o adattata al contesto, sia tratto dai miei libri/eBook, l'occasionalità e i rapidi tempi di stesura che ne caratterizzano la pubblicazione non garantiscono l'assenza di errori e/o omissioni.

Ritorna all'indice dei tutorial Index


 
Roberto Saia  -  All Rights Reserved