Aggiornato al : feb 18, 2008



Il buffer overflow (spesso abbreviato in BOF) è una delle tecniche più avanzate di hacking del software. Tutto nasce da un difetto che può caratterizzare un determinato software e, se utilizzato a dovere, può agevolare l’accesso a qualsiasi sistema che utilizza il software in questione. Spesso, infatti, si sente parlare di “exploit”, ossia metodi ad hoc che utilizzano le vulnerabilità scoperte in questo o in quel software e che permettono all’utilizzatore di acquisire privilegi che non gli spettano (ad esempio i tanto agognati privilegi di root) o di portare al “denial of service” del computer attaccato. Molti di questi exploit utilizzano per i loro scopi buffer overflow.
Questo tipo di debolezza dei programmi è noto da molto tempo, ma solo di recente la sua conoscenza si è diffusa tanto da permettere anche a dei cracker dilettanti di sfruttarla per bloccare o prendere il controllo di altri computer collegati in rete.
In poche parole, il buffer overflow consiste nel fornire al programma più dati di quanto esso si aspetti di ricevere, facendo in modo che una parte di questi dati vadano scritti in zone di memoria dove ci sono, o dovrebbero esserci, altri dati (da ciò il nome, che letteralmente significa “Trabocco dell’area di memoria”).
Ad esempio, un programma definisce due variabili: una stringa A di 8 byte e un intero B di 2 byte.
A è inizializzata con soli caratteri ‘0’ (ognuno dei quali occupa 1 byte, dunque sono 8 caratteri), mentre B contiene il numero 3.

Adesso supponiamo che sia previsto un inserimento della stringa A da parte dell’utente, ma che non si effettui un controllo sulla lunghezza dell’input inserito. In questo caso, i problemi si hanno se si prova ad inserire una stringa più lunga di 8 caratteri, che è lo spazio riservato nel buffer. Se ad esempio inseriamo la stringa “excessive”, essa occuperà 9 caratteri più il carattere di fine stringa, quindi la porzione di memoria successiva, che era occupata da B, verrà irrimediabilmente sovrascritta. La situazione sarà la seguente:

A questo punto, se si prova a leggere l’intero che ci dovrebbe essere in B, un sistema big-endian che utilizza l’ASCII, leggerà ‘e’ seguita dallo ‘0’ come 25856. Se invece provassimo a scrivere una stringa ancora più lunga, essa invaderebbe anche l’area di memoria che si trova dopo di B, causando un errore di segmentation fault con la seguente terminazione forzata del processo.
Tutto questo capita tipicamente nei sistemi operativi o nei programmi scritti nei linguaggi Assembly o C, usando funzioni di libreria di input/output che non fanno controlli sulle dimensioni dei dati trasferiti.
Questo semplice esempio ci aiuta a capire di cosa è capace un buffer overflow: a seconda di cosa è stato sovrascritto e con quali valori, il programma può dare risultati errati o imprevedibili, bloccarsi, o (se è un driver di sistema o lo stesso sistema operativo) bloccare il computer.
Non tutti i programmi sono vulnerabili a questo tipo di inconveniente, perché un dato programma sia a rischio è necessario che:
1. il programma preveda l’input di dati di lunghezza variabile e non nota a priori;
2. che li immagazzini entro buffer allocati nel suo spazio di memoria dati vicini ad altre strutture dati vitali per il programma stesso;
3. che il programmatore non abbia implementato alcun mezzo di controllo della correttezza dell’input in corso.
La prima condizione è facilmente verificabile dalle specifiche del programma; le altre due invece sono interne ad esso e riguardano la sua completezza in senso teorico.

Tipi di buffer overflow

Esistono diversi modi per portare avanti un buffer overflow. I più importanti sono:

Arithmetic Overflow

Questo tipo di overflow è ottenuto quando il risultato prodotto da un calcolo è più grande delle spazio che dovrebbe contenerlo. Possiamo spiegarlo facilmente mediante un esempio. Avviamo la calcolatrice di Windows scegliendo la modalità scientifica dal menu, scriviamo ‘-1’ e premiamo su ‘Hex’. Vedremo così il valore esadecimale di -1, che è ‘FFFFFFFFFFFFFFFF’. Il problema nasce premendo ‘Dec’: ci aspetteremmo di rivedere il nostro ‘-1’, ma invece otteniamo il valore ‘18446744073709551615’ e ciò è dovuto al fatto che la calcolatrice ha cambiato il valore da “signed” a “unsigned”. Questo esempio serve a dimostrare che anche i programmatori potrebbero compiere lo stesso errore, trasformando un numero negativo in un numero elevatissimo che potrebbe creare un buffer overflow.

Buffer Overflow basati sulla memoria

Si tratta degli attacchi di buffer overflow più noti e dannosi e generalmente vengono distinti in base all’area di memoria che vanno a interessare, in quanto sono possibili buffer overflow su tutte le aree di memoria su cui è possibile scrivere. Spesso è sufficiente un solo byte che vada al di là dello spazio assegnato, per rendere possibile un exploit. Quelli più diffusi sono i buffer overflow “di heap” e “di stack”, dato che si tratta delle aree di memoria più colpite. Di essi si parlerà diffusamente in seguito, non prima di aver fatto un rapido excursus sulla struttura della memoria e sul comportamento del processore in occasione dell’esecuzione di un programma, argomenti sicuramente propedeutici alla comprensione dei buffer overflow.
3. Struttura della memoria e comportamento del processore durante l’esecuzione di un programma
Quando eseguiamo un programma, esso verrà caricato in memoria in maniera ben strutturata creando diverse zone:
1. TEXT, che contiene il codice del programma in esecuzione ed è di sola lettura, infatti se si tentasse di scriverci sopra si incorrerebbe in un errore di Segmentation Fault;
2. zona dati, che contiene le variabili globali, sia inizializzate (contenute in una regione detta .DATA) che non inizializzate (contenute in una regione detta .BSS);
3. HEAP, generalmente posto dopo la zona dati, in cui vengono memorizzate le variabili allocate dinamicamente;
4. STACK, che contiene le variabili locali, gli argomenti delle funzioni, le informazioni di stato del chiamante (ad esempio il contenuto di alcuni registri della CPU), l’indirizzo di ritorno necessario per poter ritornare dalla funzione corrente e altre informazioni.
Naturalmente questi spazi non sono illimitati, bensì hanno una determinata lunghezza, dunque anche le variabili che vi verranno allocate dovranno rispettare tale lunghezza. In particolare, come si può vedere dalla seguente figura esemplificativa, lo heap e lo stack crescono in maniera diversa: il primo cresce verso l’alto, il secondo verso il basso.

Si tenga presente che stiamo prendendo in considerazione l’architettura Intel e che lo stack ha una direzione che può variare a seconda del sistema operativo utilizzato, ma ciò non influenza la comprensione degli argomenti trattati.
Per quanto riguarda lo stack, qualcosa in più merita di essere specificata: esso è organizzato a pila, nel senso che l’ultimo dato inserito è il primo ad essere letto (LIFO, Last In Last Out); in Assembly esistono dei comandi (push e pop) che permettono rispettivamente di inserire e di prelevare valori in cima allo stack. Man mano che i dati vengono scritti nello stack, esso cresce verso il basso, quindi va da indirizzi di memoria alti ad indirizzi di memoria bassi. Se si cerca di effettuare una operazione di pop prima dell’inizio dello stack si ha un “buffer underflow”, se invece si effettuare un’operazione di push al di là dello stack si incorre in un “buffer overflow”.
Anche il processore è interessato dall’esecuzione del programma, in particolare lo sono alcuni suoi registri, strettamente legati alla situazione della memoria durante l’esecuzione:
–> EBP, che è il puntatore alla base dello stack e, nel caso stiamo eseguendo una funzione, punta alla base della porzione di stack utilizzata da essa;
–> ESP, tramite il quale possiamo scorrere tutto lo stack per inserire o prelevare dati da un punto ben preciso di esso;
–> EIP, che punta alla prossima istruzione che la CPU dovrà eseguire dopo quella corrente.
Per comprendere a fondo come questi registri e la memoria siano interessati dall’esecuzione del programma, osserviamone uno molto semplice, che chiamiamo example.c:

