“To err is human, but to really foul up requires a computer” (Anonymous)
Figura 1: Aleph One
Dalla pubblicazione, nell’anno 1996, di “smashing the stack for fun and profit” di Aleph One, sono passati ad oggi ben 17 anni (vedi [1]).
La vulnerabilità “buffer overflow” è stato uno dei più comuni problemi di sicurezza degli ultimi dieci anni. E’ stato il mezzo più adottato da utenti anonimi per guadagnare il controllo parziale o completo di un sistema o di una intera rete informatica.
L’obiettivo di tale attacco è sovvertire il flusso di un programma in esecuzione sul sistema vittima per, nella migliore delle ipotesi, acquisirne il controllo. Tipicamente viene attaccato un programma eseguito con permessi amministrativi, infatti, tali permessi sono ereditati poi dai processi dell’attaccante. L’attacco consiste ‘nell’iniettare’ nel processo, codice macchina interpretabile dall’architettura vittima e, attraverso la corruzione di puntatori e memoria, forzarne l’esecuzione.
Ovviamente, fornire tutte le basi e metodologie per portare a termine questo tipo di operazione, non è attuabile in un semplice articolo. Tuttavia, ho cercato di fornire una adeguata quantità di materiale nella bibliografia. In questo articolo passeremo in rassegna le varie possibilità di attacco. Parleremo invece in modo approfondito, anche servendoci di codice di esempio, dei sistemi di protezione messi in atto negli anni successivi la pubblicazione di Aleph One. Per semplicità e agevolazione di apprendimento, questo articolo si riferisce a Linux. In linea di principio è comunque possibile estendere i principi affrontati anche a Microsoft Windows, sistema più complesso ma più vulnerabile.
Metodologie classiche
- Ogni volta che una funzione viene chiamata, viene salvato sullo stack un indirizzo di memoria relativo al punto in cui la chiamata è stata effettuata e a cui il programma fa riferimento per ritornare al codice chiamante. L’attacco tenta di corrompere tale locazione di memoria, in modo che il programma ritorni in un punto arbitrario, solitamente ad una locazione ove l’attaccante ha copiato il codice da eseguire. Il codice solitamente apre una comunicazione con l’attaccante.
- Quando si fa ricorso a puntatori a funzione inizializzandoli durante l’esecuzione, il loro indirizzo viene salvato nello stack, come per il caso precedente, la sua sovrascrittura forza il programma ad eseguire un altro codice, invece che quello programmato.
- In molti casi, nel codice sono presenti delle istruzioni chiamate “longjmp(<address>)”, esse saltano ad altre parti di codice, su windows ad esempio, istruzioni simili puntano verso gli “exception-handler”, funzioni usate per gestire errori logici del programma. Se qualche buffer viene corrotto sovrascrivendo l’entry point di queste funzioni, l’attaccante ha possibilità di eseguire del codice arbitrario.
Indagine
Il programma che segue ci aiuta a capire come lo stack è organizzato. Un alternativa è utilizzare direttamente un debugger.
#include <stdio.h> int main(int argc, char** argv) { void *ptr; char buffer[32]; printf("Address of ptr: 0x%X\n",&ptr); printf("Address of buffer: 0x%X\n",&buffer); printf("Address of argv: 0x%X\n",&argv); return 0; }
Output:
root@Kali:~# gcc test.c -ggdb -fno-stack-protector -z execstack root@Kali:~# ./a.out Address of ptr: 0xBF81A3CC Address of buffer: 0xBF81A38C Address of argv: 0xBF81A3E4
‘Buffer’ si trova ad un indirizzo più basso rispetto agli altri elementi, e se corrotto, può sovrascrivere sia le altre due variabili e proseguendo anche l’indirizzo di ritorno da main() salvato sullo stack. Inserendo una funzione “unsafe”, il codice diventa:
#include <stdio.h> int main(int argc, char** argv) { void *ptr; char buffer[64]; ptr = buffer; strncpy(buffer,argv[1],strlen(argv[1])); }
Con il debugger possiamo seguire e cercare di capire cosa succede quando strncpy() copia l’argomento passato tramite linea di comando nel buffer. Inserendo un argomento di dimensione maggiore di 32 byte il buffer viene sfondato. Con gdb sistemiamo un breakpoint subito dopo la chiamata a strncpy():
root@Kali:~# gdb -q a.out Reading symbols from /root/a.out...done. (gdb) b 9 Breakpoint 2 at 0x8048489: file test.c, line 9. (gdb) run $(python -c "print 'A'*4096") Starting program: /root/a.out $(python -c "print 'A'*4096") Breakpoint 1, main (argc=1094795585, argv=0x41414141) at test.c:9 9 } (gdb) x/16i $eip => 0x8048489 <main+61>: leave 0x804848a <main+62>: ret 0x804848b: xchg %ax,%ax 0x804848d: xchg %ax,%ax 0x804848f: nop 0x8048490 <__libc_csu_init>: push %ebp 0x8048491 <__libc_csu_init+1>: push %edi 0x8048492 <__libc_csu_init+2>: xor %edi,%edi 0x8048494 <__libc_csu_init+4>: push %esi 0x8048495 <__libc_csu_init+5>: push %ebx 0x8048496 <__libc_csu_init+6>: call 0x8048380 <__x86.get_pc_thunk.bx> 0x804849b <__libc_csu_init+11>: add $0x1b65,%ebx 0x80484a1 <__libc_csu_init+17>: sub $0x1c,%esp 0x80484a4 <__libc_csu_init+20>: mov 0x30(%esp),%ebp 0x80484a8 <__libc_csu_init+24>: lea -0xf4(%ebx),%esi 0x80484ae <__libc_csu_init+30>: call 0x80482d4 <_init> (gdb) x/32x $esp 0xbfffe310: 0xbfffe32c 0xbfffe59a 0x00001000 0x00000001 0xbfffe320: 0xb7e24b98 0xb7fdc858 0xbfffe58e 0x41414141 0xbfffe330: 0x41414141 0x41414141 0x41414141 0x41414141 0xbfffe340: 0x41414141 0x41414141 0x41414141 0x41414141 0xbfffe350: 0x41414141 0x41414141 0x41414141 0x41414141 0xbfffe360: 0x41414141 0x41414141 0x41414141 0x41414141 0xbfffe370: 0x41414141 0x41414141 0x41414141 0x41414141 0xbfffe380: 0x41414141 0x41414141 0x41414141 0x41414141 (gdb) info reg eax 0xbfffe32c -1073749204 ecx 0xb7ea6c20 -1209373664 edx 0x4141 16705 ebx 0xb7fc7000 -1208193024 esp 0xbfffe310 0xbfffe310 ebp 0xbfffe378 0xbfffe378 esi 0x0 0 edi 0x0 0 eip 0x8048489 0x8048489 <main+61> eflags 0x283 [ CF SF IF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 (gdb) si 0x0804848a 9 } (gdb) si 0x41414141 in ?? () (gdb)
main() tenta di far ritorno ad una locazione sovrascritta, e si tenta di leggere all’indirizzo 0×41414141, quindi il kernel ritorna un errore di segmentazione. Il programma viene terminato per evitare il crash del sistema.
Se avessimo sistemato al posto dei byte ’0×41′ un indirizzo che punta a codice eseguibile avremmo potuto eseguire codice arbitrario. Per esempio l’apertura di una shell. Il codice piazzato viene infatti denominato “shellcode” per questo motivo.
Figura 2: Buffer Overflow standard
Per molti anni è stata questa la tecnica utilizzata per prendere il controllo dell’host.
Negli anni sono nati diversi progetti con l’obiettivo di fermare attacchi di questo tipo a prescindere che il codice sia esente o no da problemi di sicurezza. Scrivere codice sicuro è attualmente ancora troppo costoso. Sostanzialmente esistono tre insiemi di metodi:
- Modifiche ai linguaggi di programmazione: boundary check sulle strutture di memoria.
Ovvero viene migliorata di fondo la sicurezza del linguaggio. Linguaggi recenti come Java e quelli del .NET si propongono, tra i vari obiettivi, anche quello di eliminare tutte le vulnerabilità che possono essere provocate dall’uso non corretto dello stesso. Questo è infatti denominato anche “gestito”. In realtà poi ci sono eccezioni, per esempio Java funziona necessariamente supportato da un engine, che però è scritta in C++. Ad ogni modo, in questi linguaggi tutte le operazioni su buffer di memoria vengono implementate nel linguaggio stesso, aumentandone sia la sicurezza che la facilità di utilizzo e diminuendo però le prestazioni, fattore che però diventa ampiamente trascurabile con l’aumentare della potenza di calcolo dei computer. Un’altro contro è la portabilità, il framework .NET di Microsoft ad esempio, limita la portabilità ai sistemi Windows.
- Modifiche ai compilatori: boundary check nei canali di registrazione dati e controllo.
Dopo il prologo della funzione l’indirizzo di ritorno viene salvato in una zona protetta. Durante l’epilogo l’indirizzo viene ripristinato (StackGuard, vedi [2]). Questa soluzione fu la prima adottata, ma si è rivelata onerosa come performance.
Un’altra protezione vede l’inserimento di un “canarino” tra l’indirizzo di ritorno e le variabili locali. Il canarino è una variabile che viene controllata prima e dopo l’esecuzione della funzione, se durante l’epilogo risulta modificata (nel caso precedente vi avremmo trovato quattro byte ’0×41′) il processo viene fermato. Il problema fondamentale è che ogni applicativo deve essere ricompilato in questo modo.
Inoltre, questa procedura non è sempre efficace, in molti casi la corruzione del frame pointer non determina anche la modifica della variabile canarino.
Nel seguente listato, tramite “objdump” analizziamo il programma di esempio (compilato senza l’opzione -fno-stack-protector per escludere StackGuard), si scorgono le istruzioni aggiuntive inserite. Dell’output, ci interessa la funzione main:
root@Kali:~# objdump -d -j .text a.out a.out: formato del file elf32-i386 Disassemblamento della sezione .text: ... 0804849c <main>: 804849c: 55 push %ebp 804849d: 89 e5 mov %esp,%ebp 804849f: 83 e4 f0 and $0xfffffff0,%esp 80484a2: 83 ec 70 sub $0x70,%esp 80484a5: 8b 45 0c mov 0xc(%ebp),%eax 80484a8: 89 44 24 1c mov %eax,0x1c(%esp) 80484ac: 65 a1 14 00 00 00 mov %gs:0x14,%eax 80484b2: 89 44 24 6c mov %eax,0x6c(%esp) 80484b6: 31 c0 xor %eax,%eax 80484b8: 8d 44 24 2c lea 0x2c(%esp),%eax 80484bc: 89 44 24 28 mov %eax,0x28(%esp) 80484c0: 8b 44 24 1c mov 0x1c(%esp),%eax 80484c4: 83 c0 04 add $0x4,%eax 80484c7: 8b 00 mov (%eax),%eax 80484c9: 89 04 24 mov %eax,(%esp) 80484cc: e8 9f fe ff ff call 8048370 <strlen@plt> 80484d1: 8b 54 24 1c mov 0x1c(%esp),%edx 80484d5: 83 c2 04 add $0x4,%edx 80484d8: 8b 12 mov (%edx),%edx 80484da: 89 44 24 08 mov %eax,0x8(%esp) 80484de: 89 54 24 04 mov %edx,0x4(%esp) 80484e2: 8d 44 24 2c lea 0x2c(%esp),%eax 80484e6: 89 04 24 mov %eax,(%esp) 80484e9: e8 a2 fe ff ff call 8048390 <strncpy@plt> 80484ee: 8b 54 24 6c mov 0x6c(%esp),%edx 80484f2: 65 33 15 14 00 00 00 xor %gs:0x14,%edx 80484f9: 74 05 je 8048500 <main+0x64> 80484fb: e8 50 fe ff ff call 8048350 <__stack_chk_fail@plt> 8048500: c9 leave 8048501: c3 ret 8048502: 66 90 xchg %ax,%ax 8048504: 66 90 xchg %ax,%ax 8048506: 66 90 xchg %ax,%ax 8048508: 66 90 xchg %ax,%ax 804850a: 66 90 xchg %ax,%ax 804850c: 66 90 xchg %ax,%ax 804850e: 66 90 xchg %ax,%ax ...
L’istruzione “mov %gs:0×14,%eax” piazza il canarino. Nell’epilogo, le istruzioni “xor %gs:0×14,%edx – je 8048500 <main+0×64> – call 8048350 <__stack_chk_fail@plt>” prelevano e controllano la variabile, se questa risulta essere stata modificata, viene fatta una chiamata a __stack_chk_fail().
Essendo Linux piacevolmente libero, l’implementazione della funzione è consultabile:
/* * Called when gcc's -fstack-protector feature is used, and * gcc detects corruption of the on-stack canary value */ void __stack_chk_fail(void) { panic("stack-protector: Kernel stack is corrupted in: %p\n", __builtin_return_address(0)); }
Questo codice chiama la più complicata funzione panic(). Entrambe sono definite nel file kernel/panic.c con lo scopo di arrestare il processo. (vedi [3])
Su Linux, stackguard in molti casi è in grado di riordinare gli elementi dello stack, o meglio, alloca gli elementi in modo tale che l’overflow di un buffer non possa sovrascrivere gli altri elementi eccetto l’indirizzo di ritorno ovviamente, che però è protetto dalla variabile canarino.
Per capire cosa si vuol dire, è sufficiente rieseguire il primo programma di esempio che stampa gli indirizzi delle variabili compilato con stackguard attivo:
Address of ptr: 0x29F57508 Address of buffer: 0x29F57510 Address of argv: 0x29F574F0
Buffer è allocato ad indirizzi più alti di memoria rispetto le altre due variabili, il suo sfondamento non comporta alcun rischio per queste.
Ad ogni modo, la corruzione di uno stack pointer o di un indirizzo di ritorno di una funzione non è l’unico modo per dirottare il flusso di esecuzione, anche le sezioni .got e .plt sono regioni di memoria sensibili (di fatto dove vengono eseguite call). Che funzione hanno qeste sezioni?
PLT aggiunge un livello di indirettezza alle chiamate di funzione, consentendo anche il lazy binding dei relativi indirizzi. In compilazione ogni chiamata ad una funzione condivisa (shared) è tradotta in una chiamata ad una entry della PLT. Quando la funzione viene chiamata, è effettuata una jmp indiretta ad una entry della GOT che conterrà la prima volta l’indirizzo di una entry della PLT che trasferisce il controllo al linker, per risoluzione e aggiornamento della GOT. Successivamente conterrà l’indirizzo effettivo della funzione. Per visualizzare le sezioni possiamo utilizzare objdump:
root@matteo-virtual-machine:~/Scrivania/exp# objdump -d -j .plt test test: formato del file elf64-x86-64 Disassemblamento della sezione .plt: 0000000000400460 <__stack_chk_fail@plt-0x10>: 400460: ff 35 a2 0b 20 00 pushq 0x200ba2(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8> 400466: ff 25 a4 0b 20 00 jmpq *0x200ba4(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10> 40046c: 0f 1f 40 00 nopl 0x0(%rax) 0000000000400470 <__stack_chk_fail@plt>: 400470: ff 25 a2 0b 20 00 jmpq *0x200ba2(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18> 400476: 68 00 00 00 00 pushq $0x0 40047b: e9 e0 ff ff ff jmpq 400460 <_init+0x20> 0000000000400480 <printf@plt>: 400480: ff 25 9a 0b 20 00 jmpq *0x200b9a(%rip) # 601020 <_GLOBAL_OFFSET_TABLE_+0x20> 400486: 68 01 00 00 00 pushq $0x1 40048b: e9 d0 ff ff ff jmpq 400460 <_init+0x20> 0000000000400490 <__libc_start_main@plt>: 400490: ff 25 92 0b 20 00 jmpq *0x200b92(%rip) # 601028 <_GLOBAL_OFFSET_TABLE_+0x28> 400496: 68 02 00 00 00 pushq $0x2 40049b: e9 c0 ff ff ff jmpq 400460 <_init+0x20> 00000000004004a0 <__gmon_start__@plt>: 4004a0: ff 25 8a 0b 20 00 jmpq *0x200b8a(%rip) # 601030 <_GLOBAL_OFFSET_TABLE_+0x30> 4004a6: 68 03 00 00 00 pushq $0x3 4004ab: e9 b0 ff ff ff jmpq 400460 <_init+0x20>
Oppure anche con:
root@matteo-virtual-machine:~/Scrivania/exp# objdump -x test ... 11 .plt 00000050 0000000000400460 0000000000400460 00000460 2**4 CONTENTS, ALLOC, LOAD, READONLY, CODE ... 21 .got 00000008 0000000000600ff8 0000000000600ff8 00000ff8 2**3 CONTENTS, ALLOC, LOAD, DATA ...
Infatti:
root@matteo-virtual-machine:~/Scrivania/exp# gdb -q test Reading symbols from /root/Scrivania/exp/test...done. (gdb) b main Breakpoint 1 at 0x4005ab: file test.c, line 4. (gdb) run Starting program: /root/Scrivania/exp/test Breakpoint 1, main (argc=1, argv=0x7fffffffe1c8) at test.c:4 4 { (gdb) x/32i $rip => 0x4005ab <main+15>: mov %fs:0x28,%rax 0x4005b4 <main+24>: mov %rax,-0x8(%rbp) 0x4005b8 <main+28>: xor %eax,%eax 0x4005ba <main+30>: lea -0x30(%rbp),%rax 0x4005be <main+34>: mov %rax,-0x38(%rbp) 0x4005c2 <main+38>: lea -0x38(%rbp),%rax 0x4005c6 <main+42>: mov %rax,%rsi 0x4005c9 <main+45>: mov $0x4006c4,%edi 0x4005ce <main+50>: mov $0x0,%eax 0x4005d3 <main+55>: callq 0x400480 <printf@plt> 0x4005d8 <main+60>: lea -0x30(%rbp),%rax 0x4005dc <main+64>: mov %rax,%rsi 0x4005df <main+67>: mov $0x4006da,%edi 0x4005e4 <main+72>: mov $0x0,%eax 0x4005e9 <main+77>: callq 0x400480 <printf@plt> 0x4005ee <main+82>: lea -0x50(%rbp),%rax 0x4005f2 <main+86>: mov %rax,%rsi 0x4005f5 <main+89>: mov $0x4006f3,%edi 0x4005fa <main+94>: mov $0x0,%eax 0x4005ff <main+99>: callq 0x400480 <printf@plt> 0x400604 <main+104>: mov $0x0,%eax 0x400609 <main+109>: mov -0x8(%rbp),%rdx 0x40060d <main+113>: xor %fs:0x28,%rdx 0x400616 <main+122>: je 0x40061d <main+129> 0x400618 <main+124>: callq 0x400470 <__stack_chk_fail@plt> 0x40061d <main+129>: leaveq 0x40061e <main+130>: retq 0x40061f: nop 0x400620 <__libc_csu_init>: mov %rbp,-0x28(%rsp) 0x400625 <__libc_csu_init+5>: mov %r12,-0x20(%rsp) 0x40062a <__libc_csu_init+10>: lea 0x2007e7(%rip),%rbp # 0x600e18 0x400631 <__libc_csu_init+17>: lea 0x2007d8(%rip),%r12 # 0x600e10 (gdb) x/8i 0x400480 0x400480 <printf@plt>: jmpq *0x200b9a(%rip) # 0x601020 <printf@got.plt> 0x400486 <printf@plt+6>: pushq $0x1 0x40048b <printf@plt+11>: jmpq 0x400460 0x400490 <__libc_start_main@plt>: jmpq *0x200b92(%rip) # 0x601028 <__libc_start_main@got.plt> 0x400496 <__libc_start_main@plt+6>: pushq $0x2 0x40049b <__libc_start_main@plt+11>: jmpq 0x400460 0x4004a0 <__gmon_start__@plt>: jmpq *0x200b8a(%rip) # 0x601030 <__gmon_start__@got.plt> 0x4004a6 <__gmon_start__@plt+6>: pushq $0x3
Sovrascrivendo una entry nella .got, anzichè fare una call ad una funzione linkata, per esempio printf(), verrà fatta una call al nostro codice.
- Modifiche al sistema operativo: ostacolare l’attacco.
Il metodo più diffuso è la marcatura dello stack come non eseguibile (PaX, vedi [4]). Questo ci impedisce di eseguire il codice che viene “iniettato”. Si tratta di una soluzione estremamente efficace. Tuttavia, è ancora possibile, seppur in modo diverso, sfruttare l’overflow. Se non è più possibile iniettare codice, l’unica soluzione è sfruttare quello già presente. Uno dei metodi si chiama “return-into-libc“. Conoscendo l’indirizzo di una funzione C, è possibile chiamare ad esempio system() piazzandone l’argomento contiguamente.
Figura 3: Return-to-libc attack schema
Questo sforzo si rivela poi inutile in realtà, questo perchè un’altra protezione chiamata “Address Space Layout Randomization” (PaX ASLR), randomizza gli indirizzi di caricamento delle sezioni dell’eseguibile .data, .text, librerie dinamiche, … ).
Per renderci conto di questo fatto è sufficiente leggere due o più volte la memoria di un processo.
root@Kali:~# cat /proc/self/maps 08048000-08053000 r-xp 00000000 08:01 524313 /bin/cat 08053000-08054000 r--p 0000a000 08:01 524313 /bin/cat 08054000-08055000 rw-p 0000b000 08:01 524313 /bin/cat 08c00000-08c21000 rw-p 00000000 00:00 0 [heap] b72e1000-b7412000 r--p 001ca000 08:01 1708501 /usr/lib/locale/locale-archive b7412000-b7612000 r--p 00000000 08:01 1708501 /usr/lib/locale/locale-archive b7612000-b7613000 rw-p 00000000 00:00 0 b7613000-b77c0000 r-xp 00000000 08:01 656147 /lib/i386-linux-gnu/libc-2.17.so b77c0000-b77c2000 r--p 001ad000 08:01 656147 /lib/i386-linux-gnu/libc-2.17.so b77c2000-b77c3000 rw-p 001af000 08:01 656147 /lib/i386-linux-gnu/libc-2.17.so b77c3000-b77c6000 rw-p 00000000 00:00 0 b77d5000-b77d6000 r--p 002fc000 08:01 1708501 /usr/lib/locale/locale-archive b77d6000-b77d8000 rw-p 00000000 00:00 0 b77d8000-b77d9000 r-xp 00000000 00:00 0 [vdso] b77d9000-b77f9000 r-xp 00000000 08:01 656123 /lib/i386-linux-gnu/ld-2.17.so b77f9000-b77fa000 r--p 0001f000 08:01 656123 /lib/i386-linux-gnu/ld-2.17.so b77fa000-b77fb000 rw-p 00020000 08:01 656123 /lib/i386-linux-gnu/ld-2.17.so bf93c000-bf95d000 rw-p 00000000 00:00 0 [stack] root@Kali:~# cat /proc/self/maps 08048000-08053000 r-xp 00000000 08:01 524313 /bin/cat 08053000-08054000 r--p 0000a000 08:01 524313 /bin/cat 08054000-08055000 rw-p 0000b000 08:01 524313 /bin/cat 092b5000-092d6000 rw-p 00000000 00:00 0 [heap] b7255000-b7386000 r--p 001ca000 08:01 1708501 /usr/lib/locale/locale-archive b7386000-b7586000 r--p 00000000 08:01 1708501 /usr/lib/locale/locale-archive b7586000-b7587000 rw-p 00000000 00:00 0 b7587000-b7734000 r-xp 00000000 08:01 656147 /lib/i386-linux-gnu/libc-2.17.so b7734000-b7736000 r--p 001ad000 08:01 656147 /lib/i386-linux-gnu/libc-2.17.so b7736000-b7737000 rw-p 001af000 08:01 656147 /lib/i386-linux-gnu/libc-2.17.so b7737000-b773a000 rw-p 00000000 00:00 0 b7749000-b774a000 r--p 002fc000 08:01 1708501 /usr/lib/locale/locale-archive b774a000-b774c000 rw-p 00000000 00:00 0 b774c000-b774d000 r-xp 00000000 00:00 0 [vdso] b774d000-b776d000 r-xp 00000000 08:01 656123 /lib/i386-linux-gnu/ld-2.17.so b776d000-b776e000 r--p 0001f000 08:01 656123 /lib/i386-linux-gnu/ld-2.17.so b776e000-b776f000 rw-p 00020000 08:01 656123 /lib/i386-linux-gnu/ld-2.17.so bfe3c000-bfe5d000 rw-p 00000000 00:00 0 [stack]
Non vi è modo di sapere, ad eccezzione delle sezioni .text, che indirizzi saranno utilizzati per l’allocazione dello stack, heap etc. Ad ogni esecuzione, tali indirizzi saranno diversi.
PaX, StackGuard e PaX ASLR utilizzati in combinata, come avviene al giorno d’oggi sulle moderne distribuzioni, rendono impraticabili la maggior parte delle tecniche di attacco. Anche attacchi molto evoluti come il “return-into-libc” e la sua evoluzione “Return-oriented-programming“, sono difficilmente applicabili. Tuttavia, rimangono svariati casi, soprattutto su Microsoft Windows, ove l’attacco è attuabile con successo.
Nella prossima parte ci occuperemo di questi casi, cercando di capire tecniche complesse, essenzialmente di natura mista, che fanno un uso avanzato del codice già presente.
Es. Dimostrazione exploit remoto server web.
Viene impiegata una schellcode port binding. Sfruttando la cattiva gestione del buffer che contiene la stringa di richiesta del client da parte del web server, è possibile eseguire del codice arbitrario. Nell’esempio viene fatta una veloce analisi per individuare l’indirizzo di ritorno da utilizzare nell’exploit che viene poi eseguito tramite uno script python.
*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