Corso UIC Newbies 13
From UIC
SEH (Strucured Exception handle) Reversing
Contents |
| Corso UIC Newbies 13 | |
|---|---|
| Author: | Pn |
| Email: | |
| Website: | No Site |
| Date: | 28/08/2006 (dd/mm/yyyy) |
| Level: |
|
| Language: | Italian |
| Comments: | |
Introduzione
Cos'è un' eccezione? Spero che vi siate posti questa domanda ogni volta, che in ollydbg dovete premere alt+F(X), per passarne una. Qui alla UIC c'è già un documento sull'argomento ,scritto da Quequero, dove non c'è un analisi reversing-side, ma asm programming-side, da cui copierò tutta la parte teorica e c'è anche un doc di Ntoskrnl per il c++).
Tools
Essay
Avete mai visto sul vostro bel pc, un messaggio di errore di windows, che ci avverte che il programma è terminato a causa di un errore? Questo succede perche' nel programma o non vi è un gestore di eccezione, o questo non ha svolto il suo compito.
Teoria
Le eccezioni sono usate parecchio dal sistema, un esempio è questo: supponiamo che un thread abbia bisogno di più stack, l'idea principale sarebbe quella di monitorare lo spazio che sta usando e dargli più stack quando non c'è più memoria disponibile, ma ciò porterebbe via molte risorse oltre che spazio nel programma finale, allora si installa un Exception Handler (ovvero un Gestore di Eccezioni) che con pochissime risorse e poco spazio controlla una sola cosa: appena il thread ha raggiunto il limite massimo di stack il sistema segnala un'errore che verrà controllato dal NOSTRO handler e non da quello di win, il nostro handler provvederà quindi ad allargare lo stack, il tutto succede in modo enormemente veloce e senza spreco inutile di risorse, ma entriamo nel dettaglio. Windows si preoccupa di monitorare con un Exception Handler ogni thread avviato, la posizione di questo Exception Handler si trova in fs:[0], ogni errore nel codice fa si che il controllo passi a questo Handler che controllerà in sequenza queste cose (grazie a J. Gordon per l'elenco):
- Windows controlla se ci sono altri Handler disposti a controllare l'eccezione e se il programma è debuggato allora notifica al debugger che è accaduta un'eccezione (ecco spiegata la funzione "faults on" di SoftIce, e gli errori da passare al processo in ollydbg :)
- Se non ci sono debugger presenti e se il programma non è neanche sotto debugging allora il sistema si preoccupa di vedere se noi abbiamo installato un Handler sul thread (per-Thread Handler) e va quindi a spulciare nel TIB (Thread Information Block) che si trova all'indirizzo fs:[0]
- Se esiste un per-Thread Handler disposto a dialogare con l'eccezione allora gli passa il totale controllo, ma questo Handler potrebbe a sua volta passare il controllo ad altri Handler della catena
- Se nessun Handler dialoga con l'eccezione ma il programma è sotto debug allora il sistema prova nuovamente a notificare l'evento al debugger
- Se non accade nulla di positivo allora il controllo passa al Final Handler (che dobbiamo settare noi)
- Se il nostro Final Handler non è in grado di fare nulla allora il controllo passa al System Final Handler, verrà mostrato il solito box di GPF oppure verrà attivato un debugger, se il programma non può passare il controllo al debugger allora un sano ExitProcess farà la sua comparsa :)
- Prima di terminare definitivamente il programma, il sistema si preoccupa di ripulire lo stack nella zona dove è accaduta l'eccezione.
Prima di iniziare con qualche esempio di codice dobbiamo fare una distinzione, di Handler infatti ne esistono due tipi, il primo si chiama Final Handler e si installa nel Thread principale del programma ed il secondo si chiama per-Thread Handler che come dice la parola stessa si installa all'inizio di ogni thread, facciamo un esempio di Final Handler:
push offset Final_Handler
call SetUnhandledExceptionFilter
....snip....
....normale codice del programma....
....snip....
call ExitProcess
;----------------------
Final_Handler:
....snip....
....codice di chiusura....
....qualche altra cosa....
....un box di saluti....
....snip....
mov eax, X ; eax = 0 ---> Mostra il box di chiusura
; eax = 1 ---> Nascondi il box di chiusura
; eax = -1 ---> Ricarica il contesto e continua
ret
Cosa succede? All'inizio del programma viene installato un Final Handler che resterà sul Thread principale, mi sembra palese ricordare che prima lo installate meglio è, se durante il programma dovesse incorrere un errore che noi non ci aspettiamo allora il sistema seguirà gli step di sopra, se supponiamo di aver settato SoftIce a "faults off" allora il processo di gestione dell'Exception si fermerà al 5° Step e poi il programma verrà chiuso. Come vedete il Final Handler è l'ultima spiaggia nel quale il sistema cerca riparo, ma fa sempre uso di API e cmq il livello di libertà che noi abbiamo resta molto basso. Ora prima di utilizzare un per-Thread Handler, vediamo cosa succederebbe se esso non ci fosse :
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
ASSUME FS:NOTHING ;è una direttiva di masm
.data
Brk1 db "Io non dovrei apparire :P",0
Caption db "seh",0
.code
start:
xor eax,eax
div eax ;genera un eccezione, per capire quale continuate ;P
invoke MessageBox, NULL, addr Brk1, addr Caption, MB_OK
invoke ExitProcess,0
end start
Eseguendo questo semplice programma, viene generata un'eccezione, che windows prontamente vi segnala (se usate win xp). Ora vediamo l'installazione di un per-Thread Handler:
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
ASSUME FS:NOTHING ;è una direttiva di masm
.data
Brk1 db "Ora dovrei apparire :P",0
Caption db "seh",0
.code
start:
push offset gestore ; mette nello stack l'offset del gestore d'eccezione
push fs:[0] ; salva il vecchio valore di fs:[0]
mov fs:[0], esp ; per poi storarci l'addr del nostro gestore
invoke MessageBox, NULL, addr Brk1, addr Caption, MB_OK
pop fs:[0]
add esp,4
invoke ExitProcess,0
;gestore dell'eccezione
gestore:
mov eax,0 ; se al ret eax=0 => l'eccezione è stata corretta
ret
end start
Questo codice è solo un esempio, infatti qui non avviene alcuna eccezione.
Ora che sappiamo come si installano questi due tipi di Handler, iniziamo a studiare la Struttura di Errore.
Questa è una parte importantissima, perchè con essa, possiamo sapere dove è accaduta e di che tipo è un'eccezione.
| EXCEPTION_RECORD +0 | ExceptionCode |
| EXCEPTION_RECORD +4 | ExceptionFlag |
| EXCEPTION_RECORD +8 | NestedExceptionRecord |
| EXCEPTION_RECORD +0C | ExceptionAddress |
| EXCEPTION_RECORD +10 | NumberParameters |
| EXCEPTION_RECORD +14 | AdditionalData |
ottenere questi dati è semplicissimo, all'interno del codice del nostro Exception Handler, basta fare:
e concepire che:
| Edx+0 | ExceptionCode |
| Edx+4 | ExceptionFlag |
| Edx+8 | NestedExceptionRecord |
| Edx+C | ExceptionAddress |
| Edx+10 | NumberParameters |
| Edx+14 | AdditionalData |
così un semplicissimo:
può servire nel conoscere il tipo d'eccezione, ed un altrettanto semplice:
serve a farci conoscere dove è avvenuta l'eccezione, bello non credete?
Ora che conosciamo anche la struttura di errore, vi mostro quali sono i più comuni codici di errore e quindi faremo qualche esperimento:
C0000025h ; Non continuabile, non si deve dialogare con l'Exception e si deve chiudere il prg
C0000026h ; Interrupt Exception
80000003h ; BreakPoint occorso (INT3)
C0000095h ; Integer Overlow
80000004h ; Single Step
C0000005h ; Read or Write Memory Violation
C000001Dh ; Invalid Opcode
C00000FDh ; Lo stack ha raggiunto la massima dimensione disponibile
80000001h ; Violazione di pagina settata con VirtualAlloc
Solo due dword necessitano di una spiegazione, la prima è C00000095h, ovvero Integer Overflow, questa eccezione accade quando, durante una operazione un registro si trova a dover contenere una cifra troppo grande; il secondo codice è 80000004h, ovvero Single Step, l'eccezione di Single Step accade quando, viene settato il TrapFlag; infatti se il processore vede che questo flag è settato, genera questo tipo d'eccezione, per ogni istruzione eseguita. Per una lista completa del tipo d'eccezioni, basta cercare su google (ma dai???). Bene, ora che sappiamo come installare un gestore d' eccezione, e come è formata la struttura d'errore, sperimentiamo l'uso concreto di un Exception Handler per-Thread:
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
ASSUME FS:NOTHING ;è una direttiva di masm
.data
Brk1 db "Non dovrei apparire :P",0
Brk2 db "Int3 ha generato un errore",0
Caption db "seh",0
.code
start:
push offset gestore ; già
push fs:[0] ; dovreste sapere
mov fs:[0],esp ; cosa fanno queste istruzioni
int 3
invoke MessageBox, NULL, addr Brk1, addr Caption, MB_OK
invoke ExitProcess, 0
;gestore dell'eccezione
gestore:
mov edx,dword ptr[ebp+8]
cmp dword ptr[edx], 80000003h ; 80000003h corrisponde ad int3
jnz esco
invoke MessageBox, NULL, addr Brk2, addr Caption, MB_OK
esco:
invoke ExitProcess,0 ; ho messo ExitProcess per terminare l'esecuzione
ret
end start
Come notate il programma ora non crasha, anzi vi appare anche una bella MessageBox. Dopo aver sperimentato e capito cosa sono gli Handler, approfondiamo la struttura d' errore, per ora conosciamo solo due parametri: ExceptionCode, e ExceptionAddress.... Sono i più intuitivi, ma gli altri?
Exception flag:
- 0 ----> Eccezione continuabile, possiamo ripararla
- 1 ----> Eccezione non continuabile, non possiamo ripararla
- 2 ----> Lo stack è dipanato, non possiamo (e NON dobbiamo) neanche provare a ripararla
Nested exception record: punta ad un altro EXCEPTION_RECORD, nel caso che il nostro Handler generasse da solo un'eccezione :))
NumberParameters: Numero di DWORD da seguire in Additional Information
Additional information: Sono informazioni inviate dall'applicazione quando si chiama RaiseException, se invece il codice di errore è C0000005h allora i valori contenuti dalla prima DWORD saranno:
- 0 ----> Violazione di lettura
- 1 ----> Violazione di scrittura
Ecco la parte più interessante delle SEH, infatti al momento della chiamata al per-Thread Handler, ESP+C punta al Context che contiene tutte le specifiche dei registri al momento dell'eccezione (il context si ottiene anche chiamando GetThreadContext) ed è:
DEBUG REGISTERS
+4 debug register #0
+8 debug register #1
+C debug register #2
+10 debug register #3
+14 debug register #6
+18 debug register #7
FLOATING POINT / MMX registers
+1C ControlWord
+20 StatusWord
+24 TagWord
+28 ErrorOffset
+2C ErrorSelector
+30 DataOffset
+34 DataSelector
+38 FP registers x 8 (10 bytes each)
+88 Cr0NpxState
SEGMENT REGISTERS
+8C gs register
+90 fs register
+94 es register
+98 ds register
ORDINARY REGISTERS
+9C edi register
+A0 esi register
+A4 ebx register
+A8 edx register
+AC ecx register
+B0 eax register
CONTROL REGISTERS
+B4 ebp register
+B8 eip register
+BC cs register
+C0 eflags register
+C4 esp register
+C8 ss register
Questa in pratica è la "sala dei bottoni" del vostro processore, da qua dentro fate proprio tutto :) anche quello che non potreste fare altrimenti :). Esp+8 punta invece ad una NOSTRA struttura di errore, mentre Esp+4 punta all'EXCEPTION_RECORD. Finito qui? Niente affatto, se succede un qualche tipo di errore cosa facciamo? Beh, prima di chiudere il programma dobbiamo constatare se l'eccezione è riparabile, se si allora possiamo fare qualcosa di davvero carino, appena viene invocato l'Handler ESP+8 punta alla nostra struttura di errore che è così formata:
| STRUTTURA+0 | Pointer to next ERR structure |
| STRUTTURA+4 | Pointer to own exception handler |
| STRUTTURA+8 | Code address of "safe-place" for handler |
| STRUTTURA+C | Information for handler |
| STRUTTURA+10 | Area for flags |
| STRUTTURA+14 | Value of EBP at safe-place |
vi garantisco che non è poco, infatti i valori contenuti da STRUTTURA+8 e STRUTTURA+14h sono importantissimi, ci consentono infatti di far continuare l'esecuzione del programma in un "luogo-sicuro" (se l'eccezione non è recuperabile allora si chiama RtlUnwind e pazienza :) quindi la prima cosa da fare è vedere se l'eccezione è continuabile, se si allora si estrae dalla nostra struttura di errore l'indirizzo del luogo-sicuro (STRUTTURA+8) e quello del nostro nuovo EBP (STRUTTURA+14h), quindi si estrae il CONTEXT e da li' dentro si manipola CONTEXT+B4 (registro ebp) ponendolo uguale al valore suggerito da STRUTTURA+8 e poi si manipola CONTEXT+B8 (registro eip) ponendolo uguale a STRUTTURA+14h, in questo modo l'EIP verrà cambiato ed il codice continuerà allegramente dove è in grado di prolificare :), vi riporto del codice di J. Gordon:
PUSH EBP ; salva ebp, usato per indirizzare lo Stack Frame
MOV EBP,ESP ; usa EBP come stack frame pointer
SUB ESP,40h ; 16 DWORD di spazio per i dati locali e la struttura di Errore
; Installa l'handler
PUSH EBP ; STRUTTURA+14h salva EBP che si trova al luogo-sicuro
PUSH 0 ; STRUTTURA+10h area per i flags
PUSH 0 ; STRUTTURA+0Ch informazioni per l'handler
PUSH OFFSET SAFE_PLACE ; STRUTTURA+8h nuovo eip al luogo-sicuro
PUSH OFFSET HANDLER ; STRUTTURA+4h indirizzo dell'handler
PUSH FS:[0] ; STRUTTURA+0h tieni la STRUTTURA all'inizio della catena
MOV FS:[0],ESP ; Punta alla STRUTTURA appena creata sullo stack
... ; Il codice protetto dall'Handler va qui
...
...
JMP >L10 ; Se non ci sono state eccezioni salta
SAFE_PLACE:
L10:
POP FS:[0]
MOV ESP,EBP
POP EBP
RET
;*****************
HANDLER:
...
...
...
...
RET
Reversing
Ora finalmente si inizia a reversare:
.model flat,stdcall
option casemap:none
include\masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
ASSUME FS:NOTHING ;è una direttiva di masm
.data
Brk1 db "Ora non dovrei apparire :P",0
Brk2 db "Invece sono apparso io :D",0
Caption db "seh",0
start:
push offset gestore ; le solite
push fs:[0] ; istruzioni per
mov fs:[0],esp ; l'installazione dell'handler
int 3
invoke MessageBox, NULL, addr Brk1, addr Caption, MB_OK
invoke ExitProcess,0
arrivo:
invoke MessageBox, NULL, addr Brk2, addr Caption, MB_OK
pop fs:[0]
add esp,4
invoke ExitProcess,0
;gestore dell'eccezione
gestore:
mov eax,dword ptr[esp+0ch] ; eax punta al context
mov dword ptr[eax+0b8h],offset arrivo ; eip = offset di arrivo
xor eax,eax
ret
end start
Eseguendo il programma vi accorgerete che apparire la seconda MessageBox, invece della prima, questo succede perchè int 3,genera un' eccezione. Se provate a steppare, vi accorgerete che stepperete attraverso l'int3, facendo apparire la prima MessageBox, ora provate a mettere invece di int3 "xor eax, eax" "div eax"; come notate olly non vi farà più steppare e vi dirà che per passare l'eccezione, dovrete premete alt+f7/f8/f9: f9 fa proseguire l'esecuzione del processo, premendolo ci appare la messagebox giusta, ma non ci permette di analizzare il codice. f7/f8, vi fanno apparire ciò:
7C91EAF3 PUSH ECX
7C91EAF4 PUSH EBX
7C91EAF5 CALL ntdll.7C9477C1 ; qui viene interpellato il nostro gestore d'eccezione, se
; questo
7C91EAFA OR AL,AL ; corregge l'errore, eax viene settato ad 1 e l'esecuzione
; continua
7C91EAFC JE SHORT ntdll.7C91EB0A ; tramite ZwContinue,altrimenti si esegue
; ZwRaiseException
7C91EAFE POP EBX
7C91EAFF POP ECX
7C91EB00 PUSH 0
7C91EB02 PUSH ECX
7C91EB03 CALL ntdll.ZwContinue ; ZwContinue resumes execution of a saved execution context
; (continua l'esecuzione)
7C91EB0A POP EBX
7C91EB0B POP ECX
7C91EB0C PUSH 0
7C91EB0E PUSH ECX
7C91EB0F PUSH EBX
7C91EB10 CALL ntdll.ZwRaiseException ;ZwRaiseException raises an exception (va al prossimo
; gestore d'eccezione, se questo non è presente il processo
; viene chiuso).
Ora se steppate, vedrete che arrivando a ZwContinue, e vi appare la nostra bella MessageBox. Ora approfondiamo di più:
00401005 PUSH DWORD PTR FS:[0] ; cmq basta che verificate la presenza di:
0040100C MOV DWORD PTR FS:[0],ESP ; push XXXX; push fs:[0]; mov fs:[0], esp
00401013 XOR EAX,EAX
00401015 DIV EAX
00401017 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
00401019 PUSH source.00403035 ; |Title = "seh"
0040101E PUSH source.00403000 ; |Text = "Ora non dovrei apparire :P"
00401023 PUSH 0 ; |hOwner = NULL
00401025 CALL ; \MessageBoxA
0040102A PUSH 0 ; /ExitCode = 0
0040102C CALL ; \ExitProcess
00401031 PUSH 0 ;/Style = MB_OK|MB_APPLMODAL
00401033 PUSH source.00403035 ; |Title = "seh"
00401038 PUSH source.0040301B ; |Text = "Invece sono apparso io :D"
0040103D PUSH 0 ; |hOwner = NULL
0040103F CALL ; \MessageBoxA
00401044 POP DWORD PTR FS:[0]
0040104B ADD ESP,4
0040104E PUSH 0 ; /ExitCode = 0
00401050 CALL ; \ExitProcess
00401055 MOV EAX,DWORD PTR SS:[ESP+C] ; Structured exception handler (lo mette olly)
00401059 MOV DWORD PTR DS:[EAX+B8],source.00401031 ;eip = 401031
00401063 XOR EAX,EAX ; eccezione risolta, si può
; continuare
00401065 RETN
Cosa avviene in caso di eccezione? Semplice, come precedentemente spiegato, il controllo passa in mano all'offset di gestore, che in questo caso è 00401055 (vedi exe disassemblato), quindi mettiamo un bel bp a quest'indirizzo, e lanciamo il programma. Appena si verifica l'eccezione di tipo "integer division by zero", premiamo alt+F9 (per far proseguire il programma), e come per magia non apparirà più la MessageBox (come in precedenza), ma ci fermiamo al bp settato da noi, ora analizziamo il codice del gestore:
00401059 MOV DWORD PTR DS:[EAX+B8],source.00401031 ; eip dopo l'esecuzione del gestore = 401031
00401063 XOR EAX,EAX ; eccezione risolta, si può continuare
00401065 RETN
Per prima cosa il gestore preleva il context del processo, poi setta context+B8h(EIP) a 401031, e fa continuare l'esecuzione.
In poche parole dopo il ret, il processo riprenderà dall'indirizzo 401031, quindi ora settiamo un bel breakpoint lì, e lanciamo il programma.
Come supposto, il processo riprende proprio a 401031, ora possiamo steppare fino alla MessageBox giusta :D.
Ora analizziamo un semplce crackme pieno di SEH Guardiamo il disassemblato in olly:
00401009 OR EAX,0FFFF ; |
0040100E XOR EAX,0FFFF ; |
00401013 MOV BX,WORD PTR DS:[EAX] ; |
00401016 XOR BX,23 ; |Verifica la prensenza di kernel32.dll
0040101A CMP BX,5A6E ; |
0040101F JE SHORT main.kernel32_found ; |
00401021 SUB EAX,10000 ; |
00401026 JMP SHORT main.compare ; \
Nulla di interessante per i nostri scopi, continuiamo:
0040102D MOV DWORD PTR DS:[40206D],EAX ; 40206d punta a ntdll.NtFlushBuffersFile
00401032 MOV EAX,main.handler ; attenzione qui
00401037 PUSH EAX ; attenzione qui
00401038 XOR ECX,ECX
0040103A PUSH DWORD PTR FS:[ECX] ; e qui, per un per-thread handle installato
0040103D MOV DWORD PTR FS:[ECX],ESP
00401040 ADD BYTE PTR DS:[next],11 ; aggiunge al byte di next[401047] 0x11
00401047 >DB BB
00401048 NOP
00401049 >PUSH main.exit ; exit[401049]
0040104E RETN
Vi spiego questa parte di disassemblato, lasciamo stare l'inizio e partiamo dalle istruzioni da 401032 a 40103D, non fanno altro che installare un gestore di eccezioni:
- Le istruzioni 401032 & 401037, non fanno altro che salvare nello stack l'indirizzo di handler (40104F), quindi mettiamoci un bp sopra.
- Le istruzioni da 401038 & 40103A: sarebbe "push fs:[0]", questo è un piccolo trick, xkè dopo lo "xor ecx, ecx" ,ecx diventa 0, quindi viene richiamato proprio fs:[0].
- L'istruzione 40103A, installa il per-thread handle.
Cmq dopo l'installazione dell'handle, possiamo trovare nello stack le informazioni riguardati l'installazioned di un SE handler:![]()
o grazie ad ollydbg (Nel menu view->SEH chain)
Io vi consiglio di usare SEH chain, perchè potete mettere direttamenti li' un bel bp, ed attenzione perchè,il primo Seh handler del prg listato, corrisponde all'ultimo gestore installato.
Capito come viene installato un gestore d'eccezione (quindi mettiamo un bel bp a 40104F e lanciamo il prg); continuiamo l'analisi del nostro crackme:
l'istruzione 401040, trasforma il byte di 401047 da BB a CC(int3), ma se steppiamo, noi lo superiamo e stepperemo sempre tra il push e il ret.
0040104F >MOV EAX,DWORD PTR SS:[ESP+C] ;eax punta al context
00401053 MOV DWORD PTR DS:[EAX+B8],main.cdCheck ; eip viene settato su cdCheck[401060]
0040105D XOR EAX,EAX ;errore riparato
0040105F RETN ; e continuiamo da cdCheck
;###Allora settiamo un bp su 401060, cioè continuiamo l'esecuzione normale del prg###
00401060 >PUSH main.401000 ;salva l'indirizzo del entrypoint del programma
00401065 MOV ECX,6E ;ecx = 0x6E
0040106A MOV AL,0CC ; al = 0xCC(int3)
0040106C POP EDI
0040106D >REPNE STOS BYTE PTR ES:[EDI] ;allora vengono messi 0xCC(int3), dall'ep a ep+0x6E
0040106F MOV DWORD PTR SS:[ESP+4],main.handler2 ;esp+4 = handler2[40107b]
00401077 XOR EAX,EAX
00401079 MOV DWORD PTR DS:[EAX],EAX ;qui viene generata un altra eccezione(violazione d'accesso)
Vediamo l'indirizzo del seh handler (come spiegato precedentemente): 40107B, come è possibile?? Allora l'indirizzo del seh handler è salvato nello stack, e dato che lo stack, dall'eccezione in poi non è stato modificato, esp+4 corrisponde all'indirizzo del seh handler nello stack :D, quindi il vecchio indirizzo del seh handler, viene sostituito con il nuovo.
0040107F MOV ECX,DWORD PTR DS:[EAX] ;ecx = tipo di errore
00401081 CMP ECX,80000004 ;cmp il tipo di errore con “single step”
00401087 JE SHORT main.exit ;non dovrebbe saltare,poiché non è stato generato quest'errore
00401089 MOV EAX,DWORD PTR SS:[ESP+C] ;esp+c è il context
0040108D MOV DWORD PTR DS:[EAX+B8],main.ok ;eip = ok[4010A0]
00401097 DEC DWORD PTR DS:[EAX+18] ;lavora con i debug regist
0040109A DEC DWORD PTR DS:[EAX+4] ;lavora con i debug regist
0040109D XOR EAX,EAX
0040109F RETN ;continuiamo l'esecuzione a 4010A0
;###Ricordatwvi di settare un bel bp qui :D###
004010A0 >PUSH main.rightWay ;mette in esp rigntWay[4010A6]
004010A5 RETN ; e cotinuiamo da 4010A6
Settiamo un bp a 4010a6, e continuiamo:
004010AE MOV EAX,DWORD PTR DS:[40206D] ;questo
004010B3 PUSH EAX ;questo
004010B4 XOR DWORD PTR SS:[ESP],1234 ;e questo
004010BB PUSH main.004020FE ; ASCII "CreateFileA" (con questo)
004010C0 POP ESI ; non fanno altro
004010C1 POP EAX ; che trovare l'indirizzo in memoria
004010C2 CALL main.GetFunctionAddress ; di CreateFileA
004010C7 MOV DWORD PTR DS:[402071],EAX
004010CC PUSH 0
004010CE PUSH 2
004010D0 PUSH 3
004010D2 PUSH 0
004010D4 PUSH 0
004010D6 PUSH 40000000
004010DB PUSH main.0040210A ; ASCII "c:\havok.dat"
004010E0 CALL EAX ;qui verifica l'esistenza del file c:\havok.dat
004010E2 CMP EAX,-1 ;se non esiste arriviamo a un int3,
004010E5 JE main.exit ;altimenti sorry :P
004010EB PUSH 0
004010ED PUSH main.00402079 ; ASCII "sorry, man"
004010F2 PUSH main.00402084 ; ASCII "try your luck again"
004010F7 PUSH 0
004010F9 CALL DWORD PTR DS:[402075]
Mettiamo un bp ad handler3
00401103 MOV EBX,DWORD PTR DS:[EAX]
00401105 XOR EBX,80000003
0040110B MOV ESI,OFFSET main.encrypt_start ;ce lo dice lui, è l'indirizzo di inizio decrypt
00401110 MOV ECX,68 ;ci dice anche quanti byte decritta
00401115 >SUB BYTE PTR DS:[ESI+ECX],BL ;e lo fa
00401118 LOOPD SHORT main.decrypt ;con un bel loop
0040111A PUSH DWORD PTR SS:[ESP+C] ;eax punta al context
0040111E POP EAX
0040111F MOV DWORD PTR DS:[EAX+B8],OFFSET main.encrypt_start ;eip=402001
00401129 PUSH main.00402098 ; salva nello stack "congratulations"
0040112E POP DWORD PTR DS:[EAX+A8] ;pop edx
00401134 PUSH main.004020A8 ;salva nello stack "you finally made it :)"
00401139 POP DWORD PTR DS:[EAX+9C] ;pop edi
0040113F XOR EAX,EAX
00401141 RETN ;e fa riprendere l'esecuzione da 402001
Per controllare le modifiche al context eseguite in un seh handler, potete anche usare il mio plugin per olly, senza dover controllare il codice, in modo ossessivo. Continuiamo l'analisi:
00402003 PUSH EDX
00402004 PUSH EDI
00402005 PUSH 0
00402007 PUSH 0
00402009 PUSH 2
0040200B PUSH 3
0040200D PUSH 0
0040200F PUSH 0
00402011 PUSH 40000000
00402016 PUSH main.00402117 ; ASCII "c:\congratulations.txt"
0040201B CALL DWORD PTR DS:[402071] ;chiama CreateFileA per verificare la presenza di quel file
00402021 CMP EAX,-1
00402024 JE SHORT main.bye ; se non esiste si chiude, altrimenti arriva la finestra di complimenti
00402026 PUSH DWORD PTR DS:[40206D] ; <&ntdll.NtFlushBuffersFile>
0040202C XOR DWORD PTR SS:[ESP],1234
00402033 POP EAX
00402034 MOV ESI,main.004020D7 ; ASCII "LoadLibraryA"
00402039 CALL main.GetFunctionAddress
0040203E PUSH main.004020CC ;ASCII "user32.dll"
00402043 CALL EAX
00402045 MOV ESI,main.004020E4 ;ASCII "MessageBoxA"
0040204A CALL main.GetFunctionAddress
0040204F CALL EAX
00402051 >PUSH 0
00402053 MOV EAX,DWORD PTR DS:[40206D]
00402058 XOR EAX,1234
0040205D MOV ESI,main.004020C0 ;ASCII "ExitProcess"
00402062 CALL main.GetFunctionAddress
00402067 CALL EAX
Per risolvere questo crakme basta creare un file congratulations.txt ed il gioco è fatto :D RAGAAAAAAA ALLA PROSSIMA (speriamo)
Note Finali
Ringrazio quequero ke mi ha dato la possibilità di scrivere questa lezione, a nt, quake, evilcry, ermes, 0x87k e tutti quelli di #crack-it #pmode e #cryptorev, anche se più o meno so sempre le stesse persone :D
Pn =)
Disclaimer
I documenti qui pubblicati sono da considerarsi pubblici e liberamente distribuibili, a patto che se ne citi la fonte di provenienza. Tutti i documenti presenti su queste pagine sono stati scritti esclusivamente a scopo di ricerca, nessuna di queste analisi è stata fatta per fini commerciali, o dietro alcun tipo di compenso. I documenti pubblicati presentano delle analisi puramente teoriche della struttura di un programma, in nessun caso il software è stato realmente disassemblato o modificato; ogni corrispondenza presente tra i documenti pubblicati e le istruzioni del software oggetto dell'analisi, è da ritenersi puramente casuale. Tutti i documenti vengono inviati in forma anonima ed automaticamente pubblicati, i diritti di tali opere appartengono esclusivamente al firmatario del documento (se presente), in nessun caso il gestore di questo sito, o del server su cui risiede, può essere ritenuto responsabile dei contenuti qui presenti, oltretutto il gestore del sito non è in grado di risalire all'identità del mittente dei documenti. Tutti i documenti ed i file di questo sito non presentano alcun tipo di garanzia, pertanto ne è sconsigliata a tutti la lettura o l'esecuzione, lo staff non si assume alcuna responsabilità per quanto riguarda l'uso improprio di tali documenti e/o file, è doveroso aggiungere che ogni riferimento a fatti cose o persone è da considerarsi PURAMENTE casuale. Tutti coloro che potrebbero ritenersi moralmente offesi dai contenuti di queste pagine, sono tenuti ad uscire immediatamente da questo sito.
Vogliamo inoltre ricordare che il Reverse Engineering è uno strumento tecnologico di grande potenza ed importanza, senza di esso non sarebbe possibile creare antivirus, scoprire funzioni malevole e non dichiarate all'interno di un programma di pubblico utilizzo. Non sarebbe possibile scoprire, in assenza di un sistema sicuro per il controllo dell'integrità, se il "tal" programma è realmente quello che l'utente ha scelto di installare ed eseguire, né sarebbe possibile continuare lo sviluppo di quei programmi (o l'utilizzo di quelle periferiche) ritenuti obsoleti e non più supportati dalle fonti ufficiali.