#include 
void example(int, int, int);
main() {
example(0,1,2);
}
void example(int a, int b, int c) {
int i=4;
char[] buffer="hello";
}

Il programma chiama la funzione example passandogli gli interi 0,1 e 2. La funzione si occupa di creare e assegnare la variabile i e la stringa buffer.
Compiliamo il programma con uno dei numerosi compilatori C che si trovano in rete (io ho usato Dev C++) e poi disassembliamo l’eseguibile ottenuto example.exe con un disassembler, per esempio Disasm di Sang Cho. Chiaramente il codice macchina ottenuto è molto più lungo del codice C e sono presenti un gran numero di istruzioni dal significato molto poco intuitivo, ma non è difficile individuare quelle che ci interessano:

Le prime tre istruzioni sono tre operazioni di push, che inseriscono i valori 2, 1 e 0 nello stack (sono i tre parametri della funzione example, inseriti sullo stack in ordine inverso), successivamente si ha una CALL, utilizzata per chiamare la funzione example, infatti si salta all’indirizzo 00401234. Da notare che, ogni qualvolta bisogna fare una CALL, quindi anche in questo caso, il processore salva il valore attuale di EIP nello stack e poi lo modifica per effettuare un salto incondizionato alla funzione, in modo da poterlo ripristinare al termine della funzione, per poter riprendere l’esecuzione dall’istruzione successiva alla chiamata.
Siamo all’interno della funzione: per prima cosa EBP viene salvato sullo stack, in EBP viene memorizzato il valore di ESP (cioè l’inizio dello stack per la funzione) e viene sottratto a ESP lo spazio necessario per le variabili con una operazione di SUB. Le istruzioni successive riguardano l’allocazione e l’assegnazione delle variabili i e buff, inserite nello stack seguendo come sempre la modalità LIFO. Lo stack, quindi, in questo momento si presenta pressappoco così:

Alla fine, mediante l’istruzione LEAVE, i registri EBP e ESP riacquisiscono i valori che avevano prima di chiamare la CALL e, mediante l’istruzione RET, si ritorna alla funzione principale utilizzando l’indirizzo di ritorno presente nello stack.

Buffer Overflow di Stack

Come abbiamo detto precedentemente, il BOF si ha quando le variabili non rispettano lo spazio a loro assegnato e vanno a scrivere anche lo spazio al di là di esso, sovrascrivendo i dati precedentemente contenuti. In particolare questo tipo di BOF è quello in assoluto più diffuso e interessa lo stack. Ne esistono diverse varianti, è possibile comunque trovare in tutte delle similitudini, che riguardano in primis lo scopo finale, che è sempre quello di sovvertire la funzione del programma per direzionarlo secondo i propri scopi. Se il programma è sufficientemente privilegiato (ad esempio di tipo SUID), è possibile ottenere il controllo dell’host, generalmente attivando una shell locale, mediante la quale, con i privilegi di root, è praticamente possibile effettuare qualsiasi cosa. Per ottenere un BOF, sono necessari due passi principali:
1) Fare in modo che il codice che ci interessa sia nell’address space del programma
2) Fare in modo che il programma salti ad esso e lo esegua.
Questi due passi sono comunque in stretta correlazione, dato che se inseriamo il codice senza eseguirlo non abbiamo concluso nulla.

Fare in modo che il codice sia nell’address space del programma

