ßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßÛ
²                               [x] RingZ3r0  Proudly Presents [x]                            ²
±                ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ                 ±
                 ³            PC98 Trial CrackMe KeyFile Solution           ³

                                              Kill3xx

                                         28 Dicembre ,1998


--==[   PREMESSA   ]==------------------------------------------------------------------------

LE INFORMAZIONI CHE TROVATE ALL'INTERNO DI QUESTO FILE SONO PER PURO SCOPO DIDATTICO. 
L'AUTORE NON INCORAGGIA CHI VOLESSE UTILIZZARLO PER SCOPI ILLEGALI. 

--==[  DIFFICOLTA' ]==------------------------------------------------------------------------

scala : *=Novizio, **=Apprendista, ***=Esperto, ****=Guru
target: **1/2

--==[  TOOLS USATI ]==------------------------------------------------------------------------

* PROCDUMP 1.2 (Grom/Stone UCF)          
* SoftIce 3.23 (Numega)
* IDA ver 3.80 (Ilflak / DataRescue)
* TASM 5.0     (Imprise)
* HIEW 6.0     

--==[ INTRODUZIONE ]==------------------------------------------------------------------------

Ho deciso che avrei scritto un tutorial sul CrackMe realizzato da The+Q e Plushmm, nonappena 
la Trail Session dei PC fosse finita ovviamente ;), perche' lo considero un ottimo esempio di 
come si possa utilizzare IDA per realizzare in modo piu' semplice e veloce un keymaker.

Il Crackme si compone di 4 prove in ordine di difficolta': 
 1) trovare la classica combinazione user/code
 2) eliminare il nagscreen iniziale
 3) trovare il serial valido 
 4) costruire un keyfile valido che ci consenta di registrare ad ex. il nostro nome

Ma veniamo subito al dunque:

--==[LA PROVA DEL KEYFILE]==------------------------------------------------------------------

Quello che qui ci interessa e' l'ultima prova.. la piu' interessante a mio avviso (delle prime 
tre ne parlero' velocemente alla fine).

Ora so che qualcuno di voi gia' scalpita :) e vorrebbe iniziare a martellare i tasti F8..F12, 
calma calma.. una cosa che consiglio a tutti prima di iniziare una cracking session e' di 
sedersi comodamente e fare qualche considerazione sul target che avete di fronte:

1) partiamo avvantaggiati dal fatto che sappiamo che si tratta di un keyfile quindi possiamo 
  evitare il solito workaround (FileMon/RegMon,ecc.)
2) e' un crackme presumibilmente scritto in asm.. quindi il codice e'quasi esclusivamente
  dedicato alla protezione.. il che ci facilita molto nella fase di identificazione delle 
  routine ..eheh niente overbloated code come in delphi o vb :)
3) se e' scritto in asm come pensiamo e' lecito attenderci anche trucchetti sporchi ;) 
  ad ex. antisIce, codice automodificante,ecc..
4) analizzando la strutura de PE-Header con Procdump (o un qualsiasi PE Browser) ci accorgiamo 
  subito che l'exe e' crittato/packato.. e che il decrypter sta nell'ultima section Q_Plush.

Dopo questa breve ma importante preview del nostro target, possiamo anche passare la parola
al nostro fido sIce. 
Dato che e' un keyfile i breakpoint da provare subito sono quelli sulle API winsuxx per 
l'apertura/lettura di un file: quindi BPX su CreateFileA, ReadFile, GetFileAttributesA, 
GetFileSize..
Con nostra somma gioia sIce non brekka.. ohoh cosa succede mai qui?.. nulla di particolare il 
simpatico The+Q ha usato le obsolete _lopen,_lread,_lclose,ecc. per distrarci un po':)
togliamo tutti i vecchi breaks con "BC *" e mettiamo un "BPX _lopen" e "bpx _lread"..

//Nota per eventuali newbies//---------------------------------------------------------------
dato che volenti o nolenti passerete parecchio tempo con target windogz e' buona cosa che vi 
procuriate l'help del SDK con tutti prototipi della API e vi studiate bene almeno le piu' 
importanti.. e' fondamentale che sappiate cosa viene passato ad un'API quando viene invocata. 
//------------------------------------------------------------------------------------------// 

