Già in altri articoli ci eravamo occupati di buffer overflow, e, per continuare la serie, ho deciso che nelle prossime settimane mi occuperò di diverse tipologie, partendo dallo Stack Buffer Overflow, passando poi all’Heap Overflow, Use After Free ed alcune tecniche di difesa che sono in atto ai giorni nostri.
Per chi abbia bisogno di un ripasso generico, ricordo l’articolo Introduzione al Buffer Overflow che consiglio vivamente (oltre che ai soliti approfondimenti a fondo pagina).
Gli esercizi spiegati di seguito sono presi da ExploitExercise.
Una piccola premessa, prima di iniziare i vari esercizi. Per eseguirli ho utilizzato l’ultima versione di Kali Linux x86, con le opportune modifiche. Per prima cosa, ho disabilitato la randomizzazione degli indirizzi con questo comando
Mentre per la compilazione del codice, il comando sarà
gcc -mpreferred-stack-boundary=2 -fno-stack-protector -no-pie -z execstack –g programma.c
Dove:
Ho inoltre modificato la visione di GDB per rendere più chiare le sezioni di memoria ed eventuali comandi, tramite la configurazione seguente
In modo che ogni volta che eseguirò gdb con questo comando
gdb -tui ./programm
esso sarà eseguito in maniera grafica, con la prima sezione riguardante i registri, la seconda il codice del programma e la terza i comandi di GDB
Per analizzare i dump del sistema ho invece usato i comandi
kali@kali:~/Desktop/Protostar$ sudo echo "/tmp/core-%e-%s-%u-%g-%p-%t" > /proc/sys/kernel/core_pattern
kali@kali:~/Desktop/Protostar$ ulimit -c unlimited
Ma torniamo all’esercizio
La funzione vulnerabile è a riga 11, infatti, dando un occhio alla documentazione di gets, possiamo vedere che è deprecata
Visto che per superare la sfida è necessario modificare la variabile modified sarà sufficiente inserire una stringa con più di 64 caratteri.
In questo caso _modified _non solo dev’essere modificata, ma deve essere inserito un valore preciso.
Poiché _modified _e _buffer _saranno entrambi nello stack, uno subito dopo l’altro, andiamo a vedere con GDB cosa succede andando ad inserire una stringa contenent 64 A e 4 B.
Nello screenshot seguente abbiamo:
Questo conferma la nostra ipotesi che sono vicine e basterà iniettare 0x61626364 dopo le 64 A per superare la sfida
In questo esercizio come prima cosa viene controllato se esiste la variabile d’ambiente GREENIE, poi viene copiata in buffer ed infine controllato se la variabile modified è impostata al valore 0x0d0a0d0a. Essendo copiata la variabile dentro buffer, basterà impostare il payload all’interno della stessa.
kali@kali:~/Desktop/Protostar$ GREENIE=`perl -e 'print "A"x64 . "\x0a\x0d\x0a\x0d"'`
kali@kali:~/Desktop/Protostar$ export GREENIE
In questo esercizio viene introdotta una piccola differenza, ossia quella di una seconda funzione oltre al main. Guardando il codice, si può notare che la funzione win in realtà non viene mai chiamata, infatti eseguendo il codice
oltre a prendere buffer esegue fp, che però non punta a nulla, essendo una B ripetuta quattro volte.
L’obiettivo in questo caso è quindi quello di riuscire a chiamare l’altra funzione, che ci permetterà di superare la sfida. Per farlo, ci serve sapere l’indirizzo di memoria in cui risiede win, tramite objdump
Ovviamente è possibile solo grazie al fatto che la randomizzazione degli indirizzi è disattivata, altrimenti questo cambierebbe ad ogni esecuzione.
Generiamo lo shellcode
Aumentando la difficoltà, ora non c’è più nessuna chiamata ad un puntatore e si deve trovare un’altra strada. Quella più comoda in questo caso è andare a sovrascrivere direttamente il registro EIP, in modo che una volta inserito il buffer, invece che chiudere il programma, vada a chiamare la funzione win.
Per trovare l’offset tra il buffer e EIP ci può venire in aiuto il tool pattern_create (ma è possibile anche riempire di lettere casuali e indagare con GDB)
Per generare il pattern, il comando è il seguente
kali@kali:~/Desktop/Protostar$ /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 100<br>Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2A
Lo vado poi ad inserire nel programma generando un segmentation fault, che andrò ad indagare tramite GDB
GDB avvisa che l’errore è che non può accedere all’indirizzo di memoria 0x41346341
Per trovare l’offset, ci basterà eseguire il tool _pattern_offset _con l’indirizzo trovato.
kali@kali:~/Desktop/Protostar$ /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q 0x41346341<br>[*] Exact match at offset 72
Ed ecco che, una volta conosciuta la lunghezza dell’offset, ci basterà usare nuovamente objdump per trovare l’indirizzo della funzione win e sovrascrivere EIP con quello.
Bene, ora non abbiamo proprio nulla e dobbiamo trovare un’altra strada. Ce ne potrebbero essere diverse, ad esempio quella in cui, modificando il flusso del codice e sovrascrivendo EIP, iniettiamo una nuova funzione che stampa “You win”.
Per prima cosa, bisogna creare lo shellcode della funzione “You win” (utilizzerò lo stesso della soluzione #3 di questo articolo )
Per convertire il binario in esadecimale, si può usare uno script in bash, ad esempio
for i in $(objdump -d $1 -M intel |grep "^ " |cut -f2); do
<code>echo -n '\x'$i</code>
done;
echo
Dall’esercizio di prima sappiamo che l’offset è 72, il payload è lungo 40, quindi ci servono 32 caratteri NOP per sovrascrivere l’EIP.
In questo caso non ha funzionato perché alla fine del payload c’è un carattere \x0a, che interrompe l’esecuzione. Proviamo a cancellarli, aggiungere due NOP ed eseguire nuovamente
PS: l’indirizzo bffff2c4 è la posizione di partenza della variabile buffer nello stack. La si può trovare sia con GDB (inserendo un breakpoint e stampandola, ma attenzione che GDB setta delle variabili d’ambiente che spostano di poco i valori della memoria rispetto alla mera esecuzione sul terminale, oppure andando a modificare direttamente il codice e stampando l’indirizzo di buffer)
In questo caso, sembra che tutte le tecniche di prima non possano essere eseguite. La condizione if a riga 17 controlla se l’indirizzo ret non inizi con 0xbf, e noi l’abbiamo sempre sovrascritto con indirizzi di quel tipo, proprio perché erano presenti nello stack. Infatti, se andiamo ad utilizzare la tecnica precedente
Finiamo quindi direttamente dentro la condizione e il programma termina.
Per bypassare il controllo useremo un’altra tecnica, ossia ret2libc. Questa tipologia ci può essere utile perché non andremo ad iniettare sull’EIP un indirizzo di memoria preso da una variabile come nell’esercizio precedente, ma andremo ad utilizzare chiamate di sistema che sono già in memoria al momento dell’esecuzione, come _system _ed exit. L’idea alla base è quindi quella di inserire i soliti NOP, aggiungere la chiamata a system, la chiamata ad exit ed infine la chiamata ad un comando già presente in memoria, come /bin/bash (per chi vuole approfondire consiglio questo paper).
Per ottenere questi tre indirizzi è sufficiente mandare in crash il programma ed analizzare il dump con GDB
Il comando _x/s *((char **)environ) _stampa l’indirizzo in memoria in cui risiedono le varie variabili d’ambiente.
Ora che li abbiamo tutti, costruiamo il nostro shellcode:
L’unione in pipe tra (cat shellcode6; cat) e stack6 è stata fatta poiché se eseguito come gli altri esercizi la shell si chiude immediatamente senza aspettare nessun comando.
In quest’ultimo esercizio la differenza con il precedente è la condizione, in quanto ora verifica che ret non inizi con 0xb, quindi l’attacco ret2libc non funzionerà più.
Dobbiamo usare una tecnica diversa, ossia quella del Return Oriented Programming. È simile a ret2libc, ma oltre che iniettare le precedenti istruzioni, dovrà esserne iniettata un’altra, una chiamata ad un’istruzione RET, che ci permetterà di bypassare la condizione. Per trovare RET è sufficiente lanciare objdump e greppare l’istruzione.
Siamo passati da una semplice modifica di una variabile a tecniche più avanate come ret2libc e ROP. Quest’ultime due ricordo che funzionano anche se lo stack non permette l’esecuzione, quindi anche senza l’opzione -z execstack.
Nei prossimi articoli andremo ad approfondire altre tecniche di Buffer Overflow, seguendo sempre gli esercizi di Protostar. Alcuni articoli che consiglio per approfondire la materia sono: