SEH aka Structured Exception Handling
(come si usa e come si reversa)

Data

by "Pnluck"

 

28/02/05

UIC's Home Page

Published by Quequero

Grazie pn, ho dovuto correggere quasi ottanta orrori di ortografia, ma una volta entrati nell'ottica del tuo stile il tute diventa abbastanza chiaro. Sei stato preciso e a tratti anche oscuro, ma con Olly alla mano non credo che nessuno avra' problemi a seguire il tuo tute :)

Quequero doveva toccare anche il mio turno no :P

....

E-mail: pnluck@virgilio.it
Azzurra #crack-it,#pmode,#cryptorev,#assembly,#asm

....

Difficoltà

(X)NewBies (X)Intermedio ( )Avanzato ( )Master

 
 

Introduzione

In questa lezione per newbie, spiegherò l'uso, ma soprattuto il reversing di seh, molte volte vi sarà capitato in ollydbg di dover premere alt+F(X), per poter passare un eccezione, ma vi siete mai chiesti cosa stavate facendo?
Se trovate qualcosa di giusto lo dovete a quequero, xkè cmq molti sanno, nel mio paese usiamo l'italiano come seconda lingua :D, invece se c'è qualcosa che non capite prendetevela sempre con quequero, che mi ha dato l'onore di pubblicare questa lezione.
Se trovate monotono la continua descrizione di come installare un per-thread handler, è per il vostro bene :D

Tools usati

Tools usati:
Ollydbg (debug) olly sarà + che sufficiente

URL o FTP del programma

Guardate questo crackme ke secondo me spiega le cose principali del reversing delle seh.

Essay

Iniziamo questa lezione dicendo prima le cose basilari sulle eccezioni nei programmi, comunque come noterete riporterò molte delle cose già scritte da quequero, perchè sono più che valide.

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.
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 provare a vedere come si installa un per-Thread Handler, vediamo cosa succederebbe se questo 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 a leggere dehihihho ;P
invoke MessageBox, NULL, addr Brk1, addr Caption, MB_OK
invoke ExitProcess,0
end start

Come noterete vi esce una bella finestra che vi segnala l'errore (se usate win xp, su win98 non mi ricordo che esce :P). Ora proviamo quindi a vedere come si installa un per-Thread Handler, ricordiamo che la prima DWORD puntata da "fs" è la struttura di errore, mentre la seconda DWORD è l'indirizzo dell'ExceptionHandler:

.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] ;ed imposta
mov fs:[0], esp ;il per-thread handler
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, vuol dire che l'eccezione è stata corretta
ret
end start

Ora non vi dovrebbe apparire nessuna finestra di errore, ma solo la MessageBox da noi programmata.
Quindi sappiamo come si installano questi due tipi di Handler, ma prima di vedere come si usano, facciamo luce su una cosa, ovvero la Struttura di Errore, questa è una parte importantissima perchè solo grazie alla struttura di errore noi possiamo sapere dove è accaduta l'eccezione e soprattutto possiamo conoscere il tipo di eccezione.
Sappiamo che la prima DWORD puntata da fs:[0] è la struttura di errore che ha sua volta punta a questa struttura:
 

EXCEPTION_RECORD +0

ExceptionCode

EXCEPTION_RECORD +4

ExceptionFlag

EXCEPTION_RECORD +8

NestedExceptionRecord

EXCEPTION_RECORD +C

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]
quindi basta sapere 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
basta a farci sapere che tipo di 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 codici necessitano di una spiegazione, il primo è 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, se il processore vede che questo flag è settato allora genera questa eccezione per ogni riga di codice che esegue. Per una lista più completa di eccezioni, basta cercare su google. Bene, ora che sappiamo come installare un gestore di 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 ;dice a fs:[0] l'offset del gestore di eccezioni e poi
push fs:[0] ;imposta
mov fs:[0],esp ;il per-thread headle (l'ho detto in altri termini, per mettervelo nella capoccia)
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 all'errore int3
jnz esco
invoke MessageBox, NULL, addr Brk2, addr Caption, MB_OK
esco:
invoke ExitProcess,0 ;ho messo ExitProcess perchè senza sarebbero apparse un infinità di MessageBox
ret

end start

Come notate il programma ora non crasha, anzi vi appare anche una bella MessageBox. Ora che abbiamo sperimentato (e spero capito) cosa sono gli Handler passiamo ad approfondire la funzione della struttura di 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

Ora spiego secondo me, 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
Ed è anche l'unico luogo dal quale potete cambiare il valore di EIP (per questo mi piace), 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
Ora che ho spiegato le funzioni principali della gestione di eccezioni, vi mostro un semplice source in asm, e poi vi mostro il metodo giusto di reversing:

.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 ;salva l'offset del per-thread headle
push fs:[0] ;e lo richiama.Quindi per riconoscere la presenza di un per-thread headle
mov fs:[0],esp ;anche in un dissassemblato, bisonga vedere la presenze di queste tre istruzioni)
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

