P.D.C. by The+Q
P.D.C. = Polimorphic Decryption Crackme

Data

by ZaiRoN

 

o3-1o-2oo1

UIC's Home Page

Published by Quequero

"La fantasia senza regole esatte è come la marmellata senza una fetta di pane su cui spalmarla."
Italo Calvino

Beh...Che dire? Il tute e' minuzioso fin nel piu' piccolo dettaglio, i miei migliori complimenti Zai :)

:PPP

....

Home page se presente: non presente...
E-mail: [email protected]

....

Difficoltà

( )NewBies (Z)Intermedio (z)Avanzato ( )Master

 

il titolo dice tutto...ci sarà da divertirsi;))


P.D.C by The+Q
P.D.C. = Polimorphic Decryption Crackme

Written by ZaiRoN

Allegato

 

Introduzione

finiti gli appelli di settembre sono finalmente riuscito a trovare un pò di tempo libero per scrivere questo tutorial. il soggetto in questione è un crackme (a mio avviso) interessante; la particolarità di questo programma sta nel fatto che la routine di protezione vera e propria non esiste ma viene decrittata (a partire dal nome inserito) a runtime. un nome una routine di protezione!
ciò che faremo sarà: studiare l'algoritmo e produrre un keygenerator.

Tools usati

tools necessari:
- softice
- masm o qualsiasi altro compilatore per il keygen
tools opzionali:
- ida / windasm
- hexworkshop o qualsiasi altro editor esadecimale
- upx
 
un pò di buona musica anni '70: Jethro Tull (Thick as a brick, A passion play)

URL o FTP del programma

http://crackmes.cjb.net/

Notizie sul programma

crackme:PPP

Essay

il file è packato con upx; vista la dimensione e le caratteristiche del crackme credo proprio che l'autore abbia utilizzato upx solamente per ridurre le dimensioni dell'eseguibile, quindi se avete bisogno di disassemblare il file potete tranquillamente unpackarlo con un semplice 'upx -d <nome_file>' altrimenti...fate un pò cosa vi pare:PP
dopo questa breve introduzione direi di partire subito senza tante altre chiacchere inutili...
lanciamo il crackme, inseriamo un nome, un serial, entriamo in sice e piazziamo il solito breakpoint sulla funzione GetDlgItemTextA.
usciamo dal sice e ...hmmm... dov'è il pulsante di registrazione? in realtà c'è ma non si vede. per validare la coppia name/serial dovete clickare sul viso della giovane fanciulla. come ho fatto a scoprirlo? semplice, ho clickato su tutta la superficie della finestra e alla fine il sice è poppato:PP:
 
004018F7   call  ebx   <---------------------- chiamata a GetDlgItemTextA. legge il nome
004018F9   mov   esi, eax   
<----------------- esi è la lunghezza del nome inserito
004018FB   lea   eax, [esp+2B0h+var_27C]   
<-- indirizzo da passare alla prossima funzione
004018FF   push  21h
00401901   push  eax
00401902   push  3EBh
00401907   push  edi
00401908   call  ebx   
<---------------------- chiamata a GetDlgItemTextA. legge il serial
0040190A   cmp   esi, 4   
<------------------- confronta la lunghezza del nome con 4
0040190D   jl    loc_401A03
  <--------------- il nome deve contenere almeno 4 caratteri 00401913   test  eax, eax   <----------------- mentre il serial...basta che ci sia
00401915   jz    loc_401A03
0040191B   xor   ecx, ecx
0040191D   lea   edx, [esp+2B0h+var_2A4]   
<-- edx punta ad un buffer di 16 bytes
00401921   mov   [esp+2B0h+var_2A4], ecx   <-- pulisce i primi 4 bytes di questo buffer
00401925   push  10h
00401927   push  edx
00401928   mov   [esp+2B8h+var_2A0], ecx   
<-- pulisce i bytes dal 5° all'8°
0040192C   push  eax
0040192D   lea   eax, [esp+2BCh+var_27C]
00401931   mov   [esp+2BCh+var_29C], ecx   
<-- pulisce i bytes dal 9° al 12°
00401935   push  eax
00401936   mov   [esp+2C0h+var_298], ecx   
<-- pulisce i bytes dal 13° al 16°
0040193A   call  sub_402A02
  <--------------- subito una call interessante
 
prima di proseguire una piccola segnalazione: come avrete già intuito il buffer inizializzato qua sopra sarà importante ai fini della risoluzione del crackme quindi d'ora in poi la sequenza di bytes contenuta in questo buffer sarà identificata con la parola magica: "buffer" (che fantasia:PP)
entriamo di volata nella call:
 
00402A06   xor   ebx, ebx
00402A08   mov   ecx, [ebp+arg_4]   
<-- ecx è la lunghezza del serial inserito
00402A0B   inc   ecx   <--------------- incrementa ecx, sarà il nostro contatore
00402A0C   mov   esi, [ebp+arg_0]   <-- esi punta al serial inserito
00402A0F   mov   edi, [ebp+arg_8]   
<-- edi punta al buffer
00402A12   lodsb   
<------------------- al prende un chr del serial inserito. inoltre esi++
00402A13   dec   ecx   
<--------------- decrementa ecx
00402A14   jz    short loc_402A39   <-- se ho usato tutti i caratteri del serial posso uscire
00402A16   sub   al, 30h
  <----------- al = al - 0x30
00402A18   cmp   al, 9
  <------------- confronta al con 0x09
00402A1A   jbe   short loc_402A1E   
<-- e si comporta di conseguenza
00402A1C   sub   al, 7   
<------------- se al era > 0x09 allora al = al - 0x07
00402A1E   mov   bl, al   
<------------ altrimenti si arriva direttamente qui
00402A20   shl   bl, 4   
<------------- bl = bl * 2^4   (2^4 = 16:))
00402A23   lodsb   
<------------------- al prende un chr del serial inserito. poi esi++  
00402A24   dec   ecx   
<--------------- decrementa ecx
00402A25   jz    short loc_402A36   
<-- se ecx = 0 abbiamo usato tutto il serial e si esce
00402A27   sub   al, 30h   
<----------- al = al - 0x30
00402A29   cmp   al, 9   
<------------- confronta al con 0x09
00402A2B   jbe   short loc_402A2F   
<-- e si comporta di conseguenza
00402A2D   sub   al, 7
  <------------- come prima, se al era > 0x09 allora al = al - 0x07
00402A2F   or    bl, al   
<------------ altrimenti si arriva qui e bl = bl or al
00402A31   xchg  bl, al   
<------------ scambia al con bl
00402A33   stosb   
<------------------- mette al nel byte puntato da buffer
00402A34   jmp   short loc_402A12   <-- torna su e passa ai prossimi caratteri del serial
00402A36   xchg  bl, al   <------------ salto qui se il n° dei caratteri del serial è dispari
00402A38   stosb   
<------------------- mette al nel byte puntato da buffer
 