Per effettuare l’inserimento del codice, ci sono due modi:
1) Inserirlo manualmente (Code injection): il programma chiede in input una stringa, che verrà inserita dall’attaccante in modo da contenere istruzioni per la CPU. Questa stringa verrà inserita in un determinato buffer, senza necessità di effettuare 4.2 Fare in modo che il programma salti al codice di attacco e lo esegua
Per ottenere ciò ci sono diversi modi, ma lo scopo di base è quello di effettuare l’overflow di un buffer che non ha controlli sui confini (o se ci sono, sono molto deboli), in modo da corrompere un’area adiacente. I principali tipi sono:
–>Activation Records: si tratta della tipologia più diffusa, nota in genere con la frase “Smashing the stack”. Si utilizza all’interno di una funzione e consiste nell’effettuare l’overflow di un buffer, con lo scopo di arrivare a sovrascrivere l’EIP, cioè l’indirizzo di ritorno della funzione. Se esso fosse sovrascritto per sbaglio, o con codice a caso, si avrebbe semplicemente un errore di Segmentation Fault, ma se invece esso è sovrascritto con un indirizzo realmente esistente, il risultato è che si salta alla locazione da esso indicato e si esegue il codice lì presente. Pensiamo a cosa succede se questo indirizzo indica la locazione del codice di attacco…
–>Puntatori a funzioni: bisogna trovare un buffer vicino ad un puntatore a funzione. Effettuando l’overflow del buffer, viene corrotto anche il puntatore e si fa in modo che esso punti alla locazione del codice di attacco. Questo tipo di BOF può riguardare non solo lo stack, ma anche lo heap e le altre aree della memoria su cui è possibile scrivere.
–>Longjmp buffers: sfrutta un meccanismo presente in C che consente di salvare lo stato (checkpoint) di un buffer mediante il comando setjmp(buffer) e di ripristinarlo in seguito (rollback) in caso di bisogno mediante il comando longjmp(buffer). Come per i puntatori a funzioni, se abbiamo un buffer adiacente di cui è possibile effettuare l’overflow, potremmol’overflow. In poche parole, abbiamo salvato il codice di attacco in un buffer;
2) Il codice si trova già lì: il codice che ci serve è già presente, bisogna solo parametrizzarlo a dovere. Ad esempio, se si ha in UNIX il codice exec(arg) con arg puntatore ad una stringa, basta fare in modo che arg punti a /bin/sh per avere una shell in locale.

Fare in modo che il programma salti al codice di attacco e lo esegua

Per ottenere ciò ci sono diversi modi, ma lo scopo di base è quello di effettuare l’overflow di un buffer che non ha controlli sui confini (o se ci sono, sono molto deboli), in modo da corrompere un’area adiacente. I principali tipi sono:
–>Activation Records: si tratta della tipologia più diffusa, nota in genere con la frase “Smashing the stack”. Si utilizza all’interno di una funzione e consiste nell’effettuare l’overflow di un buffer, con lo scopo di arrivare a sovrascrivere l’EIP, cioè l’indirizzo di ritorno della funzione. Se esso fosse sovrascritto per sbaglio, o con codice a caso, si avrebbe semplicemente un errore di Segmentation Fault, ma se invece esso è sovrascritto con un indirizzo realmente esistente, il risultato è che si salta alla locazione da esso indicato e si esegue il codice lì presente. Pensiamo a cosa succede se questo indirizzo indica la locazione del codice di attacco…
–>Puntatori a funzioni: bisogna trovare un buffer vicino ad un puntatore a funzione. Effettuando l’overflow del buffer, viene corrotto anche il puntatore e si fa in modo che esso punti alla locazione del codice di attacco. Questo tipo di BOF può riguardare non solo lo stack, ma anche lo heap e le altre aree della memoria su cui è possibile scrivere.
–>Longjmp buffers: sfrutta un meccanismo presente in C che consente di salvare lo stato (checkpoint) di un buffer mediante il comando setjmp(buffer) e di ripristinarlo in seguito (rollback) in caso di bisogno mediante il comando longjmp(buffer). Come per i puntatori a funzioni, se abbiamo un buffer adiacente di cui è possibile effettuare l’overflow, potremmo corrompere anche lo stato del buffer di checkpoint in modo che, non appena viene chiamato il comando longjmp, si salta alla locazione del codice di attacco.

Combinare i due passi precedenti

