Devilz's Keygenme5, Crc32 + Floating point operations
reversing
|
Data
|
by Evilcry
|
|
07/07/2004
|
UIC's Home Page
|
Published by Quequero
|
Where ones sees a limit |
Grazie tante evil, preciso come sempre. E voi non avete idea di
quanto sia comoda la bswap :))) |
the
others sees an opporunity |
Prepare for what you cannot see, expect
the unexpected, a waiting game waiting to see......
|
- WebSite:
www.cryptorev.da.ru / http://abcd6.interfree.it
- E-mail:
[email protected] / [email protected]
- IRC
frequentato irc.azzurranet.org
#cryptorev #crack-it - irc.efnet.nl #cracking4newbies #cryptology
#win32asm
-
|
|
Difficoltà
|
NewBies () Intermedio (X) Avanzato ()
Master ()
|
|
- Devilz's Keygenme5, Crc32 + Floating point operations reversing
- Written by Evilcry.
- In questo tutorial, andremo ad analizzare, reversare e keygennare il quinto
keygenme di Devilz.
- www.google.it
Troverete (crackme+ keygen) tutto in nell' allegato.
Cominciamo col vedere di che tipo di protezione si tratta, immediatamente
dopo l'apertura si presenta davanti a noi il classico schema Username/SerialNumber.
Inserisco come username 'evilcry' e come serial 'triton', e poi setto un breakpoint
su GetDlgItemTexA. Ecco cos'abbiamo di fronte:
004010AD push
0C8h
004010B2 push
offset String ;00403940 è il buffer che conterrà
il serial
004010B7 push
0C8h
004010BC push
[ebp+hDlg]
004010BF call
GetDlgItemTextA
004010C4 push
0C8h
004010C9 push
offset byte_4039A4 ;4039A4 è il buffer che conterrà
l'username
004010CE push
64h
004010D0 push
[ebp+hDlg]
004010D3 call GetDlgItemTextA
004010D8 test
al, al
004010DA jz
loc_40122B ;Se l'username è nullo, salta al messaggio
d'errore
004010E0 mov
byte_403A08, al ;AL contiene la lunghezza dell'username
004010E5 call
sub_401272 ;Call che analizzeremo più approfonditamente
004010EA test
al, al ;Se il valore di ritorno della call è 0,
allora salta al messaggio d'errore
004010EC jz
loc_4011D6
004010F2 call sub_4012DC
;Altra call che analizzeremo in seguito
Passiamo ora ad analizzare cosa fanno le call 00401272 e 004012dc...
00401272 movsx
eax, String ;Mette in eax primo carattere del serial che
abbiamo inserito
00401279 xor
eax, 65h ;Ne fa un xor con il valore 65
0040127C dec
al ;decrementa il risultato dello xor
0040127E jnz
short loc_4012D8 ;se il risultato è diverso da
0 allora salta al msg d'errore
00401280 movsx
eax, byte_403941 ;Prossimi char del serial
00401287 shl
eax, 3 ;Shifta a sinistra di 3
0040128A cmp
eax, 328h ;e controlla che il risultato sia 328h
0040128F jnz
short loc_4012D8 ;se diverso allora salta al msg d'errore
00401291 movsx
eax, byte_403942
00401298 movsx
ebx, byte_403943
0040129F add
al, bl
004012A1 cmp
al, 0DFh
004012A3 jnz
short loc_4012D8 ;La somma dei 2 char del serial deve
dare 0DFh
004012A5 movsx
eax, byte_403943
004012AC shl
eax, 1
004012AE cmp
al, 0D2h
004012B0 jnz
short loc_4012D8 ;Il risaultato dello shift dev'essere
0D2h
004012B2 movsx
eax, byte_403944
004012B9 rol
eax, 2
004012BC cmp
eax, 1B0h
004012C1 jnz
short loc_4012D8 ;Il risaultato del rol dev'essere 1B0h
004012C3 movsx
eax, byte_403944
004012CA movsx
ebx, byte_403945
004012D1 sub
bl, al
004012D3 ror
ebx, 1
004012D5 cmp
bl, 7 ;Il risultato della sottrazione e successivo ror
dev'essere 7
004012D8
004012D8 setz
al ;Nel caso la condizione è TRUE setta al a 1,
così all'uscita della call non ci sarà il jump al msg d'errore
004012DB retn
Come potete vedere il codice di questa call è semplicissimo, in poche
parole controlla che i primi 6 caratteri del serial seguono determinate "condizioni".
Penso sia inutile riscrivere quali sono le condizioni, quindi procediamo direttamente
con il reverse (per altro sono operazioni semplicissime da reversare):
1°Char = Essendo
(x Xor 65 - 1 = 0), basta prendere il valore prima di 65 cioè 64h
(d)
2°Char = Shl
x,3 = 328h quindi invertiamo l'operazione come Shr 328,3 cioè 65h
(e)
3-4°Char = x
+ y = 0DFh in 2 secondi con la calcolatrice otteniamo 76h = (v) e 69h=
(i)
5°Char = Shl
x,1 = 0D2h invertiamo anche qui come Shr 0D2,1 = 7Ah = (l)
6°Char= Senza
perdere tempo, i primi 5 bytes sono il nome dell'autore del crackme è
devilz quindi devil(z) :)
Questa prima call è fatta, se i caratteri sono quelli giusti, setz
dovrebbe settare AL=1. Procediamo ora con la prossima call 004012dc:
004012E1 movsx
eax, ss:String[ebp] ;Ebp fa da indice di String (il vettore
che contine il serial)
004012E8 test
al, al
004012EA jz
short loc_40130E ;Se la stringa è finita salta
giù, alla prossima routine
004012EC shl
eax, 1
004012EE cmp
eax, 5Ah ;Il risultato dello shift dev'essere 5Ah
004012F1 jz
short loc_40130B
004012F3 cmp
eax, 60h
004012F6 jl
short loc_401348 ;Se il risultato è minore di 60h
allora salta
004012F8 cmp
eax, 8Ch
004012FD jg
short loc_401348 ;Salta anche se è maggiore di
8Ch
004012FF cmp
eax, 82h
00401304 jge
short loc_40130B ;Passa al prossimo carattere se invece
il risultato è maggiore o uguale a 82h
00401306 cmp
eax, 72h
00401309 jg
short loc_401348
0040130B
0040130E loc_40130E:
0040130E shl
ebp, 1 ;Shifta a sinistra il contatore (contine il numero
di caratteri processati prima)
00401310 xor
ebp, 45h ;Effettua lo xor di quello che ne esce con 45h
00401313 dec
ebp ;Sottraigli 1
00401314 jnz
short loc_401348 ;Se il risultato finale è diverso
da 0 allora salta al msg d'errore
Il primo pezzo di routine controlla che i caratteri del serial che stanno
immediatamente dopo 'devilz' rispettano alcune condizioni. Considerando che
dobbiamo uscire da questo ciclo di check (004012E1-00401309) senza far saltare
l'esecuzione fuori dal ciclo stesso, la cosa migliore è far sì
che i caratteri diano 82h. Come prima facciamo:
Shl x,1= 82h, che revesato da Shr 82h,1= 41h (A)
Quindi comiciamo a mettere dopo devilz, una serie di A. Passiamo ora all'altra
porzione di codice (da 0040130E per intenderci), attraverso la quale saremo
in grado di determinare quale dev'essere la lunghezza del nostro serial. Per
sapere quindi la lunghezza corretta vediam che
((Shl x,1) xor 45) - 1 = 0 che è molto semplice da reversare, considerando
che il risultato dello xor dev'essere 1 (che poi sottratto da 0) prendiamo
come secondo operando (dello xor sempre) 44h. Perciò
Shr 44,1 = 22h (34d)
capiamo facilmente che il serial dev'essere lungo 34. Con il prossimo pezzo
di codice invece capiremo qual'è lo "scheletro" del nostro
serial:
00401314 jnz
short loc_401348
00401316 mov
cl, byte_403950
0040131C mov
bl, byte_403959
00401322 sub
cl, bl ;sottrae tra loro due caratteri precisi del serial
00401324 jnz
short loc_401348 ;se il risultato è diverso da
0 salta al msg d'errore
00401326 xor
byte_403946, 2Dh ;il 6° carattere a partire da devilz,
xorato per 2Dh
0040132D jnz
short loc_401348 ;deve dare 0
0040132F mov
bl, byte_40394B
00401335 rol
ebx, 2
00401338 cmp
bl, 0B4h ;il B° carattere Rol x,2=0B4h
0040133B jnz
short loc_401348
0040133D mov
bl, byte_403950 ;il carattere è lo stesso di quello
spostato in cl alla locazione 00401316
00401343 xor
bl, 2Ch
00401346 dec
bl ;che xorato poi sottratto deve dare 0
00401348 setz
al ;se tutte queste condizioni sono rispettate, si passa
alla prossima routine, altrimenti msg d'errore
0040134B retn
2Dh corrisponde al trattino, perciò seguendo le direttive di
questo pezzo di codice saremo in grado di costruire lo "scheletro"
del nostro seriale valido. Ecco come dovrebbe risultarvi la forma di un serial
corretto:
devilz-AAAA-AAAA-AAAAAAAA-AAAAAAAA
Per facilitare il discorso, chiameremo ogni sigolo pezzo (compreso tra due
trattini) in modo diverso. I nomi che gli daremo saranno, PrimaWord,
SecondaWord, PrimaDword e SecondaDword.
Se appunto mettiamo questo serial, dovremmo ottenere questo messaggio: "You
are Halfway.... You Need To Progress :p"
Passiamo ora a vedere cosa succede dopo questa call. Innanzitutto possiamo
notare la presenza di 4 call tutte le stesse, chiamate in successione:
0040110E call
sub_40134C ;call che studieremo in seguito
...
00401123 call
sub_40134C
...
00401138 call
sub_40134C
...
0040114C call
sub_40134C
Se notate le call sono quattro, proprio come i "pezzi" di serial.
Infatti andando ad analizzare l'nterno di questa funzione:
0040134C xor
ebx, ebx
0040134E movsx
eax, ss:String[ebp] ;mette in eax un byte di uno specifico
"pezzo" di serial
00401355 cmp
al, 41h
00401357 jge
short loc_401369 ;Controlla che il carattere non sia maggiore
di 'A'
00401359 sub
al, 30h ;sottrae trenta così da eax passiamo a
semplice char es (39-30=9)
0040135B shl
eax, cl
0040135D add
ebx, eax
0040135F sub
ecx, 4
00401362 inc
ebp
00401363 cmp
ebp, edx
00401365 jnz
short loc_40134E
00401367 jmp
short locret_401377
00401369 loc_401369:
;Nel caso si trattase si una lettera, procedi con l'altra
routine
00401369 sub
al, 37h ;che alla fin fine fa la stessa cosa di quella
dei numeri
0040136B shl
eax, cl
0040136D add
ebx, eax
0040136F sub
ecx, 4
00401372 inc
ebp
00401373 cmp
ebp, edx
00401375 jnz
short loc_40134E
00401377 retn
che, senza stare a perder tempo, altro non è che una funzione di conversione,
che trasforma il nostro serial da hex a stringa semplice. Ogni call elabora
un pezzo di serial. Adesso che sappiamo cosa fanno queste call, possiamo procedere
oltre con l'analisi.
00401151 mov
dword_403A11, ebx
00401157 call
sub_401378 ;call che analizzeremo in seguito
0040115C test
al, al
0040115E jz
loc_401212 ;se la call restituisce lo status FALSE allora
salta al msg d'errore
00401164 call
sub_401421 ;call che analizzeremo in seguito
00401169 test
al, al
0040116B jz
loc_401212
00401171 call sub_401499
;call che analizzeremo in seguito
Passiamo ad analizzare la call 00401378
00401378 push
104h
0040137D push
offset Buffer ; lpBuffer
00401382 call
GetWindowsDirectoryA
00401387 mov
dword_403A3C, 0
00401391 push
104h
00401396 push
403A3Dh
0040139B push
offset FileSystemFlags
004013A0 push
offset MaximumComponentLength
004013A5 push
offset VolumeSerialNumber
004013AA push
104h
004013AF push
offset VolumeNameBuffer
004013B4 push
offset Buffer
004013B9 call
GetVolumeInformationA
004013BE mov
ecx, VolumeSerialNumber ;mette in ecx il SeriaNumber del
nostro HDD
004013C4 mov
ebx, VolumeSerialNumber ;mette in ecx il SeriaNumber del
nostro HDD
004013CA and
ecx, 0FFFFh ;Elimina la HighWord del SeriaNumber e lascia
la LowerWord
004013D0 shr
ebx, 10h ;Fa la stessa cosa dell'istruzione sopra
004013D3 add
ecx, ebx ;somma le due word
004013D5 cmp
cx, word_403A09 ;PrimaWord dev'essere ugualle a cx (cioè
alla somma)
004013DC setz al ;Sennò
setta al FALSE
Questo pezzettino di codice ci permette di trovare il valore di PrimaWord,
possiamo facilmente intuire che questo valore dipende solamente dall'hardisk
in cui si trova il crackme.
004013DF movsx
edx, byte_403A08 ;mette in edx la lunghezza del nostro
username
004013E6 and
ebx, 5Fh ;and tra 5Fh e la LowerWord del SerialNumber
mov V1,ebx
...
Da questo pezzo di codice possiamo vedere, che adesso si comincia a lavorare
con l'username. C'è anche da dire che il risulto dell'AND tra la LowerWord
del SerialNumber del HDD ed il valore 5Fh andrà in una variabile che
ho chiamato V1, la quale sarà usata in una routine che vedremo presto.
Andiamo ora ad analizzare la call 00401421:
00401421 xor
ecx, ecx
00401423 xor
edx, edx
00401425 mov
cl, byte_403A08 ;in cl la lunghezza del username che abbiamo
inserito
0040142B mov
dword_403A15, 0FD12EA30h
00401435 loc_401435:
00401435 movzx
eax, byte_4039A4[edx] ;mette in eax un char corrispondente
all' username
0040143C mov
ebx, dword_403A15 ;la prima volta 0FD12EA30h in ebx, poi
sara aggiornato - chiamiamo la locazione 403A15h 'Delta'
00401442 and
ebx, 0FFh ;ne rimane solo il byte meno significativo (LSB)
30h
00401448 xor
eax, ebx
0040144A mov
eax, dword_[eax*4+40342A] ;mette in eax una dword appartenene
ad una tabella (40342A), questa tabella è in altre parole una Crc32_Table
00401451 mov
ebx, dword_403A15
00401457 shr
ebx, 8
0040145A xor
eax, ebx
0040145C mov
dword_403A15, eax
00401461 inc
edx
00401462 loopne loc_401435
Abbiamo davanti a noi la più classica delle routine di Crc32, riconoscibilissima
anche a 100 km di distanza :). Per chi non lo sapesse CRC significa Cyclic
Redundancy Check o a seconda dei casi anche Cyclic Redundancy Code, spesso
(quasi sempre) i CRC vengono utilizzati come controlli d'integrità
di codice o di dati. Adiamo avanti:
00401464 not
eax ;NOT del crc
00401466 mov
ecx, dword_403A0D ;in ecx andrà PrimaDword
0040146C xor
eax, ecx
0040146E jnz
short loc_401495 ;se PrimaDword è uguale a not(crc)
allora procedi, sennò salta al msg d'errore
00401470 mov
eax, dword_403A15 ;mette nuovamente in eax il crc
00401475 and
eax, 0FFFFh ;lascia solo la HigherWord
0040147A shr
ecx, 10h ;lascia solo HigherWord del not(crc)
0040147D and
eax, ecx ;AND HigherWord(not),HigherWord
0040147F and
ax, 0FF00h
00401483 mov
cx, word_403A0B ;403A0B contiene il valore corretto (che
andrà in SecondaWord)
0040148A and
cx, 0FF00h ;lascia solo il byte più significativo
della LowerWord
0040148F not
ax
00401492 and
ax, cx
00401495 setz al ;Se
eax è 0 setta AL a TRUE
Da questo pezzetto di codice possiamo ricavare il giusto valore di PrimaDword
e successivamente di SecondaWord, infatti se prendiamo il codice da 00401470
in poi e ne aumentiamo il livello di astrazione (The+Q di +Fravia docet) saremo
in grado con due semplici passaggi di reversare il codice:
Se abbiamo infatti: (PrimaDword >> 10h) && Delta
allora avremo: SecondaWord == ((PrimaDword >> 10h) && Delta)
che ritradotto in asm:
not eax ;not(crc)
mov PrimaDword,
eax
mov ecx, eax
mov eax, Delta
shr ecx, 10h
and eax, ecx
and ax, 0FF00h
mov SecondaWord, ax
Questa è solo la prima parte della routine per ottenere SecondWord,
infatti come potete vedere dalla relazione, alcune cose ancora non le abbiamo
considerate (leggete più sotto, la parte che tratta del logaritmo).
Usciti da questa call, continuiamo la nostra analisi:
00401169 test
al, al
0040116B jz
loc_401212
00401171 call
sub_401499 ;call che analizzeremo in seguito
00401176 mov
eax, dword_403A11
0040117B bswap eax
Vediamo cosa c'è dentro la call 00401499:
0040149A mov
edx, dword_403A15 ;Delta in edx
004014A0 not
edx ;not(Delta)
004014A2 mov
eax, dword_403A15 ;Delta in eax
004014A7 call
sub_40158A ;Call particolare di cui parleremo dopo
004014AC and
edx, 0FFh
004014B2 mov
ecx, edx
004014B4 xor
edx, edx
004014B6 div
ecx
004014B8 mov
al, byte_40382A[edx] ;40382A è un array di valori
fissi
004014BE and
eax, 0ABh
004014C3 mov
ebx, dword_403A61 ;Ricordate la variabile V1?..adesso
la incontriamo nuovamente
004014C9 xor
eax, ebx
004014CB mov
dword_403A61, eax ;V1 = V1 ^ Rand() -> semplice xor
reversabilissimo
004014D0 mov
edx, dword_403A15 ;nuovamente Delta in edx
004014D6 bswap
edx
004014D8 mov
eax, dword_403A15 ;nuovamente Delta in eax
004014DD add
eax, edx
004014DF call
sub_40158A ;Stessa
call di sopra
Questa routine merita qualche spiegazione. Non è necessario studiare
approfonditamente la call 40158A, perchè contiene al suo interno un
piccolo algoritmo composto da shift, rotate ecc. di cui non c'interessa granchè
dato che non dobbiamo reversarlo. Chiamerò questa call 'Algo()' per
motivi di semplicità, c'è da notare che prima della chiamata
ad Algo() viene messa in eax ed edx la variabile Delta. Analizzando i valori
di ritorno eax ed edx di Algo(), possiamo notare che alla prima chiamata (004014A7h):
1°Algo(): eax = Delta, edx =! Delta
Alla seconda chiamata di Algo() possiamo intendere eax e edx come:
2°Algo(): eax=Delta+bswap(Delta), edx=bswap(Delta)
Procediamo con lo studio della routine:
004014E4 and
edx, 0FFh
004014EA mov
ecx, edx
004014EC xor
edx, edx
004014EE div
ecx
004014F0 mov
al, byte_40382A[edx] ;in al un byte della tabella di valori
fissi
004014F6 and
eax, 0FBh
004014FB mov
dword_403A65, eax ;chiameremo 403A65 'V3', in questo caso
quindi V3 = Algo()
00401500 mov
ecx, 50h
00401505 mov
eax, dword_403A15 ;Delta in eax
0040150A
add eax, 2004h
0040150F xor
eax, 4002h
00401514 imul
eax, ecx
00401517 loopne
loc_40150A
00401519 and
eax, 0FFh
0040151E mov
dword_403A75, eax ;chiameremo 403A75 'V5', che in questo
caso sarà V5 = Algo()
00401523 fild dword_403A75
;fild V5, cioè mette V5 in St0 (dovete conoscere
un minimo di architettura del FPU - FloatingPointUnit)
Vediamo ora cosa succede all'uscita di questa call:
00401176 mov
eax, dword_403A11 ;in eax SecondaDword
0040117B bswap
eax ;bswap(SecondaDword)
0040117D fild
dword_403A71 ;mette uno in St0 (V5 passa a St1)
00401183 mov
ebx, dword_403A15 ;Delta in ebx
00401189 add
eax, ebx ;bswap(SecondaDword) + Delta
0040118B xor
eax, 2004h
00401190 bswap
eax
00401192 mov
dword_403A69, eax ;mette il risultato in 403A69
00401197 fyl2x
;calcola Z = Y log(sub2)(X) dove X è preso da St0
e Y da st1
00401199 fstp
dword_403A6D ;mette il risultato del logaritmo in 403A6D
0040119F fld
dword_403A69 ;mette 403A69 in St0
004011A5 fcomp
dword_40392E ;confronta St0 con 40392E
004011AB fstsw
ax
004011AE sahf
;salva AX nei flag AF CF PF SF ZF
004011AF jb
short loc_401212
004011B1 call
sub_40152B ;call che analizzeremo dopo
004011B6 test
al, al
004011B8 jz
short loc_401212 ;se al è FALSE salta alla messagebox
d'errore
004011BA push
0
004011BC push
offset aEurika
004011C1 push
offset aCongratulation
004011C6 push
hWnd ; hWnd
004011CC call
MessageBoxA
L'istruzione fyl2x come già detto esegue Z= Y log2 (X), voglio
solo specificare che 0 < X < Infinito (cioè X dev'essere
positivo), mentre per Y Infinito < Y < Infinito. Come prima aumentando
il livello di astrazione:
(LowerWord)SecondaDword = V2^bl-(HighWord)SerialDword
dove se V2=1 per la proprietà dei logaritmi avremo log2(V2)=0
Per semplicità chiameremo la locazione 403A69 'V4'.
Passiamo ora ad analizzare la call 40152B:
0040152B finit
;inizializza il copocessore
0040152E fld
dword_403A69 ;mette V4 in St0
00401534 fmul
dword_403A69 ;moltiplicazione
0040153A fmul
dword_403A69
00401540 fmul
dword_403A69
00401546 fmul
dword_403A6D
0040154C fld
dword_403A69 ;rimette V4 in St0
00401552 fmul
dword_403A69 ;moltiplica un'altra volta
00401558 fimul
dword_403A61 ;(V4^2)*V1
0040155E faddp
st(1), st ;(V4^2)*V1 + (le due istruzioni immediatamente
dopo)
00401560 fild
dword_403A65 ;mette V3 in St0
00401566 fmul
dword_403A69 ;V3*V4
0040156C faddp
st(1), st
0040156E fild
dword_40392A ;40392A contine all'interno il valore fisso
12345h (74565d)
00401574 fabs
;st0 in valore assouluto
00401576 fchs
;cambia il segno di st0
00401578 faddp
st(1), st ;somma St1+St0
0040157A fabs
;valore assoluto di St0
0040157C fcomp
dword_403932 ;confronta il risultato delle operazioni
precedenti con 74565d
00401582 fstsw
ax
00401585 sahf
00401586 setb
al ;se la condizione è vera setta al a 1
00401589 retn
Dentro questa call, come potete ben vedere, troviamo una serie di Floating
Point Operations, si tratta di un puro calcolo matematico. Cerchiamo come
prima cosa di elevare il livello di astrazione di questa routine per rendere
le cose più chiare, e facilmente reversibili, ecco cosa fa "matematicamente"
questa call:
Var2 = -.1529248683*((V4^2)*V1+V3*V4-74565.)/ V4^4
Risistemando un pò le cose, ecco che ricaviamo l'equazione finale
da risolvere:
(V4^2)*V1+V4*V3-74565=0
Infine vi riporto la routine che ho usato nel keygen per risolvere questa
equazione:
finit
fild quattro
;4
fimul V1
fimul h74565
;-4ac
fild V3
fimul V3 ;b²
faddp st(1),st
;Delta
fsqrt ;radice
quadrata
fild V3
fchs ;cambio
il segno
faddp st(1),st
fild V1
fild V1
faddp st(1),st
fdivp st(1),st(0)
fstp SecondaDword
mov eax, SecondaDword
bswap eax
xor eax, 2004h
mov ebx, Delta
sub eax, ebx
bswap eax
mov SecondaDword, eax
Bene, ecco tutto!! ;) spero di essere stato chiaro, se non avete capito qualcosa
non esitate a contattarmi. Se invece mi venite a fare richieste di crack..non
vi rispondo :->
Saluto: Quequero, AndreaGeddon, Ironspark, Kratorius, LonelyWolf, ZaiRoN, Misanthropic,
Neural_Noise che è da un pò di tempo che non vedo :) Anticrack
friends: Bon_Scott, Zero, Kreatief, Hell_Master. Porgo le mie scuse alle persone
di cui mi sono dimenticato :)
Noi reversiamo al solo scopo informativo
e di miglioramento del linguaggio Assembly.
Capitoooooooo????? Bhè credo di si ;))))