



Studia grazie alle numerose risorse presenti su Docsity
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Prepara i tuoi esami
Studia grazie alle numerose risorse presenti su Docsity
Prepara i tuoi esami con i documenti condivisi da studenti come te su Docsity
Trova i documenti specifici per gli esami della tua università
Preparati con lezioni e prove svolte basate sui programmi universitari!
Rispondi a reali domande d’esame e scopri la tua preparazione
Riassumi i tuoi documenti, fagli domande, convertili in quiz e mappe concettuali
Studia con prove svolte, tesine e consigli utili
Togliti ogni dubbio leggendo le risposte alle domande fatte da altri studenti come te
Esplora i documenti più scaricati per gli argomenti di studio più popolari
Ottieni i punti per scaricare
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
In dettaglio le vulnerabilità di buffer overflow, una minaccia significativa nella sicurezza informatica. Analizza le cause principali, come le caratteristiche dei linguaggi c e c++, e illustra come gli attacchi di buffer overflow possono compromettere l'integrità dei dati e il flusso di controllo. Vengono esaminati diversi meccanismi di difesa, tra cui stack canaries, controlli automatici dei limiti, l'uso di linguaggi type-safe e la protezione nx bit, offrendo una panoramica completa delle strategie per mitigare questi rischi. Esempi concreti e spiegazioni chiare, rendendolo una risorsa utile per comprendere e contrastare le vulnerabilità di buffer overflow.
Tipologia: Appunti
1 / 6
Questa pagina non è visibile nell’anteprima
Non perderti parti importanti!




Anche con un design software eccellente, un’implementazione insicura può compromettere l’intero sistema. Le vulnerabilità a livello implementativo possono derivare da deviazioni dal design, da scelte progettuali lasciate aperte (come la gestione delle password) o dall’introduzione di nuovi punti deboli durante la concretizzazione del codice. Tra queste ultime, i buffer overflow rappresentano una minaccia di primaria importanza nella sicurezza informatica. I buffer overflow sono stati identificati come una delle vulnerabilità più critiche per decenni. Un esempio emblematico è il Morris Internet Worm del 1988, uno dei primi worm su larga scala, che si propagò sfruttando, tra le altre cose, un buffer overflow nel demone fingerd dei sistemi Unix. Sebbene non dannoso intenzionalmente (non cancellava file), causò danni economici significativi interrompendo i servizi. Questo evento sottolineò la gravità di tali vulnerabilità.
Un buffer è una regione contigua di memoria che memorizza dati dello stesso tipo, come una sequenza di caratteri. Un buffer overflow si verifica quando si scrivono dati oltre la fine allocata per quel buffer. Il danno risultante dipende da quali dati adiacenti vengono sovrascritti (es. flag di controllo accessi, variabili locali, indirizzi di ritorno) e da come vengono modificati. La causa principale dei buffer overflow risiede nelle caratteristiche di linguaggi come C e C++, che offrono grande potenza e controllo sulla memoria ma mancano di meccanismi di sicurezza intrinseci, in particolare per quanto riguarda la gestione di puntatori e array.
Un puntatore (es. int *ptr) memorizza l’indirizzo di una cella di memoria. L’operatore * (dereferenziale, es. *ptr) accede al contenuto di quella cella. L’operatore & (address- of, es. &i) restituisce l’indirizzo di una variabile. L’aritmetica dei puntatori (es. ptr+ +) incrementa l’indirizzo in base alla dimensione del tipo di dato puntato, ma non ci sono controlli che impediscono di puntare al di fuori di aree di memoria valide.
Un array (es. char buf[8]) dichiara un buffer contiguo. In C, il nome dell’array (buf) è essenzialmente un puntatore all’inizio del buffer. C non esegue controlli sui limiti, è quindi possibile accedere (in lettura o scrittura) a elementi oltre la fine dichiarata dell’array (es. buff[13] in un array di 8 elementi) senza che il linguaggio generi errore immediato. Le stringhe in C sono semplicemente array di caratteri terminati dal carattere nullo (
0). Non esiste un’informazione esplicita sulla lunghezza dlela stringa associata all’array, le funzioni operano scorrendo l’array fino a trovare il terminatore \0. Questa mancanza di gestione esplicita della lunghezza è una delle causa principali dei problemi.
Molte funzioni standard delle librerie c per manipolazione di stringhe e I/O sono intrinsecamente insicure erchè non controllano i limiti del buffer di destinazione. Esempi: gets(dst): Legge una stringa dall’input standard (stdin) fino a newline e la memorizza in dst senza alcun limite; strcpy(dst, src): Copia la stringa src in dst, fermandosi solo al \0 di src. Se src è più lunga dello spazio allocato per dst, si verifica un overflow. Sprintf(dst, format, …): Scrive una stringa formattata in dst. Se il risultato formattato è più lungo di dst, avviene un overflow. Scanf, fscanf, ecc…: Possono causare overflow se non usate con attenzione specificando le lunghezze massime. Un semplice programma che usa getchar() per leggere caratteri in un buffer buf[8] fino a newline può facilmente causare un overflow se l’input supera i 7 caratteri (+ \0), portando a crash come “Illegal instruction” o “Sementation fault”.
Per comprendere l'impatto dei buffer overflow, è necessario capire come un programma C viene trasformato in codice eseguibile e come viene organizzato in memoria. Processo di compilazione
o Spesso, del codice malevolo (exploit o shellcode) inserito all’interno del buffer stesso. Quando la funzione vulnerabile termina (con l'istruzione ret), invece di tornare al chiamante, il processore legge l'indirizzo di ritorno sovrascritto dallo stack e salta a quell'indirizzo. Se l'indirizzo punta allo shellcode inserito dall'attaccante, quest'ultimo viene eseguito, tipicamente con i privilegi del programma vulnerabile (es. ottenendo una shell di root). Questo compromette l' integrità del flusso di controllo (code-flow integrity). Per facilitare l'attacco (dato che l'indirizzo esatto dello shellcode può variare), gli attaccanti spesso precedono lo shellcode con una "zona di atterraggio" (landing zone) di istruzioni NOP (No-Operation). In questo modo, è sufficiente che l'indirizzo di ritorno
"scivolerà" fino allo shellcode vero e proprio. Meccanismi di Difesa Esistono diverse strategie per prevenire o almeno mitigare gli attacchi di buffer overflow
Idea: Inserire un valore segreto (il "canarino", simile al canarino usato nelle miniere di carbone) sullo stack tra i buffer locali e i dati di controllo (ebp salvato, indirizzo di ritorno). Funzionamento: Prima che la funzione ritorni, il programma controlla se il valore del canarino è stato modificato. Se sì, significa che un buffer overflow ha sovrascritto il canarino (e potenzialmente anche l'indirizzo di ritorno). Il programma può quindi terminare in modo sicuro (es. con exit()) invece di eseguire il ret verso un indirizzo compromesso. Implementazione: Supportato da compilatori come GCC (-fstack-protector) e Microsoft Visual C++. Il canarino è solitamente un valore casuale generato all'avvio del programma o composto da caratteri terminatori di stringa. Esistono attacchi noti per aggirare i canarini in certe condizioni.
Evitare Funzioni Insicure: Non usare mai gets(). Sostituire strcpy, sprintf, ecc., con le loro varianti "bounded" che accettano un parametro per la dimensione massima del buffer di destinazione, come strncpy(), snprintf(), fgets(). Ad esempio, strncpy(dst, src, sizeof(dst)-1) garantisce che non si scriva oltre dst e che ci sia spazio per il \0. Controllo Esplicito dei Limiti: Verificare sempre i limiti degli array durante l'iterazione o l'accesso. Revisione del Codice (Auditing): Team dedicati o strumenti automatici (static analysis, grep per funzioni pericolose) possono cercare vulnerabilità nel codice sorgente. Tuttavia, è un processo dispendioso e soggetto a errori.
Idea: Il compilatore inserisce automaticamente controlli sui limiti prima di ogni accesso a un array.
Svantaggi: Può essere difficile determinare i limiti corretti in C; l'overhead prestazionale può essere significativo (fino a 10-30 volte più lento) ; alcuni compilatori controllano solo accessi espliciti (buf[n]) ma non quelli tramite puntatori (*(buf+n)).
Idea: Utilizzare linguaggi di programmazione (come Java, Pascal, ML) che gestiscono la memoria in modo sicuro per progetto. In questi linguaggi, la lunghezza dell'array è parte del suo tipo, e tentativi di scrivere fuori dai limiti generano errori a runtime gestiti in modo sicuro, invece di corrompere la memoria. Limitazioni: La scelta del linguaggio non è sempre libera ; la sicurezza dipende anche dall'ambiente di runtime (es. la JVM), che potrebbe essere scritto in C/C+
Idea: Allocare i buffer dinamicamente sull' heap (usando malloc()) invece che come variabili locali sullo stack. Poiché l'indirizzo di ritorno è sullo stack, un overflow sull'heap non può sovrascriverlo direttamente. Limitazioni: Anche gli heap overflow sono una minaccia reale. Non compromettono direttamente il flusso di controllo tramite l'indirizzo di ritorno, ma possono sovrascrivere puntatori a funzioni, metadati dell'heap o altri dati critici, portando comunque a vulnerabilità sfruttabili.
Idea: Marcare le regioni di memoria usate per i dati (come lo stack e l'heap) come non eseguibili a livello hardware/OS. Effetto: Anche se un attaccante riesce a iniettare shellcode nello stack o nell'heap e a dirottare il flusso di controllo verso di esso, il processore si rifiuterà di eseguire codice da quella regione di memoria, bloccando l'attacco. Implementazione: Supportato dai moderni sistemi operativi e processori (Data Execution Prevention in Windows, NX bit in Linux). Limitazioni: o Non protegge da attacchi che riutilizzano codice esistente (Return- Oriented Programming - ROP, return-to-libc) che si trova nel segmento Text (eseguibile). o Non previene la violazione dell'integrità dei dati (sovrascrittura di variabili). o Può creare problemi di compatibilità con software legittimo che genera codice dinamicamente (es. JIT compiler) o usa tecniche come i trampoline sullo stack (es. signal handler in Unix). Conclusioni I buffer overflow rimangono un problema pervasivo e critico a causa della gestione della memoria in linguaggi come C/C++. Essi permettono di alterare dati e flusso di controllo in modi imprevisti dal codice sorgente. La difesa richiede un approccio multilivello: Programmazione difensiva rigorosa è essenziale, privilegiando funzioni sicure e controlli sui limiti;