|
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
|
|
-
- 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:
Ollydbg (debug) olly sarà + che sufficiente
- Guardate
questo crackme ke secondo me spiega le cose
principali del reversing delle seh.
- 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:
-
- 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 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
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
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.