come vi avevo preannunciato entra in gioco il buffer. ma in che modo? vediamo di spiegare cosa è successo.
due caratteri del serial ne fanno uno del buffer (ad eccezione dell'ultimo se la lunghezza del serial è un numero dispari). ad esempio, se avete inserito '345D23A2' alla fine del ciclo vi troverete con buffer che punta alla sequenza di 4 bytes: '34 5D 23 A2'.
la sequenza di caratteri appena creata altro non è che il serial inserito. quindi il serial viene preso e copiato pari pari nel buffer.
ritorneremo sul buffer tra un bel pò...
riprendiamo il nostro cammino:
 
0040194B   lea   ecx, [esp+2B4h+var_294]    <-- ecx punta al nome inserito
0040194F   push  ebx   
<----------------------- indirizzo per la prossima call
00401950   push  esi   
<----------------------- esi = lunghezza del nome inserito
00401951   push  ecx   
<----------------------- pusha il nome inserito
00401952   call  sub_401A10   <---------------- call interessante
 
poche righe di preparazione alla call che è il primo scalino verso la meta finale.
prima di proseguire un'altra piccola annotazione; il valore pushato in 40194F sarà l'indirizzo relativo al primo byte di una sequenza (inizialmente vuota) di 256 caratteri molto importante...d'ora in poi tale sequenza sarà per noi: "sequence" (hihihih...la fantasia si spreca:PPP)
ecco la call:
 
00401A13   xor   edx, edx   <-------------- edx = 0
00401A15   xor   eax, eax   
<-------------- eax = 0
00401A17   push  ebx
00401A18   push  ebp
00401A19   mov   ebp, [esp+10h+arg_8]
  <-- ebp punta a ciò che abbiamo chiamato sequence
00401A1D   push  esi
00401A1E   push  edi
00401A1F   mov   ecx, ebp   
<-------------- ecx punta a sequence
00401A21   mov   [ecx], al   
<------------- il byte puntato da ecx prende al
00401A23   inc   eax   
<------------------- incrementa al
00401A24   inc   ecx   
<------------------- si sposta sul prossimo carattere di sequence
00401A25   cmp   ax, 100h   
<-------------- ax < 0x100 ???
00401A29   jl    short loc_401A21   
<------ si: salta su...no: prosegui qua sotto
 
mi fermo giusto un attimo per dirvi cosa è successo. ciò che abbiamo chiamato sequence punta ad una sequenza di 256 caratteri fatta così: 00 01 02 03 04...FD FE FF
mi sembra chiaro! continuiamo:

00401A2B   mov   [ebp+100h], dl   
<-------- mette zero dopo il 256° chr della nostra sequence
00401A31   mov   [ebp+101h], dl   
<-------- e un altro zero anche dopo
00401A37   mov   byte ptr [esp+18h+arg_8], dl
00401A3B   xor   esi, esi   
<-------------- esi = 0 ;)
00401A3D   mov   ebx, ebp
00401A3F   mov   [esp+18h+var_8], 100h
00401A47   mov   edi, [esp+18h+arg_8]   
<-- edi è un semplice contatore
00401A4B   mov   ecx, [esp+18h+arg_0]   <-- ecx punta al nome inserito
00401A4F   and   edi, 0FFh   
<------------- di edi resta soltanto il valore di dl
00401A55   xor   eax, eax   <-------------- eax = 0
00401A57   xor   edx, edx   
<-------------- edx = 0
00401A59   mov   al, [edi+ecx]   
<--------- al = name[edi], name è il nome inserito!
00401A5C   mov   dl, [ebx]   
<------------- dl = [sequence]
00401A5E   add   esi, eax   
<-------------- esi = esi + eax
00401A60   add   edx, esi   
<-------------- edx = edx + esi
00401A62   and   edx, 800000FFh   
<-------- edx prende il valore di dl
00401A68   jns   short loc_401A72   <------ il salto dipende dal flag s modificato dall'and
00401A6A   dec   edx   
00401A6B   or    edx, 0FFFFFF00h
00401A71   inc   edx
00401A72   mov   byte ptr [esp+18h+var_4], dl
00401A76   mov   esi, [esp+18h+var_4]
00401A7A   and   esi, 0FFh
00401A80   lea   eax, [esi+ebp]
  <-------- eax punta all'esi-esimo chr di sequence
00401A83   push  eax
00401A84   push  ebx
  <------------------- ebx punta a sequence
00401A85   call  sub_401BC0   <------------ vedi commento
 
non sto ad entrare dentro questa call perchè è molto piccola e semplice quindi ne farò una breve descrizione soltanto a parole. voi non fate i pigri, entrateci almeno una volta:P...ritorniamo a noi: cosa fà? scambia il valore del byte puntato da eax con quello puntato da ebx. niente di più semplice:))
veniamo al resto della call:

00401A8A   lea   eax, [edi+1]   
<------------------ eax = edi + 1
00401A8D   add   esp, 8
00401A90   cdq   
<--------------------------------- si prepara per la prossima divisione
00401A91   idiv  [esp+18h+arg_4]   
<--------------- esegue l'operazione: eax / length(name)
00401A95   mov   eax, [esp+18h+var_8]   
<---------- contatore relativo al n° di cicli da fare
00401A99   inc   ebx   
<--------------------------- si sposta sul prossimo byte di sequence
00401A9A   dec   eax   
<--------------------------- numero di cicli che restano da eseguire
00401A9B   mov   [esp+18h+var_8], eax
  <---------- si salva il valore di eax
00401A9F   mov   byte ptr [esp+18h+arg_8], dl
  <-- salva il resto della divisione
00401AA3   jnz   short loc_401A47   
<-------------- torna su se eax è diverso da zero
 