Come abbiamo detto precedentemente, i due passi precedenti sono collegati tra loro, quindi vanno utilizzati insieme.
Spesso l’inserimento del codice di attacco e la sua esecuzione sono effettuati in una volta sola: basta trovare un buffer di cui sia possibile fare l’overflow e che si trovi in prossimità dell’EIP, inserire una stringa opportuna contenente il codice di attacco che effettui l’overflow del buffer e modifichi l’EIP. In questo modo abbiamo fatto sia la code injection che l’activation record.
Comunque non per forza le due fasi devono avvenire simultaneamente. È possibile ad esempio che il buffer del caso precedente non abbia lo spazio necessario per contenere tutto il codice di attacco, dunque è necessario fare la code injection in un altro buffer di dimensione sufficiente e successivamente utilizzare il buffer vicino l’EIP solo per corrompere quest’ultimo realizzando l’activation record.
Se non è necessario effettuare la code injection perché il codice è già presente, bisogna, come spiegato sopra, parametrizzare il codice presente in modo da fargli eseguire ciò che si vuole e poi effettuare l’overflow del buffer vicino l’EIP per far puntare quest’ultimo al codice parametrizzato.

Lo Shellcode

Lo shellcode è un pezzo di codice macchina eseguito per sfruttare una vulnerabilità. Si tratta spesso di un codice che svolge un compito altamente specifico, che è verificato dal primo all’ultimo byte, perché anche un byte fuori posto potrebbe portare al crash dell’applicazione da “exploitare” o alla corruzione della memoria con il conseguente non funzionamento dell’applicazione; ciò potrebbe comportare il riavvio della macchina, che potrebbe avvenire dopo un tempo non proprio breve (soprattutto per quanto riguarda gli ambienti industriali) oppure l’amministratore potrebbe indagare sul crash dell’applicazione e scaricare di conseguenza l’upgrade che magari va a sistemare la falla della versione precedente. Questo porterebbe al completo fallimento del piano di attacco. Ecco perché lo shellcode deve sempre essere un codice preciso e valutato nei minimi dettagli.
Una caratteristica importante dello shellcode è l’assoluta mancanza di portabilità tra i diversi sistemi. La maggior parte degli shellcode implementati, per documentare i quali esistono centinaia di testi in rete, sono realizzati per UNIX, in quanto le API di Windows complicano la creazione di shellcode per questo sistema operativo, anche se oggi la situazione sta cambiando rapidamente, grazie a testi specifici come “The Tao of Windows Buffer Overflow” o a shellcode come il “plug and play” shellcode.

Esempi di Buffer Overflow di Stack

Come abbiamo detto, il BOF di stack può essere portato avanti in molti modi, vediamone un paio molto semplici.
Programma 1
Si tratta di un programma C che mostra un esempio di buffer overflow che utilizza la funzione gets(), notoriamente pericolosa in quanto non controlla se la stringa immessa dall’utente è più lunga del buffer che dovrà contenerla. Proprio questo causa il buffer overflow che va a corrompere la variabile successiva, che contiene un comando da eseguire. Quindi, inserendo in input un’apposita stringa, sarà possibile eseguire praticamente qualsiasi comando. L’esempio è stato realizzato in ambiente Windows, ma sarebbe la stessa cosa in Linux, in quanto cambierebbero solo gli indirizzi ma non la sostanza.


#include
void example();
char *p;
int i;
main () {
example();
}
void example() {
char command[10]="calc";
char name[10];
printf("Inserisci un nome da dare a questo script ");
gets(name);
printf("Premere un tasto per eseguire il comando");
getchar();
p=&name[0]+25;
for (i=30;i>=0;i--) {
printf("n %p = %c",p,*p);
p=p-1;
}
system(command);
}

Il programma non fa altro che chiamare la funzione example(), la quale alloca dinamicamente la variabile command con valore calc, che rappresenta il comando che vogliamo eseguire (la semplice calcolatrice di Windows). Successivamente viene allocata dinamicamente la variabile name, in cui vogliamo inserire un nome da dare allo script, cosa che viene fatta richiamando la funzione gets().
In questo momento lo stack (solo la parte relativa alla nostra funzione) si presenta pressappoco in questo modo:

