2 - Vulnerabilità
del Buffer Overflow e Attacchi
Lo
scopo complessivo di un attacco di buffer overflow è di sovvertire la funzione
di un programma privilegiato in modo che l'attaccante possa prendere il
controllo di quel programma, e, se esso è sufficientemente privilegiato,
controllare l'host. Tipicamente l'attaccante sceglie come "vittima"
programmi di root e immediatamente esegue codice simile a exec(sh) per
ottenere una shell con i privilegi di root; ma non sempre!
Per
ottenere questo risultato l'attaccante deve:
Gli attacchi di questo tipo sono organizzati
in termini dei punti 1. e 2.
Possiamo formalizzare quanto detto, con
la seguente figura:
![]() |
Figura 1 - Stack violato
|
2.1 - Come utilizzare Codice di Attacco
Ci sono due modi per realizzare tutto
ciò ai danni di un programma vittima: iniettare codice eseguibile, oppure usare
quello già esistente.
Iniettare il codice:
L'attaccante fornisce una stringa in
input al programma che lo stesso carica in un buffer. La stringa contiene bytes
che sono istruzioni della CPU che è stata appena attaccata. Qui l'attaccante
sta usando i buffers del programma vittima per memorizzarvi il codice
d'attacco.
Alcune sfumature di questo metodo:
·
L'attaccante
non ha bisogno di far traboccare nessun buffer per fare questo; quantità
sufficienti di dati possono essere iniettati all'interno di un buffer senza
eccedere il limite fisico della sua dimensione;
·
Il buffer
può essere localizzato ovunque:
·
Sullo
stack (variabili automatiche);
·
Sull'heap
(malloc di variabili);
·
Nell'area
dei dati statici (inizializzati e non);
Il codice è già lì:
Spesso, il codice adatto a fare ciò che
l'attaccante vuole è già presente nello spazio di indirizzamento del programma.
L'attaccante ha bisogno solo di parametrizzare il codice e quindi fare in modo
che il programma salti ad esso. Per esempio, se il codice d'attacco deve
eseguire exec(/bin/sh), ed esiste il codice in libc che esegue
exec(arg) dove arg è una stringa puntatore all'argomento, allora
l'attaccante ha bisogno solo di cambiare un puntatore verso /bin/sh e saltare
all'appropriata istruzione nella libreria libc.
2.2 - Modifica del Flusso
Tutti
questi metodi tendono ad alterare il controllo del flusso del programma in modo
da permettergli di saltare al codice d'attacco. Il metodo base è di far
traboccare un buffer che ha un debole o inesistente controllo sul limite dei
suoi input, con il risultato di corrompere una parte adiacente allo stato del
programma: puntatori adiacenti, etc.
Con
la rottura del buffer, l'attaccante può sovrascrivere la parte adiacente
allo stato del programma con un'arbitraria sequenza di byte.
La
classificazione è sul tipo di stato del programma che l'attaccante tenta di
corrompere. In principio, lo stato corrotto può essere di qualsiasi tipo. Per
esempio, il worm originale di Morris[2] usò un buffer overflow sul
programma fingerd per corrompere il nome di un file che fingerd
voleva eseguire. In pratica, molti buffer overflow tentano di corrompere i puntatori, in particolare, l'indirizzo di
ritorno della funzione attiva. I fattori che distinguono gli attacchi di buffer
overflow sono il tipo di stato corrotto e dove nello stato della memoria esso è
localizzato.
Record d'Attivazione:
Ogni
volta che una funzione è chiamata, essa si colloca in un record di attivazione
sullo stack che include, tra le altre cose, l'indirizzo di ritorno al quale il
programma dovrà saltare quando la funzione termina. Corrompendo l'indirizzo di ritorno nel record di attivazione,
l'attaccante causa il salto del programma al codice d'attacco quando la
funzione vittima restituisce e dereferenzia l'indirizzo di ritorno. Questo tipo
di buffer overflow è chiamato stack smashing attack (attacco che
fracassa lo stack) e costituisce la maggioranza dei correnti attacchi di buffer
overflow.
Puntatori a Funzione:
void
(* foo)() - dichiara una variabile foo che è di tipo puntatore a funzione che
ritorna void.
I
puntatori a funzione possono essere allocati ovunque (stack, heap, area di dati
statici) e quindi, l'attaccante, necessita solo di trovare un buffer adiacente
al puntatore a funzione in una qualunque di queste aree e mandarle in overflow
per cambiare il puntatore. Successivamente, quando il programma effettua una
chiamata attraverso questo puntatore a funzione salterà alla locazione di
memoria desiderata dall'attaccante.
Longjmp Buffer:
Il
linguaggio C include un semplice sistema
di controllo del ritorno
chiamato setjmp/longjmp. L'idioma è dire setjmp(buffer)
al checkpoint, e longjmp(buffer) per ritornare al checkpoint. Comunque, se
l'attaccante può corrompere lo stato del buffer, allora longjmp(buffer)
salterà al codice di attacco. Come i puntatori a
funzione, longjmp(buffer) può essere allocato ovunque, quindi, l'attaccante
ha bisogno solo di trovare un buffer vulnerabile adiacente.
2.3 - Tecnica
Tipica di Attacco
La più semplice e più comune forma d'attacco tramite buffer
overflow combina la tecnica d'iniezione con la corruzione del record
d'attivazione in un'unica stringa. L'attaccante localizza una variabile
automatica soggetta ad overflow, fornisce al programma un'ampia stringa che
contiene il codice di attacco da iniettare per far traboccare il buffer allo
scopo di cambiare il record d'attivazione.
L'iniezione e la corruzione non avvengono in un'unica
azione. L'attaccante può iniettare il codice in un buffer senza farlo
traboccare, e far traboccare un buffer differente per corrompere il puntatore
al codice. Questo è fatto tipicamente se il buffer che s'intende attaccare non
ha controlli sulla taglia, ma se non è così, il buffer potrà essere fatto
traboccare solo con un certo numero di byte. L'attaccante non ha posto per
piazzare il codice nel buffer vulnerabile, allora il codice è semplicemente
inserito in un buffer differente di taglia sufficiente.
Se l'attaccante sta cercando di usare codice già residente
invece di iniettarlo, egli ha bisogno di parametrizzare il codice. Per esempio,
ci sono frammenti di codice in libc che effettuano una chiamata a
exec(qualcosa) dove qualcosa è un parametro. L'attaccante, quindi, usa il
buffer overflow per corrompere l'argomento e un altro buffer overflow per
corrompere il puntatore che, in libc, punta all'appropriato frammento di
codice.
|
||
|
|
|
|