cosa è successo?
dopo una prima fase di inizializzazione di sequence (costruzione dei bytes da '00' a 'FF') c'è stata la modifica vera e propria di quest'ultima con la complicità del nome inserito.
non è una vera e propria modifica ma assomiglia molto più ad uno shuffle dei 256 bytes.
infatti, il valore del generico elemento in posizione j (j va da 0 a FF incrementando di 1 ad ogni iterazione) della sequenza viene scambiato con il valore del generico elemento in posizione k (k è ottenuto partendo da uno dei caratteri del nome inserito ed effettuando le operazioni comprese nell'intervallo di istruzioni 401A59/401A72).
per cui ogni singolo scambio di posizione dipende esclusivamente dal carattere corrente del nome inserito; da notare come vengono utilizzati i caratteri del nome inserito: nella prima iterazione viene preso il 1° carattere, nella seconda il 2° e così via fino all'ultimo; una volta terminati i caratteri del nome, riparte dal 1°, poi prende il 2° and so on. questo procedimento è gestito dalla div delle righe finali, in particolare dal resto che seleziona correttamente i caratteri del nome.
ok, usciamo dalla call e proseguiamo con la restante marea di codice:
 
00401957   mov   ecx, 13h
0040195C   mov   esi, offset aCloseYourEyesA
 
esi punta alla seguente sequenza di caratteri (i doppi apici sono esclusi:):
"Close your eyes and begin to relax. Take a deep breath, and let it out slowly."

00401961   lea   edi, [esp+2C0h+var_258]   
<-- altro buffer d'appoggio
00401965   push  ebx
  <---------------------- pusha il puntatore a sequence
00401966   repe movsd   
<--------------------- muove 76 bytes (0x13*4) da esi al nuovo buffer
00401968   movsw   
<-------------------------- muove i restanti due bytes
 
la solita precisazione...in quest'ultime righe ha spostato la frase "Close...". edi punta a questa nuova sequenza; bene, d'ora in poi indicherò con seq_clos questa nuova sequenza di 78 bytes.

0040196A   lea   edx, [esp+2C4h+var_258]   
<-- edx punta a seq_clos
0040196E   push  4Fh   
<---------------------- pusha il numero decimale 79
00401970   push  edx
00401971   movsb
00401972   call  sub_401AB0   
<--------------- call interessante
 
se è interessante ci entriamo al volo:
 
00401AB3   mov   edx, [esp+0Ch+arg_4]   <-- edx = 0x4F
00401AB7   push  ebx
00401AB8   push  esi
00401AB9   mov   esi, [esp+14h+arg_8]   
<-- esi punta a sequence
00401ABD   xor   ebx, ebx
00401ABF   mov   al, [esi+100h]   
<-------- al = 0
00401AC5   mov   cl, [esi+101h]
  <-------- cl = 0
00401ACB   test  edx, edx
  <-------------- edx è sicuramente maggiore di zero
00401ACD   mov   byte ptr [esp+14h+arg_8], al
00401AD1   mov   byte ptr [esp+14h+var_C], cl
00401AD5   jle   loc_401BAD   
<------------ per cui niente salto...
00401ADB   push  ebp
00401ADC   mov   ebp, [esp+18h+var_C]
00401AE0   push  edi
00401AE1   mov   edi, [esp+1Ch+arg_8]
00401AE5   and   edi, 0FFh   
<------------- edi = edi and 0xFF = dl
00401AEB   and   ebp, 0FFh   <------------- ebp = ebp and 0xFF
 
per ora niente di complicato, solo una serie di semplici operazioni preliminari. veniamo velocemente al resto della call:
 
00401AF1   lea   eax, [edi+1]
00401AF4   and   eax, 800000FFh
00401AF9   jns   short loc_401B02
00401AFB   dec   eax
00401AFC   or    eax, 0FFFFFF00h
00401B01   inc   eax
00401B02   mov   byte ptr [esp+1Ch+arg_8], al
00401B06   xor   ecx, ecx
00401B08   mov   edi, [esp+1Ch+arg_8]
00401B0C   and   edi, 0FFh
00401B12   mov   cl, [edi+esi]   
<--------- cl = sequence[edi]
00401B15   lea   eax, [edi+esi]   
<-------- eax punta a sequence[edi]
00401B18   add   ecx, ebp   
<-------------- ecx = ecx + ebp
00401B1A   mov   [esp+1Ch+var_4], eax
00401B1E   and   ecx, 800000FFh
00401B24   jns   short loc_401B2E
00401B26   dec   ecx
00401B27   or    ecx, 0FFFFFF00h
00401B2D   inc   ecx
00401B2E   mov   byte ptr [esp+1Ch+var_C], cl
00401B32   mov   ebp, [esp+1Ch+var_C]   
<-- ebp = cl dell'istruzione precedente
00401B36   and   ebp, 0FFh   
<------------- ebp = ebp and 0x000000FF
00401B3C   lea   ecx, [esi+ebp]   
<-------- ecx punta a sequence[ebp]
00401B3F   push  ecx
00401B40   push  eax
00401B41   mov   [esp+24h+var_8], ecx
00401B45   call  sub_401BC0   
<------------ questa l'abbiamo già incontrata. scambia il byte puntato da eax con quello puntato da ecx (ovviamente relativamente a sequence;)
00401B4A   mov   eax, [esp+24h+var_8]   
<-- eax punta a sequence[ebp]
00401B4E   xor   edx, edx   
<-------------- edx = 0
00401B50   xor   ecx, ecx   
<-------------- ecx = 0
00401B52   add   esp, 8
00401B55   mov   dl, [eax]   
<------------- dl = sequence[ebp]
00401B57   mov   eax, [esp+1Ch+var_4]   
<-- eax punta a sequence[edi]
00401B5B   mov   cl, [eax]   
<------------- cl = sequence[edi]
00401B5D   add   edx, ecx   
<-------------- edx = edx + ecx
00401B5F   and   edx, 800000FFh   
<-------- edx = edx and 0x800000FF
00401B65   jns   short loc_401B6F
00401B67   dec   edx
00401B68   or    edx, 0FFFFFF00h
00401B6E   inc   edx
00401B6F   mov   eax, [esp+1Ch+arg_0]
  <-- eax punta a seq_clos
00401B73   and   edx, 0FFh   
<------------- edx prende il valore contenuto in dl
00401B79   mov   cl, [edx+esi]   
<--------- cl = sequence[edx]
00401B7C   mov   dl, [ebx+eax]   
<--------- dl = seq_clos[ebx]
00401B7F   xor   dl, cl   
<---------------- dl = dl xor cl
00401B81   mov   [ebx+eax], dl   
<--------- seq_close[ebx] = dl
00401B84   mov   eax, [esp+1Ch+arg_4]   
<-- eax = 0x4F
00401B88   inc   ebx   
<------------------- ebx ++
00401B89   cmp   ebx, eax
  <-------------- ebx < eax ???
00401B8B   jl    loc_401AF1   
<------------ si: salta su...no: esci dal ciclo
 
un bel ciclo che si ripete per 0x4F volte che ha lo scopo di modificare seq_clos. in realtà viene modificata anche sequence ma credo sia una modifica utile soltanto per incasinarci ulteriormente le cose!
la modifica di seq_clos avviene tramite un semplice xor di un byte di sequence con uno di seq_clos (401B79...401B81).
non mi metto a farvi vedere esempi o roba simile perchè la cosa risulterebbe alquanto complicata e noiosa...i commenti e qualche iterazione dovrebbero bastare a farvi un buon quadro generale di ciò che succede...
usciamo dalla call e proseguiamo con lo studio del codice; i più attenti di voi noteranno subito una somiglianza con parte di codice già vista.
prima di proseguire però la solita precisazione: in 40197D eax punta ad un buffer che verrà utilizzato nella successiva call; essendo importante d'ora in poi sarà: seq_conc.
possiamo continuare:
 
0040197D   lea   eax, [esp+2D0h+var_104]   <-- eax punta a seq_conc
00401984   push  eax
00401985   push  48h   
<---------------------- pusha il numero decimale 72
00401987   push  offset aConcentrateOnY   
<--- pusha: "Concentrate on your breathing.With each breath you become more relaxed."
0040198C   call  sub_401A10   <--------------- hmmm...
 
un'altra bella frase e un'altra chiamata ad una call che abbiamo già incontrato in precedenza (vedi indirizzo 401952).
visto che l'abbiamo già incontrata possiamo tirare subito le nostre conclusioni: la sequenza di bytes denominata con seq_conc viene modificata con l'ausilio dei 72 bytes della frase "Concentrate...". se in precedenza avete capito il funzionamento della call penso che ci si possa fermare qui con le chiacchere.
solita puntuale precisazione: in 401991 ecx punta ad un buffer che verrà utilizzato nella successiva call; anche questo servirà alla causa e anche questo avrà un nome: seq_imag.
continuiamo:
 
00401991   lea   ecx, [esp+2DCh+var_208]   <-- ecx punta a seq_imag
00401998   push  ecx   
<---------------------- pusha il puntatore a seq_imag
00401999   push  2Ch   
<---------------------- pusha il numero decimale 44
0040199B   push  offset aImagineABrilli
  <--- pusha: "Imagine a brilliant white light above you, "
004019A0   call  sub_401A10   
<--------------- hmmm...ancora una volta questa call
 
ormai siamo diventati bravissimi quindi sappiamo già che cosa succederà qua dentro: la sequenza di bytes denominata con seq_imag viene modificata utilizzando i 44 caratteri della frase "Imagine...". tutto qui! non perdiamo tempo e continuiamo di corsa...il bello deve ancora arrivare:))
 
