Lo scopo di questo articolo è spiegare come scrivere shellcode per un sistema Linux a 64 bit e come realizzare un exploit basato sul buffer overflow.
Le informazioni fornite in questo articolo sono a scopo esclusivamente didattico. L'autore non si assume alcuna responsabilità nel caso di utilizzo illecito di queste conoscenze.
1. Il programma vulnerabile
Nel nostro esempio scriveremo un semplice programma che accetta una stringa come parametro, crea un buffer di 100 caratteri nello stack e vi copia la stringa sopracitata. Senza controllarne la lunghezza.
#include int main(int argc, char *argv[]) { char buffer[100]; if(argc<2) return -1; strcpy(buffer, argv[1]); return 0; }
Come si può facilmente prevedere, quando una stringa di lunghezza superiore a 100 caratteri viene passata al programma, altre porzioni di memoria vengono sovrascritte. Se sovrascriviamo quelle giuste, con le istruzioni appropriate, possiamo far fare al programma quello che vogliamo.
2. Lo shellcode
Lo scopo del nostro exploit è di far stampare al programma vulnerabile la stringa hello sullo schermo. Il nostro primo passo consiste quindi nel creare una sequenza di istruzioni macchina che esegui questo compito.
2.1. Scrivere il programma in Assembly
Scriviamo un programma in x64 assembly che stampi una riga su schermo.
jmp short ender starter: xor rax, rax xor rdi, rdi xor rsi, rsi xor rdx, rdx mov al, 1 mov dil, 1 pop rsi mov dl, 5 syscall xor eax, eax mov al, 60 xor ebx,ebx syscall ender: call starter db 'hello'
Avrete notato che l'esecuzione salta subito alla penultima riga, per poi tornare alla seconda. Questo trucco ci permette di ottenere l'indirizzo della stringa hello. Non appena chiameremo la funzione push, questa ci restituerà un puntatore alla stringa.
Il programma poi azzera i registri che verranno usati in seguito.
Ora bisogna stampare la stringa sullo schermo. Per questo serve la chiamata di sistema write.
Le chiamate di sistema sono elencate in un header chiamato unisdt. Nel nostro caso la chiamata write ha il numero 1.
La chiamata di sistema avviene attraverso l'istruzione syscall.
Il registro rax contiene il numero della chiamata.
Il registro rbx dice a write dove scrivere, nel nostro caso stdout (1).
Il registro rdx contiene la lunghezza della stringa.
Le ultime tre righe di codice terminano la schellcode.
Viene scritto solo sui byte meno significativi dei registri per evitare byte nulli nello shellcode. Questo accorgimento è di cruciale importanza, come diventerà più chiaro in seguito.
Infatti lo shellcode dovrà essere passato al programma vulnerabile come se fosse una stringa. Un eventuale byte nullo verrebbe interpretato come la fine della stringa, e lo shellcode completo non potrebbe essere iniettato nel programma vulnerabile.
2.2. Testare il programma
Ok, funziona.
2.2. Ricavare il codice macchina
Ora abbiamo lo shellcode. Si noti l'assenza di byte nulli.
Aggiungi un commento