Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli
Guide e consigli


Buffer Overflow: Vulnerabilità e Meccanismi di Difesa - Prof. Armando, Appunti di Sicurezza Dei Sistemi Informativi

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

2025/2026

In vendita dal 23/10/2025

tezcat96
tezcat96 🇮🇹

9 documenti

1 / 6

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
Buffer Overflow
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à.
Definizione
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.
Puntatori
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.
Array e Stringhe
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.
pf3
pf4
pf5

Anteprima parziale del testo

Scarica Buffer Overflow: Vulnerabilità e Meccanismi di Difesa - Prof. Armando e più Appunti in PDF di Sicurezza Dei Sistemi Informativi solo su Docsity!

Buffer Overflow

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à.

Definizione

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.

Puntatori

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.

Array e Stringhe

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.

Funzioni pericolose

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”.

Compilazione e Layout della Memoria

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

  1. Preprocessing: Espansione delle macro (#define) e inclusione dei file header (#include).
  2. Compilazione: Analisi lessicale, sintattica, semantica; ottimizzazione; generazione di codice assembly (object code).
  3. Assemblaggio: Traduzione del codice assembly in codice macchina.
  4. Linking: Combinazione di più moduli oggetto, risoluzione di riferimenti esterni (simboli definiti in altri moduli o librerie) e disposizione dei moduli in memoria per creare un file eseguibile.
  5. Loading: Il sistema operativo carica il codice macchina eseguibile nello spazio degli indirizzi virtuali del processo, eventualmente adattando gli indirizzi (relocation) e caricando dinamicamente le librerie necessarie. Layout della Memoria Virtuale Ogni processo ha il proprio spazio di indirizzi virtuali , isolato dagli altri processi. Un layout tipico (es. Linux su x86) è:  Text Segment (basso): Contiene il codice eseguibile del programma. Di solito è read-only.  Data Segment: Contiene variabili globali e statiche inizializzate e non inizializzate.  Heap (cresce verso l'alto): Area per l'allocazione dinamica della memoria (es. tramite malloc()).  Stack (cresce verso il basso - alto): Area per le variabili locali delle funzioni, i parametri passati alle funzioni e le informazioni di controllo (come gli indirizzi di ritorno).

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

sovrascritto punti a un punto qualsiasi all'interno della zona NOP; l'esecuzione

"scivolerà" fino allo shellcode vero e proprio. Meccanismi di Difesa Esistono diverse strategie per prevenire o almeno mitigare gli attacchi di buffer overflow

Stak Canaries

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.

Programmazione Difensiva

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.

Controllo Automatico dei Limiti (Compiler-based)

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)).

Utilizzo di Linguaggi Type-Safe

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+

  • e presentare esso stesso vulnerabilità.

Evitare Buffer sullo Stack

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.

Buffer Non Eseguibile (NX Bit/DEP)

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;