004019A5   add   esp, 38h
004019A8   lea   edx, [esp+2B0h+var_208]   
<-- edx punta a seq_imag
004019AF   lea   eax, [esp+2B0h+var_104]   
<-- eax punta a seq_conc
004019B6   lea   ecx, [esp+2B0h+var_258]   
<-- ecx punta a seq_clos
004019BA   push  0BADBABEh   
<---------------- pusha questo strano valore esadecimale
004019BF   push  edx   
<---------------------- pusha il puntatore a seq_imag
004019C0   push  eax   
<---------------------- pusha il puntatore a seq_conc
004019C1   push  4Fh   
<---------------------- pusha il numero decimale 79
004019C3   push  ecx   
<---------------------- pusha il puntatore a seq_clos
004019C4   call  sub_402A7D   
<--------------- e il tutto verrà utilizzato da questa call
 
entriamo nella call:
 
00402A80   cmp   dword_40814C, 0
00402A87   jz    short loc_402A8F   
<-- si salta
   ...
00402A8F   call  j_GetProcessHeap   <-- chiamata della funzione GetProcessHeap
00402A94   push  1000h   
<------------- pusha il valore 0x1000
00402A99   push  8   
<----------------- pusha il valore 0x08
00402A9B   push  eax   
<--------------- pusha eax
00402A9C   call  j_HeapAlloc   
<------- chiamata della funzione HeapAlloc
 
come potete vedere entrano in gioco due nuove funzioni, un pò inusuali, che hanno in questo crackme un ruolo fondamentale. ecco qua una breve spiegazione di queste due funzioni.
- GetProcessHeap:
è usata per ottenere l'handle dell'heap relativo al processo chiamante (in questo caso il crackme stesso); come potete vedere dal codice qua sopra la funzione non ha bisogno di parametri e se va a buon fine il valore di ritorno è l'handle relativo all'heap. il valore di ritorno viene in seguito usato dall'altra funzione:
- HeapAlloc:
alloca un blocco di memoria nell'heap del processo. ecco la sintassi:
 
LPVOID HeapAlloc(
    HANDLE hHeap,    // handle ritornato dalla funzione GetProcessHeap
    DWORD dwFlags,   
// flag per specificare alcune proprietà relative al tipo di allocazione
    DWORD dwBytes    
// numero di bytes da allocare
   );
 
nel nostro caso specifico abbiamo:
 
hHeap = 00490000
è l'handle fornito dalla GetProcessHeap
dwFlags = 8  
HEAP_ZERO_MEMORY: la memoria che verrà allocata sarà inizializzata a zero
dwBytes = 0x1000
è il numero di bytes da allocare (4096 in decimale)
 
se la funzione va a buon fine il valore ritornato è il puntatore al blocco di memoria allocato.
penso che questa breve descrizione sia più che sufficiente...se volete approfondire la cosa vi consiglio di leggervi la descrizione riportata nella nota 'api guide'.
ok; si è quindi riservato un pò di spazio...vediamo come lo userà:
 
00402AA1   or    eax, eax   <-------------- controlla se la funzione è andata a buon fine
00402AA3   jnz   short loc_402AA9   
<------ se tutto è ok si salta
   ...
00402AA9   mov   dword_40814C, eax   <----- eax = 49110C punta al blocco di memoria allocato
00402AAE   mov   byte ptr [eax], 0C3h   
<-- 'C3' è il valore del primo byte del blocco
00402AB1   push  [ebp+arg_0]   
<----------- pusha seq_clos
00402AB4   pop   dword_40813C   
<---------- mette in [40813C] il valore appena pushato
00402ABA   push  [ebp+arg_4]   <-----------
pusha 0x4F
00402ABD   pop   dword_408140   
<---------- poppa in [408140] il valore appena pushato
00402AC3   push  [ebp+arg_8]   
<----------- pusha seq_conc
00402AC6   pop   dword_408150   
<---------- poppa in [408150] il valore appena pushato
00402ACC   push  [ebp+arg_C]   
<----------- pusha seq_imag
00402ACF   pop   dword_408154   
<---------- poppa in [408154] il valore appena pushato
00402AD5   push  [ebp+arg_10]   
<---------- pusha 0xBABDBABE
00402AD8   pop   dword_408134   
<---------- poppa in [408134] il valore appena pushato
00402ADE   mov   eax, 1   
<---------------- eax = 1 per dire che tutto è andato ok
 
dopo questa fase di inizializzazione vediamo cosa ci aspetta:
 
004019C9   test  eax, eax   <---------- ci sono stati errori ???
004019CB   jz    short loc_401A03   
<-- si: salta...no: prosegui qua sotto
004019CD   call  sub_402B49   
<-------- un'altra bella call tutta per noi:))
 
senza fare discorsi via nella call più importante di tutto il crackme.
perchè è la più importante? è quella che finalmente decritta la routine di protezione. tutto il codice che verrà decrittato partirà dall'indirizzo 49110C (valore ritornato dalla HeapAlloc):
 
00402B4C   add   esp, 0FFFFFFFCh
00402B4F   cmp   dword_40814C, 0   
<--- confronta la dword puntata da 40814C con 0
00402B56   jnz   short loc_402B5C   
<-- se sono uguali errore altrimenti si prosegue
 
il valore puntato da 40814C è stato aggiornato in 402AA9 e contiene il puntatore al blocco di memoria allocato. se c'è stato qualche errore nel processo di allocazione non si salta (e verrà prodotto un'eventuale errore) altrimenti si salta e si prosegue indisturbati.

00402B5C   push  dword_408140   
<------------ pusha 0x4F
00402B62   pop   [ebp+var_4]
  <------------- e lo salva in [ebp-04]
00402B65   mov   edi, dword_40814C   
<------- edi punta al blocco di memoria allocato(49110C)
00402B6B   mov   esi, offset loc_402830
  <-- esi punta a 402830
00402B70   lodsb   
<------------------------- prende il byte puntato da esi e lo mette in al, inoltre esi++
00402B71   movzx ecx, al   
<----------------- ecx = al
00402B74   repe movsb   
<-------------------- copia i primi 0x14 bytes puntati da esi a partire dal byte puntato da edi; aggiorna edi
 
questa call è molto importante quindi conviene fermarsi spesso e chiarire tutti i singoli passaggi.
i seguenti 20 bytes vengono presi dall'offset 0x2830 del file e vengono copiati a partire dall'indirizzo 49110C:
FF 35 34 81 40 00 8F 05 38 81 40 00 56 0F B7 1E 0F B7 4E 02
se andiamo all'indirizzo 49110C scopriamo una cosa mooolto interessante (chiedo venia al Que per gli opcode ma stavolta mi sembrano doverosi:PP):
 
0049110C   FF3534814000   push dword ptr [00408134]   <----- pusha 0xBABDBABE
00491112   8F0538814000   pop dword ptr [00408138]   <------ e lo salva in [408138]
00491118   56             push esi   
<---------------------- ??? non sappiamo ancora cosa sia
00491119   0FB71E         movzx ebx, word ptr [esi]   
<----- prende la word puntata da esi
0049111C   0FB74E02       movzx ecx, word ptr [esi+02]   
<-- prende la successiva word
 