et voila' sIce brekka in mezzo alla routine di lettura del keyfile:  vediamo che il prog
usa _lread per leggere 0xA4 bytes da un file di nome "crackme.key" e poi lo chiude.. 
se effettivamente non sono stati letti 164 bytes la funct segnala l'errore con eax=-1..
bene, allora noi mettiamo un BPX all'address in cui inizia la funct..togliamo BPX _lopen,ecc...
usciamo da sIce.. ci costruiamo un bel file crackme.key lungo 164 bytes pieno di 0xFF.. 
rientriamo in quella funct mettiamo un BPR Buffer_Address Buffer_Address+164 (o solo BPM se 
usate sIceNT) .. P RET (F12) e diamo un'occhiata a cosa fa il prog:
umm.. legge l'ultima dword.. la manipola (ROL 6).. chiama una funct che costruisce una tabella 
di 8 elementi (byte) e poi entra in un loop con ecx=14h che chiama quella che ad occhio e' la 
nostra funct di decrypt.. 
aiaiii.. anche ad un'occhiata veloce questa funct ci appare subito lunghetta e piuttosto 
articolata.. vabbeh, anche se a malincuore e' il momento di passare da un approccio live del 
sIce ad un piu' meditativo :) deadlisting con IDA.

Ora pero' si pone un problema: come possiamo dissassemblare l'exe se e' crittato?
niente paura.. visto che si decritta in mem quando viene eseguito e che , fortunatamente,
la import table non e' alterata, possiamo farne il dump (usando Icedump/The_Owl,Softdump/Qaine,
ADump/UCF per citarne alcuni).. salvare le sections decrittate e sovrascriverle nell'exe 
originale (ricordate che e' solo un crypter e quindi le sect hanno le stesse dimensioni anche
dopo il mapping in mem), quindi rimuovere la section Q-Plush e riaggiustare il pe_header 
(l'entry point,section table,imagesize,ecc..)..  ok ok sembra difficile,  ma in fondo e' solo 
questione di fare un po' di Cut&Paste :) 
vabbeh, siccome noi siamo assolutamente pigri usiamo ProcDump ;) : 
nelle option e' meglio che utilizziate: 
"recompute obj size"        = OFF 
"use actual import infos"   = ON
selezionate il metodo Standard.. unpack .. premete ok .. salvate l'exe decrittato.. 
usate il pe-editor di ProcDump .. killate (bello 'sto termine :))) la section del decrypter
oramai inutile (se non per studiarla ;).. se avete eseguito correttamenete queste operazioni 
ora potete disassemblare allegramente con IDA :)

Tralasciando il codice di contorno quello che ci interessa inizia da 00402471:


loc_0_402471:                           ; CODE XREF: sub_0_40100C
                call    fnReadKeyFile   ; apre/legge in mem il KeyFile - 00402551
                cmp     eax, 0FFFFFFFFh
                jz      loc_0_402530
                mov     cl, 6
                mov     eax, ds:CRC     ; usa l'ultima dword del keyfile = CRC
                rol     eax, cl         ; rol 6
                mov     ds:Seed, eax    ; come seed iniziale
                
                call    fnBuildMagicTab ; prepara la tabella Magic 0..8

 
questo e' quello che avevamo gia' visto in sIce.. ora noterete i nomi che ho assegnato
in IDA.. questo merita un discorso a parte..

"Nomina Sunt Consequentia Rerum" , diceva S. Agostino; ma possiamo anche invertire 
questo assunto e considerare gli oggetti che ci circondano in relazione al significato
che noi stessi gli diamo (no no non ho fumato cose strane ;)

imparare a commentare con IDA e' fondamentale.. e' quello che puo' fare la differenza..
iniziando da quel poco che capite del prog assegnate i nomi alle locazioni,alle proc, ai loop,
alle variabile nello stack,ecc. vedrete che man mano che aggiungente nomi/commenti anche lo 
schema piu' ingarbugliato si dipanera'.

//Esempio pratico//---------------------------------------------------------------------------

00402481                 mov     eax, ds:40245Dh

premete il tasto CTRL + R quindi premete CRTL + R.. scegliete full 32 offset.. ora avrete 

00402481                 mov     eax, ds:dword_0_40245D

cliccate su dword_0_40245D per andare a quell'offset.. premete tasto "N" ed assegnate il 
nome CRC.. ora tornate a 402481 (premete ESC) ed avrete : 

mov     eax, ds:CRC 