La restante parte serve per farci capire cosa sta succedendo in memoria, infatti ci mostra un’istantanea dello stack, in linea con lo schema mostrato sopra, cioè mostrando in alto gli indirizzi alti e in basso quelli bassi. Essa ci sarà utile nel momento in cui effettueremo l’overflow, per capire cosa effettivamente è accaduto in memoria. In particolare, in questa sezione utilizziamo le due variabili i e p, dichiarate come globali affinché si trovino fuori dallo stack della funzione example().
Alla fine verrà lanciato sul sistema il comando command, che normalmente è la calcolatrice.
Se compiliamo il programma con un compilatore C e lo mandiamo in esecuzione, ci verrà subito chiesto di dare un nome allo script. Inseriamo inizialmente la scritta hello, che rientra perfettamente nei limiti. Infatti il programma verrà eseguito perfettamente: ci verranno mostrati gli indirizzi della memoria e si avvierà la calcolatrice. Le due variabili si trovano entrambe nello stack. Come è possibile vedere anche dallo screenshot successivo, l’output ci mostra lo stack, notare la variabile name (che contiene la stringa hello) che si trova in testa allo stack e sotto la variabile command (che contiene la stringa calc) ed è separata da essa.

Se a questo punto proviamo a rimandare in esecuzione il programma, inserendo invece di hello la stringa xxxxxxxxxxxxxxxxxxxx, essa riempie il buffer di 10 caratteri a disposizione e poi sovrascrive quello che c’è dopo, arrivando a sovrascrivere anche la variabile command, che adesso conterrà il valore xxxx. Infatti, invece di avviare la calcolatrice, ci viene restituito un messaggio che ci dice che xxxx è un comando sconosciuto. Se però, invece di xxxx, in command ci fosse stato un comando realmente esistente, esso sarebbe andato in esecuzione. Per verificare ciò, inseriamo ad esempio la stringa xxxxxxxxxxxxxxxxcmd, il risultato sarà una shell di sistema in locale. Potremmo inserire anche altri comandi, con risultati ben peggiori… In questo esempio, in particolare, possiamo inserire, invece di cmd, qualsiasi comando di lunghezza fino a 10 lettere, dato che la variabile command è stata dichiarata in questo modo:

char command[10]=”calc”;

Se invece fosse stata dichiarata in quest’altro modo:

char command[]=”calc”;

sarebbe stato possibile inserire comandi di massimo 4 lettere. Quindi basta poco per causare danni di portata incalcolabile, nel primo caso il comando potrebbe anche essere format C:…

Programma 2

Questo programma è realizzato in C come il primo, ma l’ho testato su un sistema Linux, in particolare sulla distribuzione Suse Linux 10.0. Si tratta di un ottimo esempio di activation records, in quanto viene sovrascritto l’indirizzo di ritorno di una funzione con l’indirizzo di un’altra funzione che si vuole eseguire e che dovrebbe contenere il codice di attacco. Il programma principale è contenuto nel file prog1.c:


void function ();
void function () {
printf("Ci sei riuscito!!!!nn");
exit(0);
}
main (int argc, char *argv[]) {
char var[10];
strcpy(var,argv[1]);
}

Il programma non fa altro che prendere una stringa in ingresso e inserirla all’interno della variabile var, utilizzando la funzione strcpy(), anch’essa pericolosa perché non effettua controlli sui confini del buffer di destinazione. La funzione function() non viene mai chiamata dal programma. Il nostro obiettivo sarà quello di causare un buffer overflow, inserendo in ingresso una stringa più lunga dei 10 caratteri a disposizione e di sostituire l’indirizzo di ritorno di main() con quello della funzione function(), in modo che essa venga eseguita. Per fare ciò dobbiamo fare un po’ di prove.
Cominciamo ad inserire tante x, vediamo che dalla 14a in poi, il programma va in Segmentation Fault. A questo punto avviamo il disassembler gdb (presente nei sistemi operativi Unix), dandogli come programma da disassemblare il nostro prog1 (gdb prog1) e disassembliamo la funzione function():

Vediamo che l’indirizzo di inizio della funzione function() è 0×08048438. Se in pratica riusciamo a fare in modo che il 15° elemento della stringa che diamo in input a prog1 sia questo indirizzo, il programma salterà alla funzione function() e avremo centrato l’obiettivo. Come fare a passargli l’indirizzo? Esso infatti è scritto in caratteri esadecimali e non ASCII. Scriviamo un piccolo exploit, contenuto nel programma exploit.c, che si occupa di convertire in ASCII e di passare al programma l’indirizzo da noi inserito in esadecimale.