ci siete??? i 20 bytes appena copiati sono in realtà queste 5 istruzioni piazzate a partire dal 1° byte del blocco di memoria allocato in precedenza. si inizia quindi a delineare la vera routine di protezione. non male vero?
bene, vediamo quale altra diavoleria infernatica verrà fuori:

00402B76   mov   esi, dword_40813C   
<----------- esi punta a seq_clos
00402B7C   add   esi, [ebp+var_4]   
<------------ esi = esi + 0x4F
00402B7F   mov   bl, [esi-1]   
<----------------- bl = byte puntato da (esi-1)
00402B82   movzx eax, bl   
<--------------------- eax = bl
00402B85   and   al, 0Fh   
<--------------------- al = al and 0x0F (se al=34 ho che al=04;)
00402B87   mov   cl, 6   
<----------------------- cl = 6
00402B89   div   cl   
<-------------------------- ah = resto dell'operazione al /cl
00402B8B   movzx eax, ah   
<--------------------- eax = ah
00402B8E   shl   eax, 2   
<---------------------- eax = eax * 4
00402B91   add   eax, offset off_4029EA   
<------ eax = eax + 4029EA
00402B96   mov   esi, [eax]   
<------------------ esi = [eax]
00402B98   movzx ecx, byte ptr [esi]   
<--------- ecx contiene il byte puntato da esi
00402B9B   inc   esi
  <------------------------- esi++
00402B9C   cmp   dword ptr [esi], 594C3050h   <-- la dword puntata da esi è "P0LY" ???
00402BA2   jnz   short loc_402BBB   
<------------ si: prosegui sotto...no: salta sotto
00402BA4   add   esi, 4   
<---------------------- esi = esi + 4 (si sposta di 4 bytes)
00402BA7   sub   ecx, 5   <---------------------- ecx = ecx - 5
00402BAA   lodsb
  <----------------------------- al prende il byte puntato da esi
00402BAB   movzx eax, al   <--------------------- eax = al
00402BAE   push  eax
  <------------------------- pusha eax
00402BAF   call  sub_402BD0   
<------------------ call su cui torneremo tra poco...
00402BB4   cmp   ecx, 0   
<---------------------- ecx = 0 ???
00402BB7   jnz   short loc_402B9C   
<------------ si: proseguo...no: salto di nuovo su
00402BB9   jmp   short loc_402BBE   
<------------ salto:))
00402BBB   movsb   
<----------------------------- byte ptr [edi] = byte ptr [esi]
00402BBC   loop  loc_402B9C   <------------------ il tutto viene eseguito ecx volte
00402BBE   dec   [ebp+var_4]   <----------------- decrementa il contatore (all'inizio 0x4F)
00402BC1   jnz   short loc_402B76   <------------ se è zero esco dal ciclo, sennò torno su
 
forza e coraggio. vediamo di chiarire questo ciclo...stavolta i soli commenti sono veramente poca roba in confronto a ciò che c'è dietro!!!
partiamo dal risultato che otteniamo: il ciclo appena visitato decritta una serie di istruzioni che verranno utilizzate in seguito dal crackme stesso per validare la nostra name/serial combination. queste istruzioni si piazzano direttamente dopo le 5 create in precedenza.
detto questo vediamo di capire come vengono effettivamente generate queste fantomatiche istruzioni.
innanzitutto il blocco di istruzioni comprese tra gli indirizzi 402B76 e 402BC1 viene ripetuto per ben 0x4F (79 in decimale;P) volte.
se steppate per un pò all'interno di questo ciclo noterete una cosa molto importante: in ognuna di queste 79 iterazioni non viene decrittata una sola istruzione ma bensì un gruppo di istruzioni per volta; in più le istruzioni create ad ogni iterazione possono essere suddivise in 6 gruppi ben distinti.
vediamo di spiegarci meglio.
partiamo col notare che le istruzioni comprese tra gli indirizzi 402B76/402B8E mi portano ad avere in eax un valore multiplo di 4 compreso tra 0 e 20, estremi compresi (si arriva immediatamente a questa conclusione notando che il resto della divisione, compreso tra 0 e 5, viene poi moltiplicato per 4 quindi...). questo valore viene sommato al valore fisso 4029EA. eccovi qua i primi 20 bytes puntati da 4029EA trovati nel file:
51294000 5C294000 75294000 94294000 AA294000 C3294000
ok e allora? cosa ci facciamo?
ognuna di queste 6 quadruple rappresenta un indirizzo (occhio! come al solito le cose si ribaltano. ie: 51294000 diventa l'indirizzo 00402951).
in 402B96 esi prende una di queste 6 quadruple e si serve dei valori puntati da esi per generare le istruzioni.
partiamo dall'ultima ed esaminiamo queste sei quadruple una per una.
- esi = 4029C3
esi -> 26 668BC3 FF1548814000 66330538814000 668BD8 668BC1 FF1548814000 66330538814000 668BC8
il primo byte (0x26) indica il numero di byte che verranno utilizzati per le nuove istruzioni. gli 0x26 bytes che seguono sono la codifica delle seguenti istruzioni:
 
668BC3           mov ax, bx
FF1548814000     call dword ptr [00408148]
66330538814000   xor ax, word ptr [00408138]
668BD8           mov bx, ax
668BC1           mov ax, cx
FF1548814000     call dword ptr [00408148]
66330538814000   xor ax, word ptr [00408138]
668BC8           mov cx, ax
 
per cui tutte le volte che esi ha il valore 4029C3 le istruzioni generate sono queste. sono operazioni molto semplici e anche la call non è da meno:
 
00402A5B    push edx
00402A5C    push esi
00402A5D    push eax
00402A5E    movzx eax, al
00402A61    mov  esi, dword_408150   
<-- esi punta a seq_conc
00402A67    mov  dl, [eax+esi]   
<------ dl = seq_conc[eax]
00402A6A    pop  eax
00402A6B    movzx eax, ah
00402A6E    mov  esi, dword_408154   
<-- esi punta a seq_imag
00402A74    mov  dh, [eax+esi]   
<------ dh = seq_imag[eax]
00402A77    mov  ax, dx
00402A7A    pop  esi
00402A7B    pop  edx
 
ecco qua il perchè della costruzione di seq_conc e seq_imag. non credo ci sia da aggiungere altro per questa call che parla da sola! passiamo al secondo gruppo di istruzioni.
 
- esi = 4029AA
esi -> 18 668BC1 FF1548814000 6633D8 668BC3 FF1548814000 662BC8
anche in questo caso il primo byte rappresenta il numero dei bytes utilizzati. ecco qua le istruzioni generate:
 
668BC1         mov ax, cx
FF1548814000   call dword ptr [00408148]
6633D8         xor bx, ax
668BC3         mov ax, bx
FF1548814000   call dword ptr [00408148]
662BC8         sub cx, ax
 
un caso simile al precedente quindi nessun problema.
ora fate attenzione perchè arrivano i primi dolori (non vi spaventate:))...
 
- esi = 402994
esi -> 15 6693 6633C1 50304C5900 668BD8 50304C5900 6633C8
sembra tutto come prima...e invece no! come vedremo tra pochissimo entrano in gioco le istruzioni comprese tra gli indirizzi 402BA4 e 402BB9 che ci incasineranno un pò le cose.
analizziamo la sequenza di bytes puntata da esi.
il primo byte non rappresenta più il numero di bytes relativi alle istruzioni generate...potrebbe anche non esserci.
i 5 bytes che seguono rappresentano le due istruzioni:
 
6693     xchg ax,bx
6633C1   xor ax, cx
per ora è tutto come prima. vediamo i successivi 5 bytes: 50304C59 00
ogni qual volta troviamo la parola "P0LY" (50304C59) entriamo nella 'call 402BD0' che non abbiamo ancora analizzato; pare proprio sia arrivato anche il suo momento; ecco qua le istruzioni fondamentali:
 
00402BD5   movzx   eax, bl   <----------------- eax = bl, bl prende tale valore in 402B7F
00402BD8   shr     al, 4   
<------------------- al = al / 16
00402BDB   mov     cl, 6   
<------------------- cl = 6
00402BDD   div     cl   
<---------------------- ah = resto di (eax / 6)
00402BDF   movzx   eax, ah   
<----------------- eax = resto della divisione precedente
00402BE2   shl     eax, 2   
<------------------ eax = eax * 4
00402BE5   cmp     [ebp+arg_0], 0
  <---------- [ebp+arg_0] è il valore dopo "P0LY", in questo caso 0
00402BE9   jnz     short loc_402BF2
00402BEB   add     eax, offset off_40284F   
<-- eax = eax + 40284F
00402BF0   jmp     short loc_402C17   
<-------- esce dalla serie dei controlli
00402BF2   cmp     [ebp+arg_0], 1   
<---------- confronto con 1 per [ebp+arg_0]
00402BF6   jnz     short loc_402BFF
00402BF8   add     eax, offset off_40288E   
<-- eax = eax + 40288E
00402BFD   jmp     short loc_402C17
00402BFF   cmp     [ebp+arg_0], 2   
<---------- confronto con 2 per [ebp+arg_0]
00402C03   jnz     short loc_402C0C
00402C05   add     eax, offset off_4028CF   
<-- eax = eax + 4028CF
00402C0A   jmp     short loc_402C17
00402C0C   cmp     [ebp+arg_0], 3   
<---------- confronto con 3 per [ebp+arg_0]
00402C10   jnz     short loc_402C17
00402C12   add     eax, offset off_402910   
<-- eax = eax + 402910
00402C17   mov     esi, [eax]   
<-------------- esi punta i bytes delle istruzioni da copiare
00402C19   lodsb   
<--------------------------- al = byte puntato da esi, esi++
00402C1A   movzx   ecx, al   
<----------------- ecx = al
00402C1D   repe movsb   <
---------------------- copia ecx bytes puntati da esi a partire dal byte puntato da edi
 
come potete vedere c'è una grossa similitudine tra quello che avevamo detto in precedenza e quello che succede qui; anche in questo caso vengono generate istruzioni con una piccolissima differenza...mentre prima veniva generato un intero blocco di istruzioni, adesso ne viene generata soltanto una...ad ogni poly corrisponde una ed una sola istruzione!
date un'occhiata approfondita ai 0x102 bytes a partire dall'offset fisico 0x284F del file (corrisponde al 40284F menzionato nel listato qua sopra). attenzione: i bytes che trovate qui sotto sono già invertiti mentre quelli che trovate nel file non lo sono!!! 
supponiamo che [ebp+arg_0] = 0, l'istruzione generata è una di queste 6:
 
40284F+00: 00402867 punta a: 04 66353412         che è la codifica di   xor ax, 1234
40284F+04: 0040286C punta a: 04 66053412         che è la codifica di   add ax, 1234
40284F+08: 00402871 punta a: 04 66C1C072         che è la codifica di   rol ax, 72
40284F+0C: 00402876 punta a: 07 66030538814000   che è la codifica di   add ax, [00408138]
40284F+10: 0040287E punta a: 07 662B0538814000   che è la codifica di   sub ax, [00408138]
40284F+14: 00402886 punta a: 07 66330538814000   che è la codifica di   xor ax, [00408138]
 
il valore sommato a 40284F è il valore che ha eax dopo l'istruzione in 402BE2; come era successo prima, anche stavolta tale valore è un multiplo di 4 compreso tra 0 e 20.
piccola osservazione: il primo byte che precede l'opcode dell'istruzione che verrà generata rappresenta di nuovo il numero dei bytes dell'istruzione stessa; questo è il perchè del fatto viene generata una sola istruzione di tipo poly per volta (vedi 402C19/402C1D).
supponiamo ora che [ebp+arg_0] = 1; anche in questo caso ne viene generata soltanto una tra:
 
40288E+00: 004028A6 punta a: 05 6681F33412       che è la codifica di   xor bx, 1234
40288E+04: 004028AC punta a: 05 6681C33412       che è la codifica di   add bx, 1234
40288E+08: 004028B2 punta a: 04 66C1C372         che è la codifica di   rol bx, 72
40288E+0C: 004028B7 punta a: 07 66031D38814000   che è la codifica di   add bx, [408138]
40288E+10: 004028BF punta a: 07 662B1D38814000   che è la codifica di   sub bx, [408138]
40288E+14: 004028C7 punta a: 07 66331D38814000   che è la codifica di   xor bx, [408138]
 
per finire: [ebp+arg_0] = 2:
 
4028CF+00: 004028E7 punta a: 05 6681F13412       che è la codifica di   xor cx, 1234
4028CF+04: 004028ED punta a: 05 6681C13412       che è la codifica di   add cx, 1234
4028CF+08: 004028F3 punta a: 04 66C1C172         che è la codifica di   rol cx, 72
4028CF+0C: 004028F8 punta a: 07 66030D38814000   che è la codifica di   add cx, [408138]
4028CF+10: 00402900 punta a: 07 662B0D38814000   che è la codifica di   sub cx, [408138]
4028CF+14: 00402908 punta a: 07 66330D38814000   che è la codifica di   xor cx, [408138]
 
tre poly: tipo 0, 1 e 2 che coinvolgono rispettivamente ax, bx e cx.
dal listato potete vedere che è previsto un ulteriore caso: [ebp+arg_0] = 3. ho controllato e ricontrollato più volte e sono venuto alla conclusione che questo caso non si presenta mai. forse un bug o semplicemente un depistamento? spero soltanto non sia una mia paurosa svista:))
ok, adesso sappiamo per filo e per segno come vengono decrittate le istruzioni!
tenendo sotto mano questa piccola tabella possiamo finire di scrivere le istruzioni che vengono generate se esi = 402994 (vi ricordo che esi -> 15 6693 6633C1 50304C5900 668BD8 50304C5900 6633C8):
 
6693     xchg ax,bx
6633C1   xor ax, cx
  xx         xx       <-- il valore dopo P0LY è 0 quindi è una delle 6 del tipo 0
668BD8   mov bx, ax   
  xx         xx       <-- il valore dopo P0LY è 0 quindi è una delle 6 del tipo 0
6633C8   xor cx, ax
 
fatto questo vediamo gli altri possibili valori di esi.
 
- esi = 402975
esi -> 1E 50304C5901 66331D38814000 FF1544814000 50304C5902 66030D38814000
anche qui siamo di fronte a ben due poly (la prima di tipo 1 e la seconda di tipo 2) ma visto che ormai abbiamo capito tutto vi piazzo subito le istruzioni che vengono generate:
 
  xx                 xx                        <-- poly di tipo 1
66331D38814000   xor bx, word ptr [00408138]   
FF1544814000     Call dword ptr [00408144]
  xx                 xx                        <-- poly di tipo 2
66030D38814000   add cx, word ptr [00408138]
 
prima di passare al prossimo gruppo di istruzioni è bene vedere che cosa fa questa nuova call:
 
00402A3E    pusha
00402A3F    mov  eax, dword_408138
00402A44    xor  eax, 12345678h
00402A49    cdq
00402A4A    mov  cx, 987h
00402A4E    imul cx
00402A51    rol  eax, 27h
00402A54    mov  dword_408138, eax
00402A59    popa
 
lo scopo di questa piccola call  è quello di cambiare il valore puntato da 408138 (inizialmente tale valore era 0x0BADBABE). non ho messo nessun commento perchè il codice si commenta da solo.
 
- esi = 40295C
esi -> 18 50304C5901 66331D38814000 50304C5902 66030D38814000
4 istruzioni di cui 2 poly. eccole:
 
  xx                 xx                        <-- poly di tipo 1
66331D38814000   xor bx, word ptr [00408138]   
  xx                 xx                        <-- poly di tipo 2
66030D38814000   add cx, word ptr [00408138]
 
- esi = 402951
esi -> 0A 50304C5901 50304C5902
2 istruzioni di tipo poly, la prima che coivolge bx e la seconda con cx (quindi tipo 1 e tipo 2):
 
  xx   xx   <-- poly di tipo 1
  xx   xx   <-- poly di tipo 2
 
fantastico! sappiamo come vengono decrittate tutte le istruzioni!
possiamo finire lo studio della 'call 402B49':
 
00402BC3   mov   esi, offset loc_402845   <-- esi = 402845
00402BC8   lodsb   
<------------------------- al = [esi] = 9
00402BC9   movzx ecx, al   
<----------------- muove in ecx il valore 9
00402BCC   repe movsb   
<-------------------- copia 9 bytes puntati da 4028495 a partire dal byte puntato da edi
 
con queste poche linee si conclude la costruzione dalla routine di protezione vera e propria. come era successo per le istruzioni iniziali anche quest'ultime relative alla call decriptata sono fisse. eccole qua:
 
5E         pop esi   <-------------------- poppa il valore pushato all'inizio della call
66891E     mov word ptr [esi], bx   
<----- sposta bx nella word puntata da esi
66894E02   mov word ptr [esi+02], cx   
<-- sposta cx nella seconda word puntata da esi
C3         ret   
<------------------------ esce:)
 
per ora il tutto può sembrare un pò oscuro ma se riuscite a pazientare ancora un minuto...forza ragazzi (e ragazze;) ci siamo quasi!!!
usciamo dalla call e vediamo le poche righe che ancora ci restano da analizzare:
 
004019D2    lea  edx, [esp+2B0h+var_2A4]   <-- edx punta a buffer (vi ricordate cos'è?)
004019D6    push 10h   
<---------------------- pusha il numero decimale 16
004019D8    push edx   
<---------------------- pusha il puntatore al buffer
004019D9    call sub_402B13   
<--------------- eheheh un'altra call:PP
 
toh! chi si rivede: il buffer! se non vi ricordate (il che è probabile:)) di questo individuo tornate all'inizio del tutorial e rileggetevi cosa succede intorno all'indirizzo 40193A. non ne avete voglia? oggi sono particolarmente buono, quindi: buffer è il serial che abbiamo inserito...entriamo nella call:
 
00402B19    cmp  dword_40814C, 0   <--- sicuramente diverso da zero
00402B20    jnz  short loc_402B28   
<-- e si salta
   ...
00402B28    mov  ecx, [ebp+arg_4]   <-- ecx = 0x10
00402B2B    shr  ecx, 2
00402B2E    mov  [ebp+var_4], ecx   
<-- [ebp+var_4] = 4
00402B31    mov  esi, [ebp+arg_0]   
<-- esi punta a buffer
00402B34    call dword_40814C   
<------ questa ci porta drittidritti al codice decrittato
00402B3A    add  esi, 4   
<------------ esi = esi + 4
00402B3D    dec  [ebp+var_4]   
<------- [ebp+var_4]--
00402B40    jnz  short loc_402B34   
<-- torna su se non ha eseguito il ciclo 4 volte
 
siamo finalmente arrivati al controllo sul serial inserito.
la call viene chiamata per 4 volte e ad ogni iterazione le vengono passati 4 bytes del buffer (i primi 4 nella prima iterazione, i secondi 4 nella seconda e così via...).
adesso possiamo finalmente capire il significato delle prime istruzioni della call decrittata. se vi ricordate avevamo lasciato un punto interrogativo sul significato del contenuto del registro esi; dopo queste istruzioni sappiamo che esi punta ai caratteri del buffer.
i 4 bytes passati alla call vengono presi due da bx e due da cx; dopo la call provvede a modificarli a più non posso utilizzando le istruzioni decrittate in precedenza. dopo un bel pò di operazioni le word contenute in bx e in cx vengono di nuovo memorizzate a partire dal byte puntato da esi producendo così un nuovo buffer. da questa cosa possiamo concludere che il serial deve avere esattamente 16 caratteri.
non credo ci sia bisogno di altre spiegazioni. usciamo dalla call e tiriamo le somme di tutto il crackme:
 
004019DE    call sub_402AE7   <---------------- ancora una call??? questa la saltiamo;))
004019E3    mov  ecx, 4   
<-------------------- ecx = 4
004019E8    lea  edi, [esp+2B0h+var_2A4]   
<--- edi punta al buffer
004019EC    mov  esi, offset aNoRiskNoFun   
<-- esi punta a "No risk, no fun!"
004019F1    xor  eax, eax   
<------------------ eax = 0
004019F3    repe cmpsd   
<--------------------- confronta buffer e "No risk..."
004019F5    jnz  short loc_401A03   
<---------- se sono uguali siamo finalmente registrati!!!
 
breve spiegazione. la call l'ho tralasciata volontariamente, vi basti sapere che contiene una chiamata a GetProcessHeap e a HeapFree giusto per rimettere le cose al posto giusto:))
e il resto? il tutto si riduce ad una compare (byte per byte) tra la frase "No risk, no fun!" e il nostro caro buffer. come sempre se sono uguali siamo registrati altrimenti...non lo siamo:(
prima di passare oltre credo sia opportuno fare un piccolo riassunto di quello che è successo schematizzando il tutto nei seguenti passi:
1° passo: 'call 401A10' crea sequence utilizzando il nome inserito
2° passo: 'call 401AB0' crea seq_clos utilizzando sequence
3° passo: 'call 401A10' crea seq_conc utilizzando la frase "Concentrate on your breathing.With each breath you become more relaxed."
4° passo: 'call 401A10' creca seq_imag utilizzando la frase "Imagine a brilliant white light above you, "
5° passo: utilizzando i bytes di seq_clos decritta le istruzioni relative alla routine di protezione
6° passo: il seriale inserito viene passato (a botta di 4 bytes) alla routine decrittata e il risultato confrontato con la frase "No risk, no fun!"
bene, l'arcano è stato svelato e siamo alla fine!
non ci posso credere:) e infatti non ci credo:PP
...come si suol dire abbiamo fatto 30 e facciamo giustamente 31: per fortuna mia e per (s)fortuna vostra resta ancora una cosa da fare: REVERSARE IL TUTTO!!!
 
KEYGENERATOR
premessa: non vi spiegherò per filo e per segno come costruirlo (anche perchè altrimenti questo tutorial lo finisco a Natale:PP) ma cercherò di darvi delle info in qua e la; troverete comunque il codice sorgente nell'allegato...non sono bravo a codare in asm quindi se trovate degli orrori nel sorgente passateci sopra (oppure avvisatemi;))...
torniamo a noi, da dove partiamo? hmmm...hmmm....hmmm...
pensiamo un attimo alla struttura che avrà il nostro keygenerator; sicuramente avrà una forma molto simile al crackme.
abbiamo infatti una prima parte in cui possiamo tranquillamente rubare intere porzioni di codice dal crackme per costruire sequence, seq_clos, seq_conc e seq_imag.
dopodichè possiamo riciclare l'idea con cui vengono generati i vari gruppi di istruzioni; mi spiego meglio: quello che fa lui è così schematizzato:
 
   j = 0x4F
ZZZ   inizio ciclo esterno di generazione delle istruzioni   ZZZ
   prendi il carattere puntato da seq_clos[j], lavora un pò su j e determina il gruppo k di istruzioni da generare
  - ciclo generazione gruppo k
   c'è una poly ?
   si: salta a dopo
   preparazione e generazione della poly
  dopo:
   generazione della non poly
  - fine ciclo generazione gruppo k
   j-- e torna su se j != 0
ZZZ   fine ciclo esterno di generazione delle istruzioni   ZZZ
 
noi faremo la stessa cosa con l'unica differenza che il j partirà da 0 e arriverà a 0x4F. e perchè? dobbiamo reversare un algoritmo quindi si parte dalla fine e si risale fino all'inizio...
per cui le uniche cose che verranno effettivamente reversate saranno i 6 gruppi di istruzioni, le singole istruzioni poly e le due call (408148 e 408144).
prima di proseguire c'è però da fare una piccola osservazione. nella routine decrittata viene spesso usato il valore puntato da 408138; durante il susseguirsi delle istruzioni all'interno della routine di protezione questo valore cambia tramite la 'call 408144'. visto che dobbiamo "capovolgere" le cose dobbiamo ricordarsi il numero di volte che tale call viene chiamata per poter poi calcolare il valore finale esatto (che sarà quello con cui partiremo). se il nostro ragionamento è giusto alla fine della routine decrittata troveremo che 408138 punta a 0x0BADBABE.
penso sia proprio arrivato il momento di reversare i singoli gruppi di istruzioni.
i soliti accorgimenti:
rev_00408148   è la 'call 408148' reversata
rev_00408144   è la 'call 408144' reversata
00408148         è la 'call 408148' originale
la scritta P0LY indica il fatto che quella è una delle istruzioni poly (il numerino che segue è il relativo tipo!)
 
- esi = 4029C3
    xor bx, word ptr [00408138]
   mov ax, bx
   call dword ptr [rev_00408148]
   mov bx, ax
   xor cx, word ptr [00408138]
   mov ax, cx
   call dword ptr [rev_408148]
   mov cx, ax
- esi = 4029AA
    mov ax, bx
   call dword ptr [00408148]
   add cx, ax
   mov ax, cx
   call dword ptr [00408148]
   xor bx, ax
   
- esi = 402994
    mov ax, bx
   P0LY_03
   xor cx, ax
   mov ax, bx
   P0LY_00
   xor ax, cx
   xchg ax,bx
la P0LY_03 non ha niente a che vedere con il tipo 3 utilizzato (o meglio non utilizzato) da Q; l'ho utilizzato perchè ho bisogno di utilizzare un'istruzione che sia uguale all'originale (e non la sua versione reversata) quindi mi sono scritto un poly in più che altro non è che il poly_0 utilizzato da Q.
 
- esi = 402975
   sub cx, word ptr [00408138]
   P0LY_02
   call dword ptr [rev_408144]
   xor bx, word ptr [00408138]
   P0LY_01
 
- esi = 40295C
   sub cx, word ptr [00408138]
   P0LY_02
   xor bx, word ptr [00408138]
   P0LY_01
 
- esi = 402951
   P0LY_01
   P0LY_02
 
e adesso tocca alle istruzioni poly. non credo proprio abbiano bisogno di spiegazioni:)
- P0LY_00
   xor ax, 1234
   sub ax, 1234
   ror ax, 72
   sub ax, [00408138]
   add ax, [00408138]
   xor ax, [00408138]
- P0LY_01
   xor bx, 1234
   sub bx, 1234
   ror bx, 72
   sub bx, [408138]
   add bx, [408138]
   xor bx, [408138]
- P0LY_02
   xor cx, 1234
   sub cx, 1234
   ror cx, 72
   sub cx, [408138]
   add cx, [408138]
   xor cx, [408138]
- P0LY_03
   xor ax, 1234
   add ax, 1234
   rol ax, 72
   add ax, [00408138]
   sub ax, [00408138]
   xor ax, [00408138]
 
bene, ma non abbiamo ancora finito; un'ultimissima cosa e vi giuro che poi ho finito:P.
c'è da reversare due call, la '408148' e la '408144'.
partiamo dalla prima delle due.
sia seq_conc che seq_imag erano state create utilizzando la 'call 401A10'; se vi ricordate (se non vi ricordate ve lo dico io;) la particolarità di questa call stava nel fatto che modificava le sequenze semplicemente facendo uno shuffle dei suoi bytes. seguendo questa osservazione il reverse della call è presto fatto in quanto un singolo byte appare nella sequenza una ed una sola volta per cui si tratterà semplicemente di trovare in che posizione si trova. la trovate nell'allegato come rev_408148
veniamo alla seconda call.
per questa call (che modifica il valore puntato da 408138) ho usato un approccio di tipo brute force in quanto le istruzioni non mi permettevano di effettuare un ribaltamento immediato (nell'allegato è rev_408144).
 
conclusione:
bene, con questo concluderei qui il tutorial. forse in quest'ultima parte sono stato un pò troppo precipitoso ma penso che se siete riusciti a capire come funziona il tutto non avrete grossi problemi a codare un keygen funzionante; ricordatevi che avete sempre il mio! (brutto quanto vi pare ma funzionante:PPPPPP)
alla prossima...
bona
ZaiRoN   
 

Note finali

 
ok bimbi (e bimbe;), siamo finalmente arrivati alla fine di questo lunghissimo tutorial. ho ricontrollato più volte ma è possibilissimo che mi sia scappato qualche errorino in qua e la; se così fosse vi prego di perdonarmi:))
cosa ne pensate del crackme?
obiettivamente credo proprio sia un bel crackme, divertente e con una buona idea di fondo.
è stato troppo facile, troppo difficile? ho dato una valutazione appena sopra il livello intermedio in quanto (anche se privo di trick e/o giochetti vari) ho durato molta fatica per arrivare alla soluzione finale (hey, sono un povero newbie!); se per voi è stato troppo semplice...vabbè, sarà per la prossima:PPP
veniamo ai ringraziamenti che vanno a:
+Q
per il bel crackme che ci ha proposto
Que
per la uic che spero trovi casa al più presto:)
te
che sei arrivato in fondo
io
che sono arrivato in fondo:PP

Disclaimer

sono stato autorizzato dall'autore!!! è soltanto un crackme da maltrattare a più non posso :PPPPPPPPPPPPPPP