Ora provate a steppare con olly, e come noterete non vi uscirà la seconda messageBox, ma la prima, questo succede perchè int 3,non genera una "grave" eccezione, ora provate a mettere nel source 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, invece premendo f7/f8, vi fanno apparire ciò:
7C91EAF0 MOV EBX,DWORD PTR SS:[ESP] ;siamo in ntddl.dll il motore 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 si continua l'esecuzione
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 arrivate a ZwContinue, e qui vi appare la nostra bella MessageBox, ma ancora non abbiamo capito come gestire al meglio il reversing delle seh.
Guardiamo il codice del nostro programma decompilato con olly:
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(dopo l'esecuzione del gestore) = 401031
00401063  XOR EAX,EAX                               ; eccezione risolta, si può continuare
00401065  RETN                       
        

Ora ditemi 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 "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 impostato 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      

Spiegazione: Per prima cosa il gestore preleva il context del processo quando si è verificato l'eccezione, poi setta context+B8h(EIP) a 401031, e poi fa continuare l'esecuzione, in poche parole dopo il ret il processo riprenderà dall'indirizzo 401031, quindi ora settiamo un bel breakpoint qui, e lanciamo di nuovo il programma, come supposto, il processo riprende proprio a 401031, ora steppiamo fino alla MessageBox giusta :D. Ora che sappiamo le basi della gestione delle eccezioni, e le basi del reversing di queste, possiamo passare alla vera lezione, cioè esaminare il crackme indicato all'inizio del tutorial :D

Lanciamo il crackme 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.

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:

Cmq a volte nello step, queste cose per vari motivi non le vediamo, ma cmq possiamo vedere l'indirizzo del handler di quell'eccezione, lo troviamo nello stack (il registro ESP)

o grazie ad ollydbg (Nel menu view->SEH chain) .
Io vi consiglio però di vedere il SEH chain perchè potete mettere direttamenti li' un bel bp, ed attenzione perchè qui il primo Seh handler che fa parte del prg (quindi non di kernel32, etc), corrisponde al gestore dell'ultima eccezione che si è verificata.

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 dicendo che 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, poiché il ret vede l'indirizzo del ritorno in ESP, il quale viene modificato dal push.

######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)

Oki ora vediamo l'indirizzo del seh handler (come spiegato precedentemente, cioè con olly o vedendo lo stack): 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

Continuiamo a spiegare da 4010A6 (ricordate che quando capiamo dove il seh handler ci porta, dobbiamo mettere un bp su quell'indirizzo):

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]

Ma allora che fare? Semplice mettiamo un bel bp su Handler3, e vediamo cosa succede :D

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 l'esecuzione da 402001

Evvai abbiamo quasi finito questo crackme =D

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

Oki quindi per risolvere questo crakme basta creare un file congratulations.txt ed il gioco è fatto :D RAGAAAAAAA ALLA PROSSIMA (speriamo)

                                                                                                                 Pnluck

Note finali

Ringrazio quequero ke mi ha dato la possibilit di scrivere questa lezione, ai miei boss quake e nt (sopratutto quest'ultimo ke mi ha fatto entrare in testa quel poco ke so di italiano, ma poi anche quake xkè lo disturbo sempre), poi misa ke mi consola dopo ke nt mi sgrida e tutti quelli di #crack-it #pmode e #cryptorev, poi ke + o meno so sempre le stesse persone :D

Disclaimer

Vorrei ricordare che il software va comprato e  non rubato, dovete registrare il vostro prodotto dopo il periodo di valutazione. Non mi ritengo responsabile per eventuali danni causati al vostro computer determinati dall'uso improprio di questo tutorial. Questo documento è stato scritto per invogliare il consumatore a registrare legalmente i propri programmi, e non a fargli fare uso dei tantissimi file crack presenti in rete, infatti tale documento aiuta a comprendere lo sforzo che ogni sviluppatore ha dovuto portare avanti per fornire ai rispettivi consumatori i migliori prodotti possibili. Poi i crackme devono essere pagati al programmatore in birra o wino, fate voi :D

Reversiamo al solo scopo informativo e per migliorare la nostra conoscenza del linguaggio Assembly.