da adesso in poi quando farete CRTL+R su ds:40245Dh IDA scrivera' da solo CRC.. ed 
aggiungera' una xref per quell'address. Quanto alla call, basta che cliccate su CODE XREF: 
sub_0_40100C.. premete "N".. assegnate il nome e d'ora in poi vedrete sempre call 
fnReadKeyFile invece di una piu' oscura call sub_0_402551. Ancora: per aggiungere commenti 
usate il tasto ":" per i commenti singoli, e ";" per quelli ripetibili (provate ad esempio
ad aggiungerne uno  all'inizio del codice di fnReadKeyFile scrivendo qualcosa 
tipo "read keyfile , return eax=0 -> ok" 

questi sono solo alcuni comandi che IDA vi mette a disposione per commentare il codice.. 
potete creare strutture, array, rinominare i parametri/variabili creati con un
stackframe esp+ebp,ecc.. IDA e' davvero interattivo, gli potete far fare quello che volete.. 
e' questa la differenza con wdasm : un dissamblatore per quanto intelligente non puo' capire
quello che invece potete capire voi usando il vostro cervello :))

Ok fine dell'angolino dell pubblicita' occulta ..ehm comprate IDA se avete i $ ofcoz!;) 
//------------------------------------------------------------------------------------------//

Tornando al disasm quello che ci interessa ora e' la  call fnBuildMagicTab: 

fnBuildMagicTab proc near               ; CODE XREF: DATA:0040248D
                mov     edi, offset Magic1  ; offset MagicTable 
                mov     al, 55h             ; seed iniziale "U"
                mov     ecx, 8              
loc_0_4025D8:                           ; CODE XREF: fnBuildMagicTab+12
                inc     al
                xor     eax, 12h            ; costruisce la Table con 8 Magics
                stosb
                loop    loc_0_4025D8
                retn
fnBuildMagicTab endp

qui non c'e' molto da dire tranne che la tabella e' sempre la stessa a prescindere dal
keyfile utilizzato.. good!

                mov     ecx, 14h        ; decrypt loop = 8 byte x 20 = 160 bytes
                mov     esi, offset KeyFileBuffer

loc_0_40249C:                           ; CODE XREF: DATA:004024A4
                call    fnDecrypt       ; decritta 2 dowrd x loop
                add     esi, 8          ; bf_index = bf_index + 8
                loop    loc_0_40249C

anche qui va da se'.. ora guardiamoci fnDecrypt..

               mov     edi, [esi]      ; input : dword1 = buffer(bf_index)
               mov     ebx, [esi+4]    ;         dword2 = buffer(bf_index+4)
               push    esi             
               ; <<--- pattern 
               mov     cl, ds:Magic1   
               mov     eax, edi
               rol     eax, cl
               call    fnAdjustXorMask ; X = funct(dword1 ROL MagicX)
               xor     ebx, eax        ; dword2 = dword2 xor X
               mov     cl, ds:Magic2
               mov     eax, ebx
               ror     eax, cl
               call    fnAdjustXorMask ; Y = funct(dword2 ROR MagicY)
               xor     edi, eax        ; dword1 = dword1 xor Y
               ;  pattern --->>
               mov     cl, ds:Magic3
               mov     eax, edi
               rol     eax, cl
               call    fnAdjustXorMask ; come sopra ma con MagicX = Magic3
               xor     ebx, eax
               mov     cl, ds:Magic4
               mov     eax, ebx
               ror     eax, cl
               call    fnAdjustXorMask ; come sopra ma con MagicY = Magic4
               xor     edi, eax
               .... continua stesso pattern x 10...
               pop     esi
               mov     [esi], edi      ; output : buffer(bf_index)   = dword1
               mov     [esi+4], ebx    ;          buffer(bf_index+4) = dword2

quello che salta subito all'occhio e' il pattern che si ripete con la sola variante 
dei Magic utilizzati.. in pratica vengono decrittate 2 dword per volta, con una 
sequenza di xor in cui la xor_mask per dword1 e' resa in funzione di dword2 ad ogni step.
I ROL , ROR non ci importano piu' di tanto visto che sono invarianti rispetto a dword1 e 
dword2.. qualche grattacapo in piu' ce lo puo' dare fnAdjustXorMask:

fnAdjustXorMask proc near               ; CODE XREF: fnDecrypt+14
                push    ecx
                mov     ecx, ds:Seed
                shr     ecx, 18h
                ror     eax, cl
                xor     eax, ds:Seed    ; mask = (mask ror (seed shr 18)) xor seed
                call    fnAdjustSeed    ; adjust seed
                pop     ecx
                retn
fnAdjustXorMask endp

fnAdjustSeed    proc near               ; CODE XREF: fnAdjustXorMask+12
                push    eax
                mov     eax, 51656854h
                cdq
                imul    ds:Seed
                add     eax, 6D6D482Bh
                sub     ds:Seed, eax    ; seed = seed - (High(seed * const1) + const2)
                pop     eax
                retn
fnAdjustSeed    endp

qui come si vede la mask e' aggiustata ad ogni step in base al valore di Seed 
che a sua volta e' modificato ad ogni step.. ebbravi The+Q & Plushmm :))
A questo punto l'algoritmo usato e' chiaro, anche se qualcuno potrebbe pensare 
che non sia facile da reversare.. naaa, non per noi ;) 
quello che dobbiamo fare ora per ottenere un crypter e quindi il nostro keymaker e'
invertire ogni step..
avendo le dword finali (il testo da crittare) quello che vogliamo e' ripercorrere a 
ritroso la catena..
quindi come primo passo invertite tutti "pattern" partendo dall'ultimo verso il primo,
in questo modo i Magic saranno usati in ordine esattamente inverso e con i ROL,ROR
siamo a posto.. ora dobbiamo invertire la relazione che lega dword1 e dword2:

_pattern proc
 X = funct(dword1 ROL MagicX)
 dwordB = dword2 xor X
 Y = funct(dwordB ROR MagicY)
 dwordA = dword1 xor Y
_pattern endp 

 la cui inversa e':
 
_inv_pattern proc
 dword1 = dwordA xor funct(dwordB ROR MagicY) 
 dword2 = dwordB xor funct(dword1 ROL MagicX)
_inv_pattern endp  
 
bene.. fin qui ci siamo direte voi.. ma come la mettiamo con funct : 
dobbiamo invertire anche quella? e poi come facciamo con fnAdjustXorMask che usa un seed 
diverso ad ogni step! 
azz quante domande! :)

Se guardiamo attentamente il dissasemblato quello che si nota e' che Seed viene inizializzato
prima del loop usando  CRC ROL 6 e quindi si evolve usando fnAdjustSeed in modo indipendente 
da dword1 e dword2, ma in relazione agli step percorsi.. 
ok e' fatta :) , ci basta codare una funct che ad ogni ciclo del loop esterno del crypter
costruisca una tabella con tutti i valori di Seed per quella iterazione (12 pattern = 
24 entry nella tabella) e la usiamo al posto di fnAdjustSeed..  
Per ottenere il crc basta che applichiate la stessa funzione usata dai programmatori sul 
buffer (159byte+null) che volete criptare e quindi che salviate il valore come ultima 
dword del buffer stesso.. et voila' avete il vostro keyfile!
Se mi avete seguito (io mi sono perso a dir il vero :) ora siete in grado di scrivere il 
keymaker .. potete usare  qualsiasi linguaggio (c/c++/asm/pascal).. io vi consiglio di 
scriverlo in asm win32 perche' cosi' potete usare un'altra comodissima funzione di IDA 
(te pareva ;)):
 
se avrete commentato bene il listato in IDA potrete esportare i disasm delle varie funzioni 
(basta evidenziare il blocco e selezionare Menu File->Produce output file->Produce ASM file)
in un file .asm che e' direttamente utilizzabile in TASM una volta aggiunte le varabili 
utilizzate al segmento dati.. in tutto 3min di lavoro :) un po' di cut&paste per invertire 
l'algo di fnDecript e fnAdjustXorMask nel modo in cui abbiamo visto detto prima.. altri 4min.. 
un po' di coding per aggiungere la procedura per la costruzione della Seed_Table 3/4min... 
shekerate con cura ed in 10/12min avrete fatto il keymaker.. DO YOU FEEL IDA'S POWER ?! :)
Per quelli pigri come me, vi aggiungo i src del mio keymaker.. mi raccomando non prendetelo
come esempio di coding in w32asm! ;)

--==[LE ALTRE TRE PROVE]==--------------------------------------------------------------------

Ok.. la prova del keyfile e' archiviata.. adesso come promesso parlero' brevemente
(eheh non vorrete che vi dica tutto io.. vi perdereste il gusto di provare :) ) delle
restanti tre prove: user code, nag screen e serial number: 

a) lo user/code e' la parte piu' semplice: alla calculation ci arrivate con il classico
   BPX GetDlgItemTextA.. qui il vostro nome e' manipolato in un loop che usa 
   ROR 8 + ADD (402111), mentre il code usa ROL 8 + ADD (4020F9).. quindi segue il check: 
   Result_Code = Result_Name ROR 8 (40211E)
   non mi dilungo oltre su questa protezione perche' la soluzione e' davvero semplice.. 

b) anche il serial e' tutto sommato semplice tranne per il fatto che vengono utilizzate
istruzioni FPU (vi consiglio il cap. 14 della ArtOfAssembly completamente dedicato alla FPU) 
ad ogni buon conto il break da sIce e' il solito BPX GetdlgItemTextA; quanto alla calculation 
questa passa attraverso un ciclo che controlla la formattazione del serial : xxxxxxxx-yyyyyyyy 
con x e y = numeri ed una funct di conversione ascii_to_long (402D17)..
le condizioni infine sono quattro basate sulla stessa equazione:

  2^[trunc(c*X)]*2^[(c*X)-trunc(c*X)] mod Y = j  
  
  con c = 1*log2(k) ; 

  e rispettivamente k=[13,18,7,12] ; j=[2,7,18,23]

l'unica nota importante qui e' l'implementazione di un semplice check antisIce ,CreateFileA 
per detectare il vxd di sIce w9x/NT (402D93), che se presente porta a falsare i calcoli 
utilizzando un k fasullo nell'ultimo check.

c) Per quel che riguarda la prova del nag remover.. beh .. le regole impongono di non patchare 
l'esegibile dumpato.. ora le possibili soluzioni sono piu' di una ma mi limitero' ad elencarne 
le tre piu' comuni:

1) apihook : la import table non e' alterata quindi l'hook e' possibile.. ma non e' molto 
   pratico come metodo perche' impone la presenza di un loader.. passiamo oltre :)
2) patching from inside (C) xAONON (alla vbox per intenderci). 
   questa soluzione e' abbastanza semplice da realizzare soprattutto considerando che non 
   abbiamo bisogno di utilizzare WriteProcessMemory in quanto: a) siamo nello stesso spazio 
   di indirizzamento b) la section code e' gia' writable (lo deve essere visto che il crypter
   la modifica senza utilizzare WriteProcessMemory).
   Se steppate nel crypter noterete che termina con il classico jmp entrypoint (=eax).. 
   ora una piccola correzione a questo jmp e possiamo trasformarlo in jmp patch_thunk:
   passiamo  cioe' passare il controllo ad thunk ricavato alla fine della section Q_Plush 
   (dove c'e' il logo The+Q&Plushmm andra' benissimo :) che fungera' da mem patcher:

   xor edx,edx   
   mov [eax+nag_box_ra],edx       
   ripristinate i reg e lo stack come nell'originale
   jmp eax

   con  eax = entrypoint (gentilmente calcolatoci dal programma) e nag_box_ra = l'offset 
   rispetto all'entrypoint della "Call NagBox" .. cosi' da traformarla in E800000000
3) reverse dell'algoritmo di encryption: si tratta cioe' di calcolate la sequenza di byte 
   corretta da sostituire nell'exe per ottenere che la call venga decrittata come E800000000
   (qui conviene che utilizziate un BPM address_Call_Nagbox per semplificarvi il compito).
   L'encryption non e' molto complessa.. anzi a dir il vero e' un semplice xor con una mask 
   calcolata ad ogni step :

   loc_40604D:                             ; CODE XREF: Q_Plush:00406070
                 push    ecx
                 lodsd
                 xor     eax, [ebp+402848h]
                 mov     ecx, [ebp+402848h]
                 shr     ecx, 18h
                 rol     eax, cl
  
  attenzione pero' che la section successiva (.DATA) e' stata crittata con un seed iniziale
  calcolato sulla base del crc dei dati in chiaro della precedente section (.CODE): 
  anche questo problema e' aggirabile senza troppa fatica: basta che vi segnate il valore del 
  seed corretto e lo hardcodate (miii che brutto termine :)) nel codice al posto di: 
  004060B2                 mov     word ptr [ebp+402848h], 0FFFFh
  004060BB                 mov     word ptr [ebp+40284Ah], 0FFFFh
  e saltate la routine di calcolo andando direttamente all'adress 00406135.

--==[CONCLUSIONI]==--------------------------------------------------------------------------

Questo e' tutto.. adesso e' giunto il momento di tirare le somme: 
quello che mi premeva dimostrarvi in questo tutorial e' che IDA e' uno strumento davvero 
eccezionale, specie con target piuttosto elaborati come il keyfile di questo CrackMe, e 
soprattutto di come possa essere usato per facilitarvi il coding di un keymaker/keygen. 
Non so se sono stato chiaro.. ma almeno ci ho provato :)

SORGENTI