Zoom Icon

Corso UIC Newbies 13

From UIC

SEH (Strucured Exception handle) Reversing

Contents


Infos
Author: Pnluck
Email: pnluck@virgilio.it
Website: http://pnluck.netsons.org
Date: 28/08/2006 (dd/mm/yyyy)
Level: Slightly hard
Language: Italian Image:Flag_Italian.gif
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):

  1. 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 :)
  2. 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]
  3. 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
  4. Se nessun Handler dialoga con l'eccezione ma il programma è sotto debug allora il sistema prova nuovamente a notificare l'evento al debugger
  5. Se non accade nulla di positivo allora il controllo passa al Final Handler (che dobbiamo settare noi)
  6. 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 :)
  7. 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:

Start:
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 :

.386
.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:

.386
.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:

mov edx, dword ptr[ebp+8]

e concepire che:

Edx+0 ExceptionCode
Edx+4 ExceptionFlag
Edx+8 NestedExceptionRecord
Edx+C ExceptionAddress
Edx+10 NumberParameters
Edx+14 AdditionalData

così un semplicissimo:

cmp dword ptr[edx], ExceptionCode

può servire nel conoscere il tipo d'eccezione, ed un altrettanto semplice:

mov eax, dword ptr[edx+0Ch]

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:

C0000094h  ; Divisione per 0
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:

.386
.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 è:

+0 context flags
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:

MYFUNCTION:             ; procedura di entry point
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:

.386
.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ò:

7C91EAF0 MOV EBX,DWORD PTR SS:[ESP] ; siamo in ntddl.dll il motore(r3) di windows
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ù:

00401000 >PUSH source.00401055                     ; SE handler installation (lo mette olly)
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:

00401055  MOV EAX,DWORD PTR SS:[ESP+C]              ; Structured exception handler
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:

00401006 MOV EAX,DWORD PTR SS:[ESP]   ; /
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:

00401028 XOR EAX,1234                      
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:
Image:SEh1.jpg

o grazie ad ollydbg (Nel menu view->SEH chain)
Image:SEh2.jpg

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.

;######il bp ci ferma qui###
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.

0040107B >MOV EAX,DWORD PTR SS:[ESP+4] ;eax punta a esp+4,che è la struttura d'errore
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:

004010A6 >MOV DWORD PTR SS:[ESP+4],main.handler3 ;come prima, il seh handler è handler3[4010FF]
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

004010FF >MOV EAX,DWORD PTR SS:[ESP+4]
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:

00402001 >PUSH 0
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


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