Quando una funzione termina, l'esecuzione salta all'indirizzo che è stato salvato nello stack. Gli exploit "classici" di un buffer overflow alterano quell'indirizzo salvato in modo tale da puntare, appunto, sul contenuto del buffer dello stack che era appena sorvolato. Ciò presuppone che l'attaccante possa scegliere il contenuto del buffer e inserisce lì il codice che desidera venga eseguito.
Quando lo stack non è eseguibile, il classico exploit non funziona più. Tuttavia, il buffer può ancora essere riempito e traboccare sul resto dello stack, che include l'indirizzo di ritorno, ma anche altre cose. Considera che lo stack assomiglia a questo:
address contents
0x07fff8c8 ...
0x07fff8c4 ...
0x07fff8c0 ...
0x07fff8bc ...
0x07fff8b8 ...
0x07fff8b4 return address
0x07fff8b0 ...
0x07fff8ac ...
0x07fff8a8 buffer
0x07fff8a4 buffer
0x07fff8a0 buffer
(...)
0x07fff858 buffer
0x07fff854 buffer
In questo diagramma approssimativo, lo stack cresce verso il basso (come accade nella maggior parte delle architetture); gli indirizzi sono fittizi ma realistici per un'architettura little-endian a 32 bit su un sistema simile a Unix. L'utente malintenzionato proverà, inserendo dati malintenzionati nel buffer, per ottenere questa situazione:
address contents
0x07fff8c8 ...
0x07fff8c4 0
0x07fff8c0 0x07fff854
0x07fff8bc 0x07fff854
0x07fff8b8 ...
0x07fff8b4 address of execlp()
0x07fff8b0 ...
0x07fff8ac ...
0x07fff8a8 buffer
0x07fff8a4 buffer
0x07fff8a0 buffer
(...)
0x07fff858 0x0068732f
0x07fff854 0x6e69622f
Se l'attaccante può ottenere questo, quindi, quando la funzione attaccata ritorna, il puntatore dello stack è impostato su 0x07fff8b4, quindi una parola viene estratta dallo stack e interpretata come "indirizzo di ritorno", cioè il punto in cui l'esecuzione deve essere eseguita Continua. In questo caso, la CPU passa alla funzione di libreria standard execlp()
(che è una funzione Unix). Questa funzione è, per definizione, situata in un'area di RAM che è suscettibile di esecuzione.
Il codice di execlp()
si aspetta di trovare lo stack compilato in questo modo:
address contents
0x07fff8c8 ...
0x07fff8c4 arg2
0x07fff8c0 arg1
0x07fff8bc arg0
0x07fff8b8 address of calling code
execlp()
interpreterà arg0
come puntatore a una stringa di caratteri a terminazione zero che è il percorso di un file eseguibile, che verrà eseguito con execlp()
. arg1
, arg2
... devono essere puntatori ad altre stringhe che sono gli argomenti da passare al file eseguibile.
Nella nostra situazione, execlp()
non ha idea di come sia stato "chiamato"; tutto ciò che sa è che ora la CPU sta entrando in esso. Trova come arg0
il valore 0x07fff854
che l'attaccante ha disposto a mettere in questo posto nello stack, con il suo buffer overflow. Quel valore, 0x07fff854
, sembra essere un puntatore all'area della RAM che in precedenza conteneva il buffer e contiene ancora i dati che l'utente malintenzionato ha inserito in essa perché mentre questa area è "libera", lo stack non è ancora stato reinserito esso. All'indirizzo 0x07ff854
, l'autore dell'attacco ha inserito le due parole a 32 bit 0x6e69622f
e 0x0068732f
, che è la codifica della stringa ASCII "/bin/sh"
(con uno 0 terminante).
Pertanto, execlp()
trova argomenti correttamente formati che lo istruiscono a eseguire una shell, e così fa.
Riepilogo: un buffer overflow che supera l'indirizzo di ritorno viene utilizzato per far saltare il codice attaccato a un posto arbitrario. L'attaccante vuole che la CPU esegua del codice che dia qualche vantaggio all'attaccante (ad esempio il codice che esegue una shell). Poiché lo stack non è eseguibile, l'attaccante non può mettere il suo codice nella pila. Ma la libreria standard è una raccolta di funzioni che sono, per loro natura, eseguibili, e alcune possono fare proprio quello che l'attaccante desidera ottenere. Quindi è sufficiente che l'attaccante renda il punto di riferimento dell'indirizzo alla funzione della libreria che corrisponde all'effetto desiderato, e lo stesso overflow del buffer può essere fatto per mettere in pila gli "argomenti" che la funzione della libreria utilizzerà, proprio al punto posto dove si aspetta quella funzione. Poiché nessuno di questi utilizza l'area dello stack come codice , solo come data , la proprietà non eseguibile dello stack non ha importanza.
Note:
-
Non è necessario passare a start di una funzione di libreria. Funziona anche il salto a metà di una funzione. Le possibilità sono quasi infinite.
-
Le cose funzionano in modo simile per gli overflow che sostituiscono i puntatori alle funzioni, ad es. vtables per oggetti C ++ nell'heap. Ciò che importa è che il codice della libreria di destinazione trovi nello stack ciò che si aspetta come argomenti.
-
Le architetture non-x86 rendono le cose un po 'più complesse perché le convenzioni di chiamata per la maggior parte delle architetture RISC (ARM, Sparc, PowerPC, Mips ...) definiscono che i primi argomenti sono presi dai registri convenzionali, non letti da lo stack. Il principio di base rimane valido, tuttavia, ma significa che è più difficile creare un codice di exploit one-size-fits-all. L'attaccante deve mettere a punto più dettagli per quanto riguarda l'applicazione esatta che attacca.
-
L'attaccante deve essere in grado di prevedere, con buona probabilità, dove verrà caricato il codice della libreria nello spazio di indirizzamento dell'applicazione. ASLR è una funzionalità di sicurezza che sceglie l'indirizzo di caricamento più o meno a caso, proprio per rendere tali giochi più difficili per l'attaccante.