Magazine Salute e Benessere

Smashing the stack oggi, PARTE 1: Introduzione

Creato il 27 maggio 2013 da Matteo Tosato @MatteoTosato87

To err is human, but to really foul up requires a computer” (Anonymous)

Figura 1: Aleph One

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

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, è eff ettuata 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 e ffettivo 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 <[email protected]>
   0x400486 <printf@plt+6>:	pushq  $0x1
   0x40048b <printf@plt+11>:	jmpq   0x400460
   0x400490 <__libc_start_main@plt>:	jmpq   *0x200b92(%rip)        # 0x601028 <[email protected]>
   0x400496 <__libc_start_main@plt+6>:	pushq  $0x2
   0x40049b <__libc_start_main@plt+11>:	jmpq   0x400460
   0x4004a0 <__gmon_start__@plt>:	jmpq   *0x200b8a(%rip)        # 0x601030 <[email protected]>
   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

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



Potrebbero interessarti anche :

Ritornare alla prima pagina di Logo Paperblog

Possono interessarti anche questi articoli :