main () {
char buf[31],lancia[35];
int i;
for (i=0; i<14; i++) {
buf[i]= 'x';
}
*(long *)&buf[14]=0x08048438;
strcpy(lancia,"/home/giacomo/bof/prog1 ");
strcat(lancia,buf);
system(lancia);
}

Questo programma si occupa di lanciare il comando prog1 seguito da 14 caratteri x e dall’indirizzo della funzione function(). Il risultato sarà il seguente:

Ci siamo riusciti!

Diamo un ulteriore tocco stilistico al nostro exploit, eliminando il ciclo for e inserendo l’argomento di prog1 tutto in esadecimale:


include
main () {
char buf[31],lancia[35];
buf="x61x61x61x61x61x61x61x61x61x61"
"x61x61x61x61x08x04x84x38";
strcpy(lancia,"/home/giacomo/bof/prog1 ");
strcat(lancia,buf);
system(lancia);
}

Articolo scritto da l3golas. Per ulteriori info o chiarimenti contattare l’autore.



  • Sofisma Says:
    maggio 30th, 2007 at 19:54

    Salve…

    complimenti per l’articolo, non ho parole veramente ben fatto :)

    P.S.
    come ho fatto a scoprirti solo ora !?!!
    No lo sa manco io, comunque sia sei stato “Feedizzato” :)

    Rispondi

  • intilinux Says:
    maggio 30th, 2007 at 19:59

    devi ringraziare l3golas per il contenuto…

    Manca cmq ancora tutta la seconda parte che sarà postata nei prossimi giorni.

    Grazie!

    Rispondi

  • gabcicala » Attacchi alle applicazioni basate su Buffer Overflow [parte 1] Says:
    maggio 31st, 2007 at 23:55

    [...] Utile articolo scritto da l3golas che descrive questo tipo di attacchi ….Attacchi alle applicazioni basate su Buffer Overflow [parte 1] [...]

  • Lobotomia Says:
    agosto 8th, 2007 at 16:02

    Ottimo Articolo!

    Segnalato anche da noi qui:
    http://scandaglio.blogspot.com.....rflow.html

    Rispondi

  • riva.dani Says:
    agosto 8th, 2007 at 16:31

    Complimenti per l’articolo. Un solo appunto: se sicuro che il BOF sia una “tecnica di hacking del software”? Voglio dire, il termine hacking è corretto in questo caso? Lo chiedo senza malizia da perfetto ignorante nel campo. :)

    Rispondi

  • Lobotomia Says:
    agosto 8th, 2007 at 16:37

    @riva.dani: dipende da cosa intendi per hacking, cmq si il BOF è una tecnica di hacking, sia nel campo del software che della sicurezza..

    Rispondi

  • l3golas Says:
    agosto 8th, 2007 at 21:21

    Ciao a tutti. Per hacking del software si intende lo studio accurato di esso allo scopo di trovare delle falle che ci permettano di adattarlo alle nostre esigenze; quindi il buffer overflow è senza dubbio una tecnica di hacking del software.
    Sono contento che l’articolo vi sia piaciuto, se vi interessa la programmazione leggete anche i miei articoli sulle ottimizzazioni di GCC e sulla Thumb mode che trovate su questo sito. Tra non molto arriverà qualcosa di più corposo riguardante il connubio tra ARM e Open Source, rimanete sintonizzati!

    Rispondi

  • Lorenzo Says:
    novembre 14th, 2007 at 20:09

    Ciao ho letto i 3 articoli riguardanti gli attacchi alle applicazioni basate su buffer overflow. Sono stati scritti da dio molto interessanti davvero è solo che mi sono posto 2 domande alle quali penso tu saprai spero rispondermi:
    1- In linguaggio “C” se una funzione non ritorna(a causa di loop, exit() o altro) può permettere di operare su di essa un attacco buffer overflow?Perche?
    2-In linguaggio C se invoco ’strcpy()’ con una variabile ‘char from[12]‘ come stringa di provenienza e una ‘char[20]‘ come stringa di destinazione, posso avere buffer overflow?
    Spero tu riesca a rispondermi e ti ringrazio din da ora!!Complimentoni ancora per gli articoli!!

    Rispondi

  • l3golas Says:
    novembre 15th, 2007 at 02:33

    Ciao, rispondo prima alla domanda 2. Se hai una stringa di provenienza di 12 caratteri e la copi su una stringa di 20 caratteri, non hai buffer overflow in quanto non vai al di là dello spazio a disposizione nella stringa di destinazione, a meno che non copi la stringa di 12 in quella di 20 cominciando non dalla posizione 0, ma ad esempio dalla posizione 10 o più avanti. Per quanto riguarda la domanda 1, se per loop intendi un ciclo infinito allora no, perchè siamo sempre all’interno della funzione e non torniamo da essa, se invece è un loop normale che magari va a scrivere in memoria allora potrebbe essere possibile scrivere l’indirizzo di ritorno della funzione, quindi in questo caso si potrebbe avere il buffer overflow non appena il loop finisce e proviamo a ritornare dalla funzione. Se invece utilizziamo exit() usciamo direttamente dal programma, quindi non credo ci possa essere buffer overflow, tuttavia su quest’ultima cosa mi concedo il beneficio del dubbio, perchè non conosco l’implementazione di exit(), quindi non so se magari in alcune occasioni si può sfruttare per qualche BOF. Spero di essere stato chiaro, ciao

    Rispondi

  • Lorenzo Says:
    novembre 15th, 2007 at 13:02

    Ciao volevo ringraziarti per avermi chiarito le idee. Posso approfittare della tua conoscienza per porti gli ultimissimi dubbi riguardo al buffer overflow?
    1- Quali possibili analisi a livello di codice sorgente possono aiutare a scoprire vulnerabilità di tipo buffer overflow?
    2-Perchè in C un buffer su heap difficilmente ha overflow pericolosi, che permettono attacchi?
    3-Un programma include il seguente segmento di codice C:
    char *buf;…..f(char *p) {strcpy(buf,p);}
    Sotto quali condizioni un attaccante potrebbe sfruttarlo?
    Approfitto per ringraziarti anticipatamente ti auguro buona giornata.Ciao ciao!!

    Rispondi

  • ASLR - Le feature di sicurezza del kernel di Vista spiegate in maniera semplice - Episodio 2 « Divide by zero Says:
    gennaio 31st, 2008 at 11:18

    [...] Attacchi alle applicazioni basate su Buffer Overflow | InTiLinu, in questo articolo viene spiegato in maniera davvero efficace e comprensibile a tutti che cosa è un BOF. [...]

  • ASLR - Le feature di sicurezza del kernel di Vista spiegate in maniera semplice - Episodio 2 -- viklog Says:
    aprile 10th, 2008 at 00:22

    [...] Attacchi alle applicazioni basate su Buffer Overflow | InTiLinu, in questo articolo viene spiegato in maniera davvero efficace e comprensibile a tutti che cosa è un BOF. [...]

  • Anonimo Says:
    dicembre 9th, 2008 at 17:38

    Solo una precisazione: LIFO corrisponde a Last In First Out, anche se viene indicato come Last Int Last Out (LILO).

    Rispondi

  • luca Says:
    luglio 18th, 2009 at 20:39

    Salve a tutti,
    mi complimento anch’io per l’articolo.

    Avete mai provato con BugFighter, scaricabile da http://www.bugfighter-soft.com ?

    L’ho provato, consente di individuare errori di buffer overflow nel codice C/C++.

    Per esempio:

    int x[5][5][5];

    x[1][7][2] = 8;

    Scopre in fase di runtime che il secondo indice non è valido, segnalando il nome del file, della riga e della variabile coinvolta.

    Quello che è interessante è che individua gli indici non ammessi nei vettori multipli.

    Inoltre non dipende dal compilatore nè dal sistema operativo in cui si sta operando, perchè non fa altro che creare codice sorgente nuovo da quello originario.

    Ciao a tutti

    Rispondi