Soluzione al 5° Corso di Quequero
Tecniche varie


26/12/99

by "AndreaGeddon "

 

 

UIC's Home Page

Published by Quequero



Allora, il tutorial è fatto abbastanza bene ma manca il keygenerator, alcune routine non sono neanche spiegate e non hai capito bene a cosa servissero quelle referenze a autoexec.bat e system.ini, tranne questi errori nel complesso va bene

 
UIC's form
Home page: presto sarà disponibile... 
E-mail: AndreaGeddon@hotmail.com
UIC's form

Difficoltà

( )NewBies (x)Intermedio ( )Avanzato ( )Master

 

Qui di seguito descriverò come risolvere il crackme del 5° corso di Quequero. Vi consiglio di provare a risolverlo da soli prima, e poi di leggere questa soluzione solo se proprio non capite cosa dovete fare. Questo crackme contiene varie tecniche, cioè Anti Debug, serial calcolati mischiando valori random,  un keyfile creato anche questo sfruttando valori random, e, dulcis in fundo, capire perchè non appare un messagebox che ti dice che l'hai crakkato.


Soluzione al 5° Corso di Queqeuro
Tecniche varie
Written by AndreaGeddon

Introduzione

Questo crackme l'ho classificato come Intermedio, perchè effettivamente non è poi così difficile, però io crakkandolo ho imparato molto. Vi consiglio di studiarlo con attenzione, così capirete quanto può essere bastardo Quequero che l'ha fatto! A parte gli scherzi, è davvero un ottimo esercizio. Adesso iniziamo.

Tools usati

-  SoftIce

-  WinDASM

-  IceDump o ADump (ma non è necessario)

-  Un hex editor qualunque

Il libro Necronomicon per mandare le maledizioni più tremende ed orribili a Quequero il coder (assolutamente necessario)

URL o FTP del programma

http://linox.tecnoprogress.it/quequero/prj/corsocinque.html

Lo trovate nella sezione Lezioni del sito di Quequero

Notizie sul programma 

Questo crackme consiste in 4 punti:

1) Trovare il serial fisso

2) Trovare il Serial generato dal Nome immesso

3) Trovare il keyfile

4) Capire come far apparire il message box che dice che labbiamo crakkato

Ovviamente non possiamo limitarci a invertire i jump, ma dobbiamo trovare tutti i vari serial ed il keyfile. Il patching è permesso solo per eliminare i controlli antidebugger.

Essay

Okkkey, si inizia. Vogliamo fregare il crackme di Quequero, quindi per prima cosa carico il SoftIce, eseguo il crackme già pensando a quali breakpoints usare e... tadaa!! Appare un messagebox con su scritto: "Chi usa SoftIce alzi la mano!!!". Merda, protezione anti debugger. Cheppalle. Okay, visto che per ora il softice ce lo possiamo sbattere, carichiamo il WinDASM (seeee, lo so che è meglio l'IDA, ma non ce l'ho) ed iniziamo a cercare di capire come eliminare il controllo anti debugger.

PUNTO INTRODUTTIVO: ELIMINARE IL CONTROLLO ANTI DEBUGGER

Molte delle tecniche di controllo della memoria per sgamare il debuggere si basano sugli interrupt. Inizio a cercare il testo "INT", e lo trovo tre volte: una volta trovo un INT 03, e le altre due volte trovo INT 68. Tana! Proprio gli interrupt usati spesso per gli antidebugger. Ecco i punti del codice in cui troviamo gli INT:

 

0040105D      mov dword ptr [00402024], eax

00401062      mov ah, 43

00401064      int 68               ecco il primo

00401066      cmp ax, F386

0040106A      je 00401B3E    salta al messagebox di errore se trova il softice

 

004010D6      mov dword ptr [00402024], eax

004010DB      mov ah, 43

004010DD      int 68          secondo controllo

004010DF      cmp ax, F386

004010E3      je 00401406    salta al messagebox di errore se trova il softice

 

00401536      mov ebp, 4243484B

0040153B      mov ax, 0004

0040153F      int 03         ecco il terzo controllo

00401540      cmp al, 04

00401542      jne 004011EE   salta al messagebox se c'è il softice

 

quindi fino a qui tutto chiaro, giusto? Dopo gli interrupt c'è sempre un jump che se lo eseguite vi manda al messagebox che dice che vi ha sgamato il softice. Siccome la messagebox ve la siete beccata, vuol dire che questi salti vengono eseguiti, quindi li possiamo semplicemente noppare, e tutto funzionerà. Dopo aver noppato tutti e tre i salti potete caricare il crackme con il softice caricato, e lui non vi sirà nulla! Okkey, adesso possiamo cominciare il vero lavoro.

ATTENZIONE: essendo il crackme un programma piccolo che non contiene altro che le routine di calcolo, io qui sono andato subito a cercare nel WinDASM le stringhe INT per trovare dove erano gli interrupt. E' ovvio che in un normale programma tutto ciò non è possibile: trovereste tantissimi interrupt, e non potete starveli a vedere uno ad uno! Il metodo che ho usato io mi ha fatto risparmiare tempo, ma tecnicamente è poco corretto. Normalmente avresto dovuto trovare una reference al dialog box (o alla stringa che contiene) di errore, e poi risalire a ritroso nel codice, vedere da dove viene chiamata questa parte di codice e continuare a risalire fino a trovare l' INT che si vuole.

 

PUNTO PRIMO: TROVARE IL SERIAL FISSO

Adesso che il programma parte anche con il SoftIce caricato, possiamo iniziare con i serial. Il primo punto di questo crackme consiste nell'immettere un codice nell'edit box superiore. Notiamo infatti che inserendo un codice qualsiasi e premendo "register", il codice viene cancellato. Adesso se riproviamo ad immetere il codice e premere register, questo non viene più cancellato. Cosa fa? La prima volta (ed intendo SOLO la prima volta) prende il codice, e se è giusto, salva un valore in memoria, se non è giusto ne salva un altro. Poi continua ed adesso i due edit box vanno considerati contemporaneamente, cioè il solito schema Nick/Serial.

Ma per adesso ci fermiamo al primo codice, perchè è quello che vogliamo trovare per primo. Allora, iniziamo subito mettendo un bpx getwindowtexta. Inseriamo il codice (ricordate che deve essere la prima volta che premete register dall'avvio del programma) e andiamo su register. Una volta apparso il softice, torniamo al prog con F12, ed iniziamo l'esame. Il codice sarà questo:

 

* Reference To: USER32.GetWindowTextA

 

00401247      Call 00401EC7                arrivate qui

0040124C      call 0040159C                call ad una serie di calcoli tramite i file autoexec.bat e system.ini

                                                                                    possiamo anche tralasciarli

00401251      xor ecx, ecx                 azzera ecx che verrà usato come contatore nel seguente ciclo

00401253      xor dword ptr [ecx+00403273], 000000ED   |

0040125D      inc ecx                                  |__Questo ciclo fa lo xor con il valore ED di ogni carattere inserito.

0040125E      cmp ecx, 0000000C                        |    Come si può vedere dal cmp ecx,0C il codice viene elaborato

00401261      jne 00401253                             |    solo per i primi 12 caratteri

00401263      xor ecx, ecx                 riazzera ecx che sarà di nuovo usato come contatore per il prossimo ciclo

00401265      xor dword ptr [ecx+00403273], 134F7432  

0040126F      xor dword ptr [ecx+00403273], 4A710930   |        Questo ciclo fa lo xor con i valori del codice immesso (già xorati con ED) a 4

00401279      xor dword ptr [ecx+00403273], 58D71DE6   | ___ a 4, con i valori 124F7432, 4A710930 e 58D71DE6.

00401283      add ecx, 00000004                         |        Alla fine troverete al posto del codice che avevate immesso, una serie di 12 byte

00401286      cmp ecx, 0000000C                        |        che è una stringa lavorata.

00401289      jne 00401265                                           |

0040128B      Call 00401E61                chiama l'API GetTickCount

00401290      xor dword ptr [00403273], eax        in eax viene restituito il valore del'API appena chiamata. Ques'API restituisce il valore in

00401296      xor dword ptr [00403277], eax        millisecondi del tempo trascorso dall'avvio del windows. Praticamente è un valore casuale

0040129C      xor dword ptr [0040327B], eax        in quanto varia di volta in volta e non è possibile prevederlo, quindi tutti i calcoli che seguono

004012A2      xor dword ptr [0040327F], eax        dopo quell'api non li considereremo, in quanto non possono esserci utili.

004012A8      xor dword ptr [00403283], eax

004012AE      xor dword ptr [00403287], eax

004012B4      and dword ptr [0040327F], 00627893

004012BE      and dword ptr [00403273], 00627893

004012C8      mov esi, 00403273                    Muove in esi la stringa che abbiamo inserito noi e che è stata lavorata

004012CD      mov edi, 0040327F                    Muove in edi la stringa giusta anch'essa lavorata

004012D2      mov ecx, 0000000C                    muove in ecx il numero di volte per cui deve essere effettuato il cmp successivo

004012D7      repz                                 |  fa il compare byte per byte delle due stringhe

004012D8      cmpsb                                |  per 12 caratteri

004012D9      je 00401300                          salta se sono uguali

 

Questa è la routine in dettaglio. Essendoci in esi ed edi alla fine le due stinghe per il compare, non possaimo semplicemente visualizzare l'indirizzo 0040327F e vedere qual'è la stinga contenuta (che deve essere quella giusta)? NO! Se fosse stato così non avrei mandato le maledizioni a quequero. Vedete la chiamata a gettickcount? Da lì il programma prende un valore casuale, e lo usa per calcolare la stinga nostra (primi tre xor successivi) e la stinga giusta (secondi te xor). Quindi in esi ed edi troveremo sempre valori diversi ogni volta che andremo a debuggare. La nosta analisi si deve fermare quindi alla call a gettickcount.

Prima di questa call, tutto ciò che avviene è sempre uguale perchè si lavora con valori fissi. Spieghiamo quindi come funziona il tutto. Il prog ha in memoria la stinga giusta già lavorata parzialmente (all'indirizzo 0040327F) all'inizio della call gettickcount. Quindi il prog prende la stinga che inseriamo noi, inizia facendo uno xor di ogni carattere con il valore fisso ED, poi fa lo xor dei primi 4 caratteri con 134F7432, dei secondi 4 caratteri con 4A710930, e dei terzi 4 caratteri con 58D71DE6. Finiti questi calcoli si tova all'inizio della call gettickcount con la nosta stinga parzialmente lavorata, e la stringa giusta parzialmente lavorata. Poi prosegue con calcoli con valori random, e da qui in poi abbiamo detto che non ci frega. Come trovare quindi la stinga giusta? Semplice. Ho detto che all'inizio della call gettickcount abbiamo il valore parzialmente lavorato della stinga giusta (valore che si trova già in memoria). Per calcolare il codice giusto, prendiamo la stinga giusta parzialmente lavorata dall'indirizzo 0040327F (mi raccomando ancora: prendetela PRIMA di gettickcount) e la calcoliamo all'indietro, visto che l'algoritmo ce l'abbiamo.

Ecco come svolgere i calcoli:

la stinga puntata da 0040327F è:     4AFF30824BAB569E38BE20EC

Iniziamo prendendo i primi 4 byte, cioè 4AFF3082. La rigiriamo ed otteniamo 82 30 FF 4A (conoscete ovviamente il LIFO dello stack, vero?).

Iniziamo facendo lo xor con tutti e te i valori sopra detti. Saprete infatti che se A xor B = C, allora C xor B = A. E' quello che faremo noi.

Per fare i conti potete usare la calcolatice del windows che ha lo xor e la settate in modalità esadecimale.

    xor col 3° valore:                    xor col 2° valore:              xor col 1° valore:

       82 30 FF 4A   xor            DA E7 E2 AC  xor           90 96 EB 9C    xor

       58 D7 1D E6   =                4A 71   09 30   =               13 4F 74   32   =

-------------------------         ----------------------         ---------------------

       DA E7 E2 AC                   90 96 EB 9C                     83 D9 9F AE

siccome prima stavamo parlando di word, abbiamo dovuto rigirare il valore da xorare. Siccome adesso andremo a fare un xor che va byte per byte, dobbiamo rigirare di nuovo l'ultimo valore ottenuto, cioè: 83 D9 9F AE, che diventa AE 9F D9 83. Se non capite perchè si devono rigirare, andate a leggere i tutorial sul funzionamento dello stack e della stuttura LIFO. E a questo punto con la stinga ririgirata, vado a fare l'ultimo calcolo, cioè lo xor con ED:

AE 9F  D9 83   xor

ED ED ED ED   =

---------------------

43  72  34  6E    ---> risultato

C     r     4    n    ---> corrispondente ASCII del risultato

 

E così abbiamo trovato i primi 4 caratteri del codice giusto. Fate lo stesso procedimento anche con i secondi 4 caratteri e i terzi 4 caratteri della stinga giusta parzialmente calcolata, ed alla fine otterrete i caratteri:

Cr4nB&Rr13$    

Si capisce subito che è ciò che vogliamo. Infatti questa stringa non è altro che un modo diverso di scrivere Cranberries (ecco svelati tutti i misteri sulla musica irlandese scoppiati in ML). Sono in realtà 11 caratteri, perchè il 12° valore che avete ottenuto è 00.

Potete ulteriormente vedere che il valore è giusto, perchè quando arivate al cmp, se avete inserito il codice tovato, questo dà esito positivo, accende il flag Z e quindi vuol dire che le stinghe comparate sono uguali, e quindi la nosta stinga è giusta!

Ecco risolto facilmente il primo punto. Passiamo al secondo.

 

PUNTO SECONDO: TROVARE IL NAME / SERIAL

Qui la situazione è simile. Ci sono sempre i valori random, ma stavolta invece che un codice fisso, il codice giusto verrà calcolato in base al nome che noi metteremo. Innanzitutto chiariamo che l'edit box superiore è quello del nome, mente l'edit box inferiore è quello dove dovremmo inserire il giusto codice che adesso ancora non conosciamo. Iniziamo inserendo dei valori a caso. Io ho inserito al nome 12123434 e al codice un valore qualsiasi. L' importante è per adesso il nome.

(ricordate di riavviare il crackme ogni volta che debuggate)

 

* Reference to GetWindowTextA

 

00401330    Call 00401EC7             call GetWindowTextA (in 00403249 c'è il name 12123434 inserito)

00401335    mov dword ptr [00403241], eax   mette in 00403241 la lunghezza del nostro name (12123434): 8 caratteri

0040133A    cmp eax, 00000006                se la lunghezza del name è meno di 6

0040133D    jl 0040141E                      salta al messagebox di errore

00401343    Call 00401E61                    call GetTickCount per prendere un valore random

00401348    mov dword ptr [0040323D], eax    muove il valore random in 0040323D

0040134D    xor ecx, ecx                      azzera ecx (il contatore)

0040134F    xor ebx, ebx                      azzera ebx

00401351    mov edx, dword ptr [0040324A]    muove in edx dal secondo al 5° carattere del name | ricordate che saranno invertiti

00401357    mov eax, dword ptr [00403249]    muove in eax i primi 4 caratteri del name                 | per la struttura lifo

0040135C    imul eax, edx                     li moltiplica e mette in eax il risultato

0040135F    mov dword ptr [00403249], eax    muove eax al posto dei primi 4 caratteri del name

00401364    xor ecx, ecx                      riazzera il contatore ecx

00401366    add ecx, 00000004                 incrementa ecx di 4

00401369    dec dword ptr [00403241]         decrementa il numero di caratteri del name

0040136F    inc ecx                           incrementa il contatore ecx

00401370    mov al, byte ptr [ecx+00403249]  muove in al il byte puntato da ecx+00403249

00401376    add al, byte ptr [ecx+0040324A]  gli aggiunge il byte successivo

0040137C    mov byte ptr [ecx+00403249], al  il risultato lo mette al posto del byte puntato da ecx+00403249

00401382    cmp ecx, dword ptr [00403241]    confronta ecx con la lunghezza dei caratteri (che è stata decrementata)

00401388    jl 0040136F                       salta se è minore

0040138A    mov eax, dword ptr [00403249]    muove in eax i primi 4 caratteri del name

0040138F    mov ebx, dword ptr [0040324D]    muove in eax i secondi 4 caratteri del name

00401395    imul eax, ebx                     li moltiplica

00401398    mov dword ptr [00403249], eax    sostituisce il risultato ai primi 4 caratteri del name

0040139D    inc dword ptr [00403241]         incrementa il numero di caratteri

004013A3    xor ecx, ecx                      azzera il contatore ecx

004013A5    mov ecx, dword ptr [00403241]    muove in ecx il numero di caratteri e lo userà come contatore

004013AB    mov eax, dword ptr [ecx+00403249] muove in eax i 4 caratteri puntati da ecx+00403249

004013B1    xor eax, 04CF580F                 xora questi caratteri col valore 04CF580F

004013B6    mov dword ptr [ecx+00403249], eax sostituisce il risultato dello xoring ai 4 caratteri presi

004013BC    dec ecx                           decrementa il contatore

004013BD    test ecx, ecx                     lo controlla

004013BF    jne 004013AB                      salta se non è zero

004013C1    call 00401438             call alla routine che converte in numeri il serial ottenuto finora

004013C6    xor eax, eax               ora se visualizziamo 00403249 ci troveremo il serial che ci serve

004013C8    call 004013EF             call ad una routine che prende il valore di GetTickCount

004013CD    call 004014AE             call ad una routine che cripta il codice giusto calcolato

004013D2    push 00000014             salva il valore 14

004013D4    push 0040325D             salva la locazione dove verrà messo il serial che abbiamo inserito

004013D9    push dword ptr [00402034] salva il valore contenuto in 00402034

004013DF    Call 00401EC7                     call GetWindowTextA

004013E4    call 0040157A             call ad una rotuine che cripta il nostro codice inserito

004013E9    call 004015EF             call alla routine di compare (VEDI SOTTO)

004013EE    ret

 

ROUTINE DI COMPARE:

 

004015EF    push dword ptr [00402034]

004015F5    Call 00401EB5      call GetWindowTextLengthA

004015FA    cmp eax, 0000000F          confronta la lunghezza del serial inserito con 15

004015FD    jne 00401108                salta ad errore se non è uguale

00401603    mov esi, 0040324A          muove in esi il puntatore alla stringa ottenuta dal name che abbiamo inserito

00401608    mov edi, 0040325D          muove in edi il puntatore alla stringa ottenuta dal serial che abbiamo inserito

0040160D    mov ecx, dword ptr [00403241]  muove in ecx il numero di byte da confrontare

00401613    repz                        |_ Confronto delle

00401614    cmpsb                       |   due stringhe

00401615    je 004014F4                 se sono uguali salta alla parte giusta del programma

 

E questo è tutto quello che vi serve per trovare il giusto serial. Ho commentato tutti i calcoli che vengono eseguiti nella routine principale. Non mi sono addentrato in una descrizione dettagliata di tutte le call per due motivi: primo perchè sarebbe troppo lungo, secondo perchè le routine non sono difficili da decifrare, quindi lascio a voi il divertimento di esaminarle (tralaltro non sono necessarie per noi). Allora, dopo un bel pò di calcoli si arriva alla linea di codice 004013C1, che è una call ad una routine che converte in un numero il serial ottenuto da tutti i calcoli fatti sul name. Tornando dalla call, se andiamo a vedere cosa c'è all'indirizzo 00403249 (che era l'indirizzo dove all'inizio era stato messo il name da noi inserito), ci troveremo un bel numeretto, cioè 20132144 (questo se avete messo come name 12123434).

Possiamo pensare che questo sia il codice giusto. In realtà lo è ma solo a metà. Se si continua ad esaminare il prog, troviamo che questo numero trovato viene criptato in un'altra stringa, poi il prog prosegue e cripta il serial da noi inserito, ed infine và alla routine di compare che confronta le due stringhe criptate. Ed è proprio a questa routine che dobbiamo adesso spostare l'attenzione. Se la andiamo a vedere, si nota subito all'inizio una call a GetWindowTextLengthA. Uhhmmm.... E la riga dopo c'è un compare di eax con 0F. Siccome in eax c'è il valore restituito dalla call, cioè la lunghezza del serial inserito, vediamo che poi il prog richiede una lunghezza di 15 caratteri, altrimenti salta alla parte sbagliata di codice (dove inizia un loop infinito). Siccome non era richiesto che venisse inserito un codice di 15 caratteri, inizio a pensare che ci sia un tranello. Infatti il compare successivo si basa sul numero di caratteri effettivamente immessi (alla riga 0040160D), e non sul valore fisso 15. Allora provo a fare in modo da inserire il codice trovato (che era 20132144) più altri 7 caratteri, per raggiungere il valore 15. Se infatti metto questi due valori:

Name:  12123434

Serial:   20132144aaaaaaa

il prog prosegue fino al compare, ed il compare mi dà risultato positivo, e non va alla parte sbagliata del programma. Ecco quindi risolto anche il secondo punto.

Proseguendo ora c'è il punto del keyfile, che forse è quello unpò più lungo.

 

PUNTO TERZO: TROVARE IL KEYFILE

E qui cominciano i cazzi!! Devo dire, però che non è poi tanto difficile questo terzo punto, è che io avendo scritto un byte sbagliato nel keyfile mi ci sono impuntato per giorni, perchè pensavo che stessi sbagliando il ragionamento, invece il keyfile era giusto, aveva solo un byte invertito perchè l'avevo scritto male. Comunque, cominciamo. Proseguendo dopo il precedente punto, arriviamo qui tramite il jump subito successivo al repz cmpsb precedente.

(ricordate di riavviare il crackme ogni volta che debuggate)

 

004014F4    mov dword ptr [0040329F], 000091B2   muove il valore 91B2 in 0040329F. Per adesso non ha importanza                                                                                                     ma poi vedremo che è fondamentale

004014FE    push 00000014

00401500    push eax

00401501    push dword ptr [00402030]

00401507    Call 00401EC7           call GetWindowTextA

0040150C    mov dword ptr [00403241], eax

00401511    mov eax, 00402BFC

00401516    add eax, 00000014

00401519    push 00000014

0040151B    push eax

0040151C    push dword ptr [00402034]

00401522    Call 00401EC7           call GetWindowTextA

00401527    call 0040167F           call alla prima routine di calcolo del keyfile

0040152C    call 00401B79            call ad una routine che apre e legge il keyfile

00401531    call 00401BCE           call alla seconda routine di calcolo del keyfile

00401536    mov ebp, 4243484B

0040153B    mov ax, 0004

0040153F    int 03

00401540    cmp al, 04

00401542    jne 004011EE

00401548    mov esi, 00402BFC       mette in esi il valore giusto del keyfile

0040154D    mov edi, 004025F0       mette in edi il valore del nostro keyfile

00401552    mov ecx, dword ptr [004031FC]   mette in ecx il numero di caratteri da confrontare

00401558    repz                     |__ Li confronta

00401559    cmpsb                    |

0040155A    je 00401DE9              salta se sono uguali

 

Ecco, tentare di spiegare COMPLETAMENTE tutti i cavilli che ci sono dietro a questo terzo punto, porterebbe via troppo spazio, quindi cercherò di descrivere le cose fondamentali senza però dilungarmi troppo su tutte le call di calcolo e ricalcolo.

Innanzitutto, primo problema: come faccio a sapere dove è/se esiste/dove deve stare il keyfile? Questo problema è semplice. Come vedete dal frammento di prog qui sopra, alla linea 0040152C c'è una call ad una routine che apre e legge il keyfile. Okay, andiamo a vedere:

 

:00401B79    push 00000002

:00401B7B    push 00402050

:00401B80    push 0040322A      push il nome del file da aprire

:00401B85    Call 00401E43          call OpenFile

:00401B8A    mov dword ptr [00402048], eax

:00401B8F    cmp eax, FFFFFFFF  se eax = FFFFFFFF, allora il file non è stato aperto

:00401B92    je 00401560        salta a errore

:00401B98    push 00000000

:00401B9A    push 00000000

:00401B9C    push dword ptr [004032A7]

:00401BA2    push dword ptr [00402048]

:00401BA8    Call 00401E3D          call SetFilePointer

:00401BAD    push 00000000

:00401BAF    push 00402320

:00401BB4    push dword ptr [004031FC]  push del numero di byte da leggere

:00401BBA    push 004025F0

:00401BBF    push dword ptr [00402048]

:00401BC5    Call 00401E31         call ReadFile: legge e salva i byte contenuti nel keyfile

:00401BCA    ret

 

quindi, se lo eseguiamo notiamo che dopo readfile avremo eax = FFFFFFFF. Questo vuol dire che l'operazione di apertura del file non è avvenuta perchè il file non

c'è. Se visualizziamo il contenuto di 0040322A alla riga 00401B80, vediamo nella data window un bel CryKey.key. Ma và! Allora questo è il nome del fiel da aprire. Ma dove ce lo dobbiamo mettere? Risparmiatevi le volgarità e date ancora un'altra occhiata alla data window. Se scorriamo lo slider un pò più su vediamo che oltre a crykey.key ci sono i nomi di autoexec.bat e windows\sistem.ini. Senza sapere effettivamente a cosa servono (cioè il prog li usa per ottenere prima la root e poi la directory del windoze), posso andare ad intuito e provare a piazzare il crykey.key su c:. Se rieseguo il crackme, openfile mi dà esito negativo. Se il crykey.key lo metto in c:\windows, allora OpenFile mi dà esito positivo: in eax troverò 10. Quindi abbiamo capito dove deve stare il keyfile. NOTA: se continuate a rieseguire openfile senza riavviare il crackme, noterete che il valore restituito in eax varia di volta in volta, ecco perchè vi rompo sempre le palle e vi faccio riavviare sempre il crackme. Comunque, adesso occupiamoci del secondo problema, e cioè: che ci scrivo nel keyfile? E qui bisogna iniziare a ragionare. Se date un'occhiata al frammento di codice principale che vi ho scritto sopra, troverete alla riga 00401527 una call alla PRIMA routine di calcolo, e alla riga 00401531 una call alla SECONDA routine di calcolo. Se vi stampate le due routine (come ho fatto io sotto consiglio de Il Socio che colgo l'occasione di salutare), noterete subito una cosa che vi sarà fondamentale. Però iniziamo a chiarire alcune cose da subito:

 

la PRIMA routine la chiamerò     BA    (poi capirete perchè)

la SECONDA routine la chiamerò    A'

tra queste due routine viene letto il keyfile e vengono messi in memoria i byte letti

 

...tenetelo bene a mente. Cominciamo dalla routine A'. Questa inizia alla riga 00401BCE. Inizia con:

 

00401BCE    mov    eax,  004025F0

 

dove 004025F0 contiene i caratteri letti dal keyfile. Fin qui tutto okay. Questa routine prende i caratteri del keyfile, li elabora e li confronta con quelli generati dalla routine BA. La cosa preoccupante è che subito dopo questa istruzione vediamo:

 

00401BD3    xor    ecx,  ecx

00401BD5    mov  ebx,   dword ptr [00402BF0|

00401BDB   mov  edi,   dword ptr   [00402010|

00401BE1    mov  esi,    dword ptr  [00402014|

...

 

cosa c'è di brutto? Semplice, i tre valori che vengono messi in ebx, edi ed esi sono casuali. Se vi interessa saperlo, sono generati all'inizio del crackme dalla funzione GetCursorPos, che mette in 00402010 l'ascissa del cursore, in 00402014 la sua ordinata, e in 00402BF0 mette un valore calcolate tramite i primi due. Ora, siccome cliccare sempre sullo stesso pixel mi sembra una cazzata, considero questi valori come se fossero sempre casuali. Quindi questa routine ci ha segato le gambe in partenza, ed è inutile continuare ad analizzarla. Andiamo allora alla routine BA.

Fortunatamente questa inizia con dei valri che sono sempre fissi (sempre ad ogni riavvio del crackme!). Quindi possiamo procedere. Se andiamo a vedere la fine di questa routine, avremo in 00402BFC il valore calcolato internamente che dovrebbe essere uguale al keyfile. Ora notiamo che quesot valore è sempre diverso. E' chiaro che anche questa routine ad un certo punto mischia dei valori random con il codice in elaborazione. Quello che dobbiamo fare è trovare dove finisce la parte di calcolo con paramentri fissi e dove inizia quella con parametri random. Se confrontiamo la routine BA con A' noterete subito il punto di cambio (e capirete anche perchè ho chiamato così le due routine). Infatti alla riga 0040190B della routine BA, iniziano i valori random. Se confronto la parte di routine che inizia qui con la routine A', noterò che sono UGUALI. L'unica differenza è il buffer dove vengono elaborati i valori , che in BA è 00402BFC, mentre in A' è 004025F0.

Quindi B denota la prima parte della routine BA, e A denota la seconda parte della routine BA, seconda parte che è uguale alla routine A' !!!

Abbiamo ristretto il campo di esame alla sola sub-routine B, il che equivale a dire a 4/5 pagine di prog, ma non dobbiamo esaminare necessariamente istruzione per istruzione. Basta usare la testa! Iniziamo a far bollire l'acqua.... ehm, voglio dire, iniziamo a mettere in moto alcuni neuroni e cerchiamo di farli incontrare per vedere se ci dicono qualcosa. Noi sappiamo che non possiamo basarci sulle parti A ed A' perchè si basano su calcoli tramite parametri random. L'unico punto che abbiamo è B. Allora come funziona il tutto? Semplice! B genera una serie di byte, tramite calcoli fissi (mi raccomando riavviate SEMPRE il crackme o quello che dico non servo a niente), e questi byte vengono poi calcolati con parametri random da A. Poi il prog legge i byte dal keyfile (quindi byte FISSI), e li passa ad A' che li elabora con parametri random.E' quindi chiaro che i byte calcolati alla fine della sub-routine B devono necessariamente essere uguali ai byte letti dal keyfile, in modo che questi poi vengano elaborati allo stesso modo dagli stessi parametri random. Se voglio sapere quindi quali sono i byte che devo mettere nel keyfile, basta che vado alla riga 0040190B di B e visualizzo il contenuto di 00402BFC. Vedete i byte nella data window? Potete segnarli tutti da li in sotto, finchè non arrivate alla fine, cioè dove poi ci sono tutti 00. Il numero esatto di byte che vi dovete copiare potete anche non considerarlo, perchè tanto readfile sà già quanti ne deve leggere, e sono di meno di tutti i byte che vi andrete a copiare. Se però volete saperlo, allora tornate a ReadFile. Se considerate i parametri che vengono pushati prima della call, vedrete alla riga 00401BB4    push dword ptr [004031FC]. Come avevo già detto, qui è contenuto il numero di byte da leggere. Se lo visualizzate vedrete 000002BC. Questo valore in decimale è = a 700. E tralaltro è un parametro ricorrente nella rutine B.

Per segnarvi tutti i byte che vi servono, potete usare un dumper. Siccome a me non funzionavano nè l' Adump e nè l' IceDump, mi sono dovuto scrivere a mano tutti e 700 quei cazzo di byte. Scrivendoli a mano, mi è capitato di scrivere fischi per cazzi, cioè un 3A al posto di un A3, e non vi dico i riti satanici che ho fatto contro Quequero perchè non capivo l'errore (che poi era mio...). Comunque, o ve li scrivete a mano e li riscrivete con un editor esadecimale, oppure dumpate il tratto di memoria che contiene tutti i valori (cosa migliore).

Eccovi il mio keyfile (che ovviamente funziona solo con i valori del punto 1 e 2 che vi ho scritto in questo tute):

41 49 00 80      41 43 60 08      61 0B 60 00       61 03 10 08

61 0B 10 00      43 45 60 08      61 43 60 00       43 41 70 88

43 09 70 80      63 05 00 88      63 0D 00 80       43 45 70 88

43 4D 70 80      43 01 60 88      65 09 00 80       43 07 30 80

61 0F 10 00      41 05 00 08      61 05 00 88       61 05 00 88

20 4C 00 08      20 46 60 80      00 0E 60 88       00 06 10 80

00 0E 10 88      22 40 60 80      00 46 60 88       22 44 70 00

47 E3 A6 91      67 E5 B6 11      47 A5 B6 11       67 E5 B6 11

67 E5 B6 11      45 E7 D6 11      41 E9 B6 11       45 E5 96 99

61 05 00 88      61 05 00 88      00 00 31 AC       00 00 31 AC

00 00 31 AC      00 00 31 AC      00 00 31 AC       00 00 31 AC    

00 00 31 AC      00 00 31 AC      00 00 31 AC       00 00 31 AC    

DD 3A 82 40      DC 36 93 00      D5 36 92 04       D4 38 93 04

D5 38 92 04      DC 36 D3 C0      DC 36 92 04       DC 34 C3 40

D5 34 C2 40      D4 3A C3 C4      D5 3A D2 D4       DC 34 C3 C0

DD 34 C2 C0      D4 36 C3 40      D5 3A 02 44       D4 3C C2 80

08 00 F1 68      08 0E E1 28      08 00 A1 E8       09 0E E1 2C

D5 36 D2 84      D4 36 83 80      DC 3A 02 44       DC 30 92 C4

01 02 11 C0      08 0C 50 04      08 0C 11 C0       08 0E 40 84

0E 41 84 01      00 40 00 00      21 59 A4 0C       21 59 A4 0C

21 59 A4 0C      60 1A C4 04      40 52 C4 05       40 5A B4 04

40 52 B4 0C      62 1C C4 04      40 1A C4 0C       62 18 D4 84

62 50 D3 8C      42 5C A4 84      42 54 A4 8C       62 1C D4 84

62 14 D4 8C      62 58 C4 84      44 50 A4 8C       62 5E 94 8C

40 56 B4 0C      60 5C A4 04      40 5C A4 84       40 5C A4 84

01 15 A4 04      01 1F C4 8C      21 57 C4 84       21 5F B4 8C

21 57 B4 84      03 19 C4 8C      21 1F C4 84       03 1D D4 0C  

66 BA 02 9D      46 BC 12 1D      66 FC 12 1D       46 BC 12 1D

46 BC 12 1D      64 BE 72 1D      60 B0 12 1D       64 BC 32 95

40 5C A4 84      40 5C A4 84      21 59 95 A0       21 59 95 A0

21 59 95 A0      21 59 95 A0      21 59 95 A0       21 59 95 A0

21 59 95 A0      21 59 95 A0      21 59 95 A0       21 59 95 A0

FC 63 26 4C      FD 6F 36 0C      F4 6F 36 08       F5 61 37 08

F4 61 36 08      FD 6F 77 CC      FD 6F 36 08       FD 6D 67 4C

F4 6D 66 4C      F5 63 67 C8      F4 63 66 C8       FD 6D 67 CC

FD 6D 66 CC      F5 6F 67 4C      F4 63 A6 48       F5 65 66 8C

29 59 55 64      29 57 45 24      29 59 05 E4       28 57 45 20

F4 6F 76 88      F5 6F 27 8C      FD 63 A6 48       FD 69 36 C8

20 5B B5 CC      29 55 F4 08      29 55 B5 CC       29 57 E4 88

2F 18 20 0D      21 19 A4 0C      00 00 00 00       00 00 00 00

00 00 00 00      20 59 A4 0C      21 99 64 04       29 D9 E4 05

2B 99 A4 0D      2F C8 60 04      29 D8 60 04       2D 99 A4 0C

21 59 A4 0C      21 59 A4 0C      21 59 A4 0C       21 59 A4 0C

21 59 A4 0C      21 59 A4 0C      21 59 A4 0C

 

così è come appare nell' Hex Editor. Adesso che avete immesso il keyfile giusto al posto giusto, riavviate il cackme, e debuggate fino alla routine principale del terzo punto, fino ad arrivare alla riga 0040155A. Qui c'è il JE dopo il compare, e se avete fatto tutto bene dovrebbe essere attivo e farvi saltare verso l'ultima parte di codice!

 

PUNTO COMPLEMENTARE: PERCHE' NON APPARE IL MESSAGE BOX DI CONGRATULAZIONI?

Pensavate di aver finito? Col cazzo. C'è ancora un ultimo piccolissimo controllo. Nonostante abbiate risolto tutto, non appare il box di congratulazioni. Perchè? E' quello che andremo a vedere. Innanzitutto sappiamo che per il crackme Quequero ci vieta il patching, quindi ci deve essere un piccolo inghippo, un tracobetto che non dovrebbe rappresentare un problema. Iniziamo a debuggare. Dopo il jump seguente l'ultimo compare, finiamo a questo frammento di codice:

 

00401DE9    mov dword ptr [004032A3], 000057B3   muove in 004032A3 il valore 57B3

00401DF3    xor eax, eax                          azzera eax

00401DF5    mov eax, dword ptr [0040329B]        mette in eax il valore contenuto in 0040329B

00401DFA    mov ebx, dword ptr [0040329F]        continua con movimenti e calcoli

00401E00    add eax, ebx                          ...

00401E02    mov ebx, dword ptr [004032A3]

00401E08    add eax, ebx

00401E0A    mov ebx, dword ptr [00403431]        questo ci interessa: infatti è uguale a zero. Come mai gli altri valori contengono

00401E10    add eax, ebx                          qualcosa, e questo non contiene niente?

00401E12    mov ecx, 5723814A

00401E17    xor eax, ecx

00401E19    rol eax, cl

00401E1B    cmp eax, 8982D55C                     confronta il valore ottenuto dai calcoli con 8982D55C

00401E20    jne 00401108                          se non sono uguali salta ad errore

00401E26    call 00401B26                         esegue la message box di congratulazioni ed esce dal prog

 

Ricordate quando un pò più su in un commento vi ho detto: questo valore per ora non ci serve, ma vedrete che è fondamentale? Ecco il perchè. Quando si inserisce il primo serial, se è giusto il prog salva un valore, se è sbagliato ne salva un altro. Così per gli altri punti: se li abbiamo azzeccati salva il valore giusto, se li abbiamo sbagliati salva un valore sbagliato. In questa routine vediamo che il programma riprende questi valori "di conferma", li elabora un pò fino ad ottenere un risultato che và a confrontare con il valore fisso 8982D55C. Quello che subito mi incuriosisce è alla riga 00401E0A. Viene caricato dalla memoria un valore che è 0. Allora a che serve? So per certo che gli altri valori sono giusti, perchè vengono dai tre punti che ho risolto. Allora inizio a cercare col WDASM il punto in cui viene usata la locazione 00403431.

 

004011BF   push 00403462

004011C4    push 00000000

* Reference To: USER32.MessageBoxA, Ord:0000h

|

004011C6    Call 00401EA3

004011CB    inc dword ptr [00403431]

004011D1    push [ebp+14]

004011D4    push [ebp+10]

 

trovo che la locazione 00403431 viene usata solo nella routine che avevo scritto più sopra, ed in questa routine. Balza subito agli occhi che il valore della locazione viene incrementato ogni volta che si accede alla MessageBoxA. La MessageBox in questione è quella che si ottiene premendo su about nella schermata principale del crackme. Allora cosa devo fare? Quale è il valore per il quale mi apparirà il messagebox di congratulazioni? Potete tornare alla routine e cercare di trovare per quale valore venga alla fine dei calcoli eax = 8982D55C. Oppure, provate a culo per risparmiare tempo. Infatti il valore che ci interessa è determinato da quante volte premiamo il pulsante about per far apparire la messagebox di about. Ogni volta che premiamo su about, il valore nella locazione 00403431 che inizialmente è zero, viene di volta in volta incrementato. Se provate a culo, basta avviare il crackme, e prima di immettere i vari serial, premere una volta su about. Inserire il serial fisso, il Name/Srial, premere register, e... niente. Riprovate il tutto premendo due volte about. Niente. Niente anche per tre. Se About viene premuto 4 volte, poi inserite Serial fisso e Name/Serial... voilà! Appare finalmente la messagebox di congratulazioni. Quindi il valore buono di 00403431 è 4.

E con questo possiamo dichiarare concluso il corso quinto. Come vedete, non era mica bau bau micio micio.

Ho cercato di essere il più chiaro possibile, e spero che abbiate capito tutto.

      

     AndreaGeddon

 

Note Finali

Saluto tutti gli amici della mailing list con cui ho condiviso questo cracking, in particolare saluto Syscalo, Ritz, Cod, Quequero, Tinman, Grisù, Il Socio.

Disclaimer

Queste informazioni sono solo a scopo puramente didattico.

 
UIC's page of reverse engineering, scegli dove andare:

Home   Anonimato   Assembly    ContactMe  CrackMe   Links   
NewBies   News   Forum   Lezioni  
Tools   Tutorial   Search

UIC