Soluzione Crackme v1.00 by Cruehead
Serial Calculation

Data

by "C0d3M!nd"

 

23/07/2005

UIC's Home Page

Published by Quequero

"Fare, o NON Fare. Non c'è provare"

Grazie Code proprio un bel tute!

"Le dimensioni non contano, [...] perché la Forza è mia alleata ed un potente alleato essa è"

....

E-mail: [email protected]

non è propriamente il mio indirizzo, ma se dovete contattarmi Daedalus mi inoltrerà i vostri messaggi

....

Difficoltà

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

 


Introduzione

Il programma in questione è un semplice crackme che richiede nome utente e serial. Il serial ovviamente viene calcolato in base al nome utente utilizzando un algoritmo matematico. Per questo tute basta una conoscenza base dell’assembly.

Tools usati

Ollydbg Debugger

Qcmdline PlugIn Indispensabile

Win32.hlp Guida alle API di Windoxs

URL o FTP del programma

Crackme v1.00 by Cruehead (è il primo crackme che trovate nella sezione crackme del sito della UIC)

Essay


Whela Ragazzi!!! Questo è il mio primo tute e come argomento di esordio o deciso di trattare un crackme semplice semplice. Questo tutorial è rivolto a coloro che, come me, conoscono ancora poco il mondo del reversing. In questo tute cercherò di essere il più chiaro possibile. Dopo questo primo palloso preambolo, cominciamo a reversare... Dunque, aprite Ollydbg, pigiate F3, e selezionate il file Crackme.exe. Olly automaticamente si posizionerà nell'entry point (il punto d'accesso) del programma. Premendo F9 lancerete in esecuzione il crackme.Andate su Help=>Register.Adesso vi apparirà la schermata in cui dovrete inserire un nome e un serial. Nel nostro caso (come nel 99,99998% dei casi) il serial viene calcolato manipolando in qualche modo il nome. Quindi nella casella "Name" inserite il vostro nick (nel mio caso userò quello del mio amico Daedalus) mentre nella casella serial inserite un codice a piacere, detto bogus code,[nel mio caso Darth Malak(scommetto che nessuno si è accorto che sono un fan di Star Wars)].Ora tornate ad olly e premete ALT+F1 per aprire la riga di comando e scrivete "bp GetDlgItemTextA" (rispettando le maiuscole e le minuscole).In questo modo nn fate altro che dire a olly di bloccare (in gergo di "brekkare"), di congelare, il programma quando viene eseguito il comando GetDlgItemTextA. Questo comando (se conoscete qualche linguaggio di programmazione capiterete quando vi dico che in realtà non è un comando ma una funzione) è compreso in alcune librerie standard del sistema operativo, chiamate API, che la maggior parte dei programmi utilizzano per funzionare. Nella fattispecie, la funzione "GetDlgItemTextA" non fa altro che leggere del testo da una casella di testo o da un elemento grafico in generale, e immagazzinarlo in una locazione di memoria specificata. Dopo aver chiarito la faccenda del break su un'API (intendendo la libreria, non gli insetti fastidiosi) possiamo tornare al nostro crackme e premere "OK". Tadà, improvvisamente vi ricompare per magia Olly, che come gli avevamo ordinato ha brekkato il programma quando è stata richiamata la funzione GetDlgItemTextA che fa parte dell'API USER32. Infatti se guardate nella barra del titolo vedrete scritto "module USER32". Ma a noi non interessa reversare l'API [almeno per ora, che sono niubbo, forse fra 5-6 secoli quando avrò 2317 anni, che ne saprò di + anche di Quequero e AndreaGeddon messi insieme (BWHAHAHAHAHHA) potrebbe anche interessarmi], a noi interessa reversare il crackme. Per tornare al codice assembly del crackme basta pigiare CTRL+F9 (serve per arrivare automaticamente all'uscita dell'API) e premere F8 per uscire definitivamente. A questo punto dovreste trovarvi qui:

004012A1  XOR     EAX,EAX

004012A3  CMP     [ARG.3],3EB

004012AA  JE      SHORT 004012F7

004012AC  CMP     [ARG.3],3EA

004012B3  JNZ     SHORT 004012F0

004012B5  PUSH    0B                                

004012B7  PUSH    0040218E                          

004012BC  PUSH    3E8                               

004012C1  PUSH    [ARG.1]                           

004012C4  CALL    <JMP.&USER32.GetDlgItemTextA>     

004012C9  CMP     EAX,1

004012CC  MOV     [ARG.3],3EB

004012D3  JB      SHORT 004012A1

004012D5  PUSH    0B                                 

004012D7  PUSH    0040217E                           

004012DC  PUSH    3E9                                

004012E1  PUSH    [ARG.1]                             

004012E4  CALL    <JMP.&USER32.GetDlgItemTextA>      

004012E9  MOV     EAX,1

004012EE  JMP     SHORT 004012F7

004012F0  MOV     EAX,0

004012F5  JMP     SHORT 00401284

004012F7  PUSH    EAX                               

004012F8  PUSH    [ARG.1]                             

004012FB  CALL    <JMP.&USER32.EndDialog>            

 

 

In tabella ho evidenziato in giallo le istruzioni di chiamata della nostra amata funzione GetDlgItemTextA e le 4 righe precedenti. Se guardate bene, noterete che le 4 istruzioni che precedono ogni CALL sono tutte dei PUSH. Questo comando serve ad immagazzinare nello stack (un'area di memoria temporanea) dei determinati valori. E a questo punto la domanda vi sorgerà spontanea: perché prima di ogni CALL vengono salvate queste informazioni nello stack? Che cosa rappresentano queste informazioni?

Cominciamo dalla seconda domanda. Le 4 informazioni che vengono inviate nello stack sono dei parametri, dei valori,necessari per la corretta esecuzione della funzione GetDlgItemTextA. Infatti se consultate lo storico e utilissimo WIN32.hlp  e cerchiamo la funzione GetDlgItemText, vedrete una cosa simile:

 

UINT GetDlgItemText(

HWND hDlg, // handle della finestra di dialogo
int nIDDlgItem, // identificatore del Controllo
LPTSTR lpString, // indirizzo del buffer per il testo
int nMaxCount // massimo numero di caratteri da prelevare
)

 

Vediamo di spiegare bene cosa sono tutti questi valori:

HWND hDlg è l'handle dell'oggetto da cui si deve leggere il testo. Un handle è un numero con cui Windoxs identifica ogni elemento presente sullo schermo.Questo cambia ogni volta che il prog viene avviato;

nIDDlgItem è un altro numero identificativo con cui il programma identifica un elemento presente sullo schermo;

lpString rappresenta l'indirizzo della locazione di memoria(buffer) in cui verrà salvato il testo letto dal controllo;

nMaxCount abbastanza semplicemente, indica quanti caratteri devono essere letti dal controllo.

 

Adesso credo che abbiate capito cosa sono i famosi 4 valori che vengono mandati nello stack. Adesso che sapete cosa rappresentano questi valori allora posso tranquillamente spiegarvi perché queste informazioni vengono mandate nello stack. Lo stack, come ho accennato prima, è una zona della memoria in cui è possibile "appoggiarvi" delle informazioni come se fossero dei piatti. Quando queste informazioni vengono "poggiate" in realtà vengono "impilate" come quando mettete dei piatti uno sopra l'altro. Ovviamente se le informazioni vengono impilate, quando vengono prelevate vengono per forza di cose prese in ordine inverso rispetto al quale sono state poggiate originariamente.Ecco uno schema idiota per farvi capire questo concetto fondamentale:

 

[INIZIO DELL'ESEMPIO PIU' IDIOTA DEL MONDO]

Questi sono i vostri piatti identificati da un numero \_1_/ \_2_/ \_3_/ \_4_/. La vostra trisnonna vi ha detto che li dovete impilare.Voi ovviamente cominciate dal primo fino ad arrivare all'ultimo: 

 

\_4_/

\_3_/

\_2_/

\_1_/  

il primo lo avete messo sul tavolo.Poi avete messo sopra il primo il secondo, poi sopra il secondo il terzo ecc. ecc. ecc.

Tuttavia, in questo stato, se la vostra trisnonna vi chiede di prendere il piatto n°1, voi dovete togliere prima tutti i piatti e poi prendere il piatto n°1.Siccome voi siete furbi :), e sapete in anticipo che la vostra trisnonna è ossessionata dall'ordine, e nn le piace perdere tempo XD, i piatti li impilerete così:

 

\_1_/

\_2_/

\_3_/

\_4_/  

 

così quando la vostra beneamata trisnonna vorrà il piatto 1, voi glielo porgerete in tempo record....

Se vi siete rotti i cogl!0n! (e avete ragione), adesso sarete felici di comprendere che i 4 piatti sono le famose informazioni, lo stack è il tavolo dove poggiate i piatti, e che nel vostro albero genealogico avete una trisnonna che si chiama GetDlgItemTextA 

[FINE DELL'ESEMPIO PIU' IDIOTA DEL MONDO]

 

Guardate di nuovo il codice (ve lo riporto sotto, altrimenti usurate la rotellina del mouse):

 

004012A1  XOR     EAX,EAX

004012A3  CMP     [ARG.3],3EB

004012AA  JE      SHORT 004012F7

004012AC  CMP     [ARG.3],3EA

004012B3  JNZ     SHORT 004012F0

004012B5  PUSH    0B                                 nMaxCount=0B(in decimale 11)

004012B7  PUSH    0040218E                           lpString = CRACKME.0040218E

004012BC  PUSH    3E8                                nIDDlgItem = 3E8 (1000.)

004012C1  PUSH    [ARG.1]                            hWnd

004012C4  CALL    <JMP.&USER32.GetDlgItemTextA>      GetDlgItemTextA

004012C9  CMP     EAX,1

004012CC  MOV     [ARG.3],3EB

004012D3  JB      SHORT 004012A1

004012D5  PUSH    0B                                  nMaxCount=0B(in decimale 11)

004012D7  PUSH    0040217E                            lpString = CRACKME.0040217E

004012DC  PUSH    3E9                                 nIDDlgItem = 3E9 (1001.)

004012E1  PUSH    [ARG.1]                             hWnd

004012E4  CALL    <JMP.&USER32.GetDlgItemTextA>       GetDlgItemTextA

004012E9  MOV     EAX,1

004012EE  JMP     SHORT 004012F7

004012F0  MOV     EAX,0

004012F5  JMP     SHORT 00401284

004012F7  PUSH    EAX                                

004012F8  PUSH    [ARG.1]                            

004012FB  CALL    <JMP.&USER32.EndDialog>            

 

 

Noterete, che seguendo la logica spiegata nell'esempio precedente, il n° di caratteri da prelevare, essendo l'ultimo parametro richiesto da GetDlgItemTextA viene messo nello stack per primo, mentre per esempio l'handle del controllo (il primo parametro richiesto) viene inserito nello stack per ultimo [Questa strana struttura dello stack viene chiamata LIFO(in eng Last In First Out l'ultimo che entra è il primo che esce)].A questo punto, capirete anche, che quando prima di una qualsiasi CALL, ci sono dei PUSH, significa che i valori messi nello stack sono richiesti dalla funzione per funzionare. Adesso sappiamo che i primi B caratteri[se nn vi piace l'hex(nn piace neanche a me) vi ricordo che B=11] del nostro nick, che si trovava nel controllo numero SE8 (l'handle nn ci interessa un caxxo), sono stati salvati nella locazione di memoria n° 0040218E.

Adesso che vi ho annoiato, vi faccio agire un po'. Premete F9.Trac!!Olly è apparso di nuovo.Ma che caxxo vuole?Adesso si è fermato di nuovo in USER32, e mi sembra proprio che si sia fermato nello stesso punto di prima!!Mmmhhh...usciamo di nuovo da USER32.Quindi premiamo di nuovo CTRL+F9 e poi premiamo F8.Nahhh....guardate: stiamo alla riga 004012E9 che si trova appena dopo la seconda chiamata a GetDlgItemTextA.Ma è logico...i più intelligenti fra voi (ma anche i più ritardati) avranno capito che GetDlgItemTextA è stata chiamata due volte, perché i valori da leggere sono 2(il nome e il serial).Adesso che sappiamo che sia nome e sia serial sono stati letti, andiamo a controllare dove stanno. Se riguardate bene i parametri della funzione GetDlgItemTextA, noi avevamo scovato dove vengono inviati i testi che vengono letti. Il primo sta all'indirizzo 0040218E(vedi riga 004012B7), mentre il secondo sta in 0040217E (vedi riga 004012D7). Se in olly volete controllare cosa c'è in una locazione di memoria scrivete nella riga di comando(ALT+F1) "d [indirizzo]". Nel nostro caso scrivete "d 0040218E".Pluf!Nella finestra in basso a sinistra vedrete scritto il vostro nick.Se invece scrivete "d 0040217E" troverete i primi 11 caratteri del serial.Bene!Ora sappiamo almeno dove sono.Ora prendete un foglio di carta igienica e scrivete 0040218E=Nome e sotto scrivete 0040217E=serial. Bene, ora siamo pronti per continuare. A questo punto il prog comincia a fare tante caxxate che sono riuscito a capire poco ma che nn sono tanto importanti.Per arrivare alla parte interessante potete fare in due modi: o premete F8 circa 170 volte (sconsigliato) oppure premete 1 volta CTRL-F9, 1 volta F8[questo comando serve per avanzare di un passo nel codice(in gergo si dice "step over")] e 1 volta ALT-F9(questo comando serve per tornare al programma principale senza uscire singolarmente da 23412342 API). Arriverete qui:

00401223   CMP EAX,0
00401226   JE SHORT 004011E6
00401228   PUSH 0040218E ; ASCII "Daedalus"
0040122D   CALL 0040137E
Questa call richiede l'indirizzo del buffer in cui si trova il nome
00401232   PUSH EAX
00401233   PUSH 0040217E ; ASCII "Darth Mala"
00401238   CALL 004013D8
Questa call richiede l'indirizzo del buffer in cui si trova il serial
0040123D   ADD ESP,4
00401240   POP EAX
00401241   CMP EAX,EBX

 

Se olly sta settato bene, e magari avete installato tutti i plugin che ci sono sul sito della UIC, affianco alle righe evidenziate di giallo vedrete comparire il vostro nome e i primi 10 caratteri del seriale.

Quando arrivate alla riga 00401228 premete F2. In questo modo setterete un punto di stop, un cosidetto break point.Quando olly incontra una riga segnalata come breakpoint, brekka il programma.

Adesso siamo vicini al punto in cui cominciano le "manipolazioni" fermiamo le dita e controlliamo cosa fa il prog. Come avete letto nel commento del codice, la prima CALL al 99,99998% manipola il nome, perché se avete notato, subito prima della CALL viene mandato nello stack l'indirizzo del buffer dove è salvato il nome.Arrivate con F8 fino alla riga 0040122D e quando questa riga sarà evidenziata premete F7[F7 ha la stessa funzione di F8 tranne per il fatto che con F7 si "entra" nelle CALL(step into) mentre con F8 le CALL vengono eseguite senza "entrare"(step over)].Adesso sarete nel codice della CALL:

 

0040137E        MOV     ESI,SS:[ESP+4]  L’indirizzo 0040218E viene spostato in ESI

00401382        PUSH    ESI             ESI viene salvato nello stack

00401383        MOV     AL,DS:[ESI]     Viene copiato il primo byte dall’indirizzo puntato da ESI ad

                                        AL

00401385        TEST    AL,AL           AL=0??

00401387        JE      SHORT 0040139C  Se AL=0 allora salta a 0040139C (fine lettura)

00401389        CMP     AL,41           Confronta AL con 41h

0040138B        JB      SHORT 004013AC  Se AL<41h allora salta a 004013AC (serial sbagliato!!)

0040138D        CMP     AL,5A           Confronta AL con 5Ah

0040138F        JNB     SHORT 00401394  Se AL>5Ah salta a 00401394

00401391        INC     ESI             Incrementa ESI

00401392        JMP     SHORT 00401383  Salta a 00401383 (prox lettera)

00401394        CALL    004013D2        Chiamata di una CALL a 004013D2

00401399        INC     ESI             Incrementa ESI

0040139A        JMP     SHORT 00401383  Salta a 00401383 (prox lettera)

0040139C        POP     ESI             Prendi dallo stack il primo “piatto” e mettilo in ESI

0040139D        CALL    004013C2        Chiamata di una CALL a 004013C2 (totalizzazione)

004013A2        XOR     EDI,5678        EDI(totale) XOR 5678h

004013A8        MOV     EAX,EDI         EAX=EDI

004013AA        JMP     SHORT 004013C1  Salto a 004013C1(fine manipolazione)

004013AC        POP     ESI

004013AD        PUSH    30

004013AF        PUSH    00402160                            ; |Title = "No luck!"

004013B4        PUSH    00402169                            ; |Text = "No luck there, mate!"

004013B9        PUSH    [ARG.1]                             ; |hOwner

004013BC        CALL    <JMP.&USER32.MessageBoxA>           ; \MessageBoxA

004013C1        RETN

004013C2        XOR     EDI,EDI

004013C4        XOR     EBX,EBX

004013C6        MOV     BL,DS:[ESI]

004013C8        TEST    BL,BL

004013CA        JE      SHORT 004013D1

004013CC        ADD     EDI,EBX

004013CE        INC     ESI

004013CF        JMP     SHORT 004013C6

004013D1        RETN

004013D2        SUB     AL,20

004013D4        MOV     DS:[ESI],AL

004013D6        RETN

 

Se conoscete bene l’assembly capirete subito che fa questa routine (ma se sapete bene l’assembly perché perdete tempo a leggere questo tute), cmq per i principianti spiegherò un po’ tutto. Subito dopo aver premuto F7 sulla riga 0040122D vi ritroverete alla riga 0040137E(ma và!).Questa prima istruzione (MOV ESI,SS:[ESP+4]) si occupa di copiare l’indirizzo 0040218E in ESI. Ehi!!L’avete conservato il foglio di carta igienica. Andate un attimo a guardare:noi avevamo scritto “0040218E=Nome.Eccolo! Quindi il nostro crackme per prima cosa sposta in ESI l’indirizzo del Nome [ecco dimostrato a cosa serviva mettere le cose nello stack (per chi nn lo sapesse SS:[Indirizzo] significa prendi dallo stack l’elemento che si trova a [indirizzo])]. Poi l’indirizzo viene rimesso nello stack perché probabilmente(leggete "sicuramente") dopo verrà recuperato.Alla riga 00401383 il byte che si trova all’indirizzo indicato da ESI (ESI=0040218E=Nome!) viene spostato in AL. In termini terrestri in AL viene copiato il primo carattere del nostro nome. La riga successiva (00401385) è molto importante e molto comune. Come indicato nel commento viene controllato se AL=0, e in caso positivo si salta a 0040139C. Questa istruzione è molto importante perché viene controllato se si è raggiunta la fine della stringa. Ogni stringa in assembly termina sempre con un carattere terminatore che ha codice ASCII=00. In pratica, quando il nostro prog ha preso il nome Daedalus dalla casella di testo, in memoria lui lo ha rappresentato come:

0040218E|  44  61  65  64  61  6C  75  73  00

Indirizzo |   D   a    e    d    a   l     u    s

 

Potete notare che alla fine della stringa, subito dopo il codice ASCII della “s” c’è un altro codice: 00.Questo è il carattere terminatore[i più bravi si saranno accorti che quando è stato prelevata la stringa del serial è stato memorizzato “Darth Mala”(10 caratteri) e nnDarth Malak”(11 caratteri).Adesso penso che abbiate capito perché:-)].

Tornando al crackme credo che adesso la riga 00401385 sia più chiara.La riga successiva al TEST, è un'istruzione di salto. Le istruzioni di salto si dividono in salti condizionati e salti incondizionati. I primi vengono eseguiti solo al verificarsi di particolari condizioni, al contrario dei secondi che vengono eseguiti in ogni caso. Esistono tanti tipi di salti condizionati [se ne volete sapere di più leggete le guide di Assembler di bonu$ che trovate sul sito (cosa!? nn le avete ancora lette??)]. Nel nostro caso il salto viene eseguito se la condizione controllata da un TEST risulta vera. Quindi, nel nostro frangente, questo salto serve per raggiungere le operazioni che devono essere effettuate a fine lettura.Continuiamo la nostra analisi dell'algoritmo. Subito dopo la verifica di fine stringa, vengono effettuati diversi controlli sul carattere prelevato. Prendetevi un attimo una tabella ASCII che riporti anche i codici esadecimali. Prima viene controllato se il carattere prelevato ha un codice ASCII inferiore a quello della A [il codice ASCII di A è 41h(h significa esadecimale)] e poi se il carattere ha un codice ASCII superiore a quello della Z (il codice ASCII di Z è 5Ah). Controllando i jump successivi ai controlli, possiamo vedere che se il carattere è "inferiore" ad A , allora viene dato automaticamente un msg di errore [alla riga 004013AC comincia il settaggio per la visualizzazione di un messaggio di errore(l'api che si occupa di mostrare messaggi a video è MessageBoxA,andatela a controllare in Win32.hlp) che ha come testo "No luck there, mate"(molto incoraggiante vero?);mentre se il carattere è "maggiore" di Z, viene fatto un salto ad una call che diminuisce di 20h il valore del carattere. Questa diminuzione serve chiaramente a mettere tutto il nome in MAIUSCOLO.

Esempio veloce:

immaginiamo che il carattere preso in considerazione sia "f" che ha codice ASCII 66h.Se il numero 66h viene diminuito di 20h abbiamo 46h che corrisponde al codice ASCII di "F"(ricordate che la sottrazione la dovete fare in esadecimale)

 

In seguito a questi salti (a meno che nn venga visualizzato il msg di errore) viene sempre fatto un incremento di ESI e si risalta all'inizio dell'algoritmo con un  JMP 00401383. Come?! Viene incrementato ESI?Che significa? Vi ricordate cosa c'era in ESI? ESI=0040218E=Nome. Adesso questo valore lo abbiamo incrementato di 1 quindi diventa 0040218F.Andate a vedere cosa c'è a questo indirizzo, scrivete nella riga di comando "d 0040218F".Vedrete nella finestra in basso a sinistra il secondo carattere del vostro nome. Spero che abbiate capito che l'incremento di ESI serve per prendere il prox carattere del nome. Quando ESI raggiungerà una locazione di memoria che contiene 00, il TEST alla riga 00401385 darà esito positivo e di conseguenza verrà effettuato il salto immediatamente successivo. Alla riga 0040139C viene fatta un'operazione di POP. Il POP è praticamente il contrario di PUSH: serve a prelevare un elemento dallo stack. POP ESI, significa prendi il primo elemento che si trova nello stack (che per tt il discorso di prima è l'ultimo ad essere stato depositato) e mettilo in ESI. Dopo aver eseguito questa istruzione, se andate a vedere in ESI cosa c'è, vedrete scritto 0040218E(ormai sapete che questo è l'indirizzo del vostro nome). Subito il POP abbiamo una CALL. Seguiamola. Sempre x nn farvi usurare troppo il mouse ve la rimetto qui sotto:

 

004013BC        CALL    <JMP.&USER32.MessageBoxA>           ; \MessageBoxA

004013C1        RETN

004013C2        XOR     EDI,EDI        Vi ritroverete qui dopo la call

004013C4        XOR     EBX,EBX        Questi primi due XOR servono ad azzerare EDI e EBX

004013C6        MOV     BL,DS:[ESI]    BL=[Byte puntato da ESI]

004013C8        TEST    BL,BL          Ormai questo lo sapete [BL=0?]

004013CA        JE      SHORT 004013D1 Se la stringa è finita salta 004013D1

004013CC        ADD     EDI,EBX        Fai questa operazione EDI=EDI+EBX

004013CE        INC     ESI            Incrementa ESI (questo pure sapete che significa)

004013CF        JMP     SHORT 004013C6 Torna all'inizio dell'algoritmo

004013D1        RETN                   Torna alla riga successiva alla CALL

004013D2        SUB     AL,20

004013D4        MOV     DS:[ESI],AL

004013D6        RETN

 

Dopo la CALL vi troverete alla riga 004013C2. Su questa riga e sulla successiva, c'è un'operazione di XOR. Adesso, nn credo che sia necessario soffermarsi su cosa fa lo XOR {andatevi a vedere le guide di bonu$ [voi sarete reverser, dovete imparare ad autodocumentarvi (minchia che parolona)]}; vi basti sapere che se si fa uno XOR EAX,EAX viene azzerato EAX; se viene fatto XOR EBX,EBX viene azzerato EBX; se si fa uno XOR registro,registro viene azzerato registro. Allora prima vengono azzerati EDI ed EBX.

Poi viene copiato in BL il primo car del nome (ESI se vi ricordate contiene l'indirizzo 0040218E). BL rappresenta i primi 8 bit di EBX.

Poi viene controllato se il carattere è 00 (come prima, serve per capire se la stringa è finita) e in caso positivo si salta 004013D1. Se la stringa nn è finita, somma EBX ad EDI(che all'inizio è 0); incrementa ESI; e ritorna all'inizio dell'algoritmo. Tornando all'inizio, questa volta verrà prelevato il secondo carattere della stringa (ESI è stato incrementato, come quando doveva essere messo in maiuscolo) e il suo codice ASCII verrà sommato a quello del precedente... e così via, fino a quando nn si raggiungerà il terminatore della stringa (carattere 00). Alla fine dell'algoritmo, quando si salterà a 004013D1, EDI conterrà la somma dei codici ASCII dei caratteri del vostro nome messi in maiuscolo; e l'istruzione RETN farà tornare il programma all'istruzione successiva alla CALL(cioè alla riga 004013A2).Qui verrà fatto uno XOR di EDI con 5678. Prima di uscire dall'algoritmo che manipola il nome che fornite, il valore di EDI verrà copiato in EAX. Riassumiamo brevemente questo facile algoritmo che abbiamo appena finito di smontare:

 

--ALGORITMO 1--

1. Prendi i primi 10 caratteri del nome e mettili in maiuscolo

2. Prendi i codici ASCII di ogni carattere del nome e sommali

3. Fai uno XOR del totale delle addizioni con il numero hex 5678(22136 per gli esadecimofobi)

4. Metti il risultato dello XOR in EAX

--FINE ALGORITMO--

 

Dopo tutto sto macello tornerete qui:

 

00401223   CMP EAX,0
00401226   JE SHORT 004011E6
00401228   PUSH 0040218E ; ASCII "Daedalus"
0040122D   CALL 0040137E

00401232   PUSH EAX     
Voi tornerete qui
00401233   PUSH 0040217E ; ASCII "Darth Mala"
00401238   CALL 004013D8
Questa call manipola il serial
0040123D   ADD ESP,4
00401240   POP EAX
00401241   CMP EAX,EBX

 

Prima della CALL che manipola il serial, ci sono due PUSH. Il primo salva nello stack la somma dei codici ASCII del nome XORATI con 5678(d'ora in poi, per amor di brevità chiamiamolo "NomeCodificato"); mentre con il secondo PUSH viene mandato nello stack l'indirizzo del serial inserito. Adesso facciamo uno step into, e ci troveremo qui:

 

004013D8    XOR EAX,EAX Azzera EAX
004013DA    XOR EDI,EDI Azzera EDI
004013DC    XOR EBX,EBX Azzera EBX
004013DE    MOV ESI,SS:[ESP+4] ESI=Indirizzo del serial
004013E2    MOV AL,0A  AL=0Ah(10 in decimale)
004013E4    MOV BL,DS:[ESI] BL=Codice ASCII della lettera puntata da ESI
004013E6    TEST BL,BL  Controllo di fine stringa
004013E8    JE SHORT 004013F5 Se sì vai 004013F5(Operazioni finali)
004013EA    SUB BL,30 Sottrai 30h(48 in decimale) a BL
004013ED    IMUL EDI,EAX Moltiplica EDI*EAX
004013F0    ADD EDI,EBX Aggiungi EBX a EDI
004013F2    INC ESI Incrementa ESI (prendi la prox lettera)
004013F3    JMP SHORT 004013E2 Salta a 004013E2(ricomincia il ciclo)
004013F5    XOR EDI,1234 EDI XOR 1234
004013FB    MOV EBX,EDI EBX=numero ottenuto dal serial (serial codificato)
004013FD    RETN ritorna al programma principale

 

Le prime 3 righe dell'algoritmo sono elementari: vengono azzerati EAX,EDI ed EBX. Subito dopo questi azzeramenti, viene copiato in ESI l'indirizzo del serial. In seguito AL (i primi 8 bit di EAX) viene settato a 10(A in hex), mentre in BL (i primi 8 bit di EBX) viene spostato il carattere puntato da ESI (al primo "giro" è il primo carattere). Poi viene fatto il solito controllo di fine stringa e in caso positivo viene fatto un salto alla fine dell'algoritmo per le operazioni finali. Se la fine della stringa nn è stata raggiunta, viene sottratto 30(in decimale 48) al codice ASCII del carattere del serial (che si trova in BL). Viene moltiplicato EDI per EAX, e poi viene sommato EBX(che sarebbe il carattere del serial diminuito di 30h) a EDI. Dopodiché viene incrementato ESI (per prendere il prox carattere) e viene ripetuto il processo. Quando finalmente si raggiunge la fine della stringa, si fa uno XOR di EDI con 1234 e il risultato viene copiato in EBX. Riassumiamo l'algoritmo prestando particolare attenzione a ciò che succede ad ogni ciclo:

 

--ALGORITMO 2--

1. Vengono azzerati EAX,EDI ed EBX.

2. Ad ESI viene assegnato l'indirizzo del serial

3. AL quindi EAX viene settato a 10(in esadecimale A)

4. Viene copiato in BL (e quindi in EBX) il codice ASCII del carattere indicato da ESI

5. Viene effettuato un controllo per la fine della stringa e in caso positivo si salta al punto 11

6. Viene sottratto 30h a BL 

7. Viene moltiplicato EDI per EAX(quindi viene moltiplicato per 10)

8. Viene sommato EBX(BL diminuito di 30h) a EDI

9. Si incrementa ESI

10. Si salta al punto 3 per ricominciare il ciclo

11. Raggiunta la fine della stringa viene fatto uno XOR di EDI con 1234h(4660)

12. EBX=EDI

--FINE ALGORITMO--

 

Quindi alla fine dell'algoritmo, otterremo un numero (contenuto in EBX) che rappresenterà il vostro serial.

Torniamo al programma principale:

 

00401238    CALL 004013D8 Siamo appena usciti da questa CALL
0040123D    ADD ESP,4 Viene scaricato dallo stack il primo elemento

00401240    POP EAX Viene ricaricato in EAX il nome codificato

00401241    CMP EAX,EBX Viene comparato il nome codificato con il serial codificato

00401243    JE SHORT 0040124C Se sono uguali si salta al msgbox di Congratulazioni

00401245    CALL 00401362 Altrimenti si fa una call che ti manda affan*

0040124A    JMP SHORT 004011E6

0040124C    CALL 0040134D

00401251    JMP SHORT 004011E6

 

Ecco, abbiamo quasi finito la prima parte (cosa!???). Viene per prima cosa caricato il nome codificato in EAX; e poi viene comparato con il serial codificato(che si trova in EBX). Se sono uguali effettua un salto ad una messagebox che si congratula con noi altrimenti viene fatta una call che ci manda a quel paese.

Adesso se voi foste dei cracker, vi prendereste un editor esadecimale, modifichereste la riga 00401243 (la trasformereste in un JMP o in un JNZ) e il gioco sarebbe finito.Qualsiasi serial inserireste il programma si congratulerà sempre con voi per quanto siete cracker. Noi però siamo REVERSER, abbiamo un onore da difendere, e quindi creeremo un keygen. Per creare un keygen è possibile usare un qualsiasi linguaggio di programmazione. Io userò il C per creare un semplice keygen DOS [ancora nn mi sono messo a imparare come si creano applicazioni per windoxs usando il C (ragazzi cosa pensavate?! sono niubbo anch'io!)]. Riassumiamo totalmente il programma:

 

1. Prendi il nome e il serial

2. Manipola il nome con l'ALGORITMO 1

3. Manipola il serial con l'ALGORITMO 2

4. Se i risultati dei due algoritmi sono uguali Congratulati con il Reverser altrimenti insultalo.

 

La condizione che noi dobbiamo rispettare affinché il serial sia valido è che il risultato dell'algoritmo 2 deve essere uguale a quello dell'algoritmo 1. Ragioniamo per gradi.

-ANALISI

Cosa sappiamo?

-Il nome (sarà il nostro nick o qualsiasi altro nome che NOI sceglieremo)

-ALGORITMO 1 (necessita del nome)

-ALGORITMO 2 (necessita del serial)

 

Cosa nn sappiamo?

-Serial da usare

 

Che interazioni ci sono fra questi dati?

-Il NOME viene manipolato da Algoritmo 1 per ottenere NomeCodificato

-Il Serial viene manipolato da Algoritmo 2 per ottenere SerialCodificato

-NomeCodificato deve essere uguale a SerialCodificato

 

Conclusioni:

Affinché il programma funzioni è necessario che SerialCodificato sia uguale a NomeCodificato. Noi abbiamo sia il nome e sia ALGORITMO 1; quindi possiamo ottenere NomeCodificato. Conoscendo ALGORITMO 2 e sapendo il suo risultato (SerialCodificato=NomeCodificato) possiamo reversare, far funzionare al contrario, ALGORITMO 2 per ottenere il SERIAL.

 

Considerazione personale:

Facile, no?

 

-REVERSING

La parte più difficile di tutto il processo è il reversing di ALGORITMO 2 [per reversing, intendo l'atto di partire dalla fine di un algoritmo per giungere ai dati iniziali(in realtà il reversing è molto più potente di quanto io possa immaginare)]. Riprendiamolo:

 

--ALGORITMO 2--

1. Vengono azzerati EAX,EDI ed EBX.

2. Ad ESI viene assegnato l'indirizzo del serial

3. AL quindi EAX viene settato a 10(in esadecimale A)

4. Viene copiato in BL (e quindi in EBX) il codice ASCII del carattere indicato da ESI

5. Viene effettuato un controllo per la fine della stringa e in caso positivo si salta al punto 11

6. Viene sottratto 30h a BL 

7. Viene moltiplicato EDI per EAX(quindi viene moltiplicato per 10)

8. Viene sommato EBX(BL diminuito di 30h) a EDI

9. Si incrementa ESI

10. Si salta al punto 3 per ricominciare il ciclo

11. Raggiunta la fine della stringa viene fatto uno XOR di EDI con 1234h(4660)

12. EBX=EDI

--FINE ALGORITMO--

 

Per prima cosa abbiamo bisogno di EBX, perché questo algoritmo ha come risultato proprio EBX.

EBX, se abbiamo seguito l'analisi di prima, rappresenta SerialCodificato, che a sua volta è uguale a NomeCodificato. Siccome al momento non abbiamo modo di conoscere SerialCodificato, calcoliamoci NomeCodificato tramite l'ALGORITMO 1. Per rendere più efficace la spiegazione è meglio ricorrere ad un esempio concreto. Immaginiamo di dover trovare il serial che corrisponde al nick del mio amico Daedalus. Applichiamo ALGORITMO 1:

 

--ALGORITMO 1--

1. Prendi i primi 10 caratteri del nome e mettili in maiuscolo

2. Prendi i codici ASCII di ogni carattere del nome e sommali

3. Fai uno XOR del totale delle addizioni con il numero hex 5678(22136 per gli esadecimofobi)

4. Metti il risultato dello XOR in EAX

--FINE ALGORITMO--

 

Il mio nick è Daedalus.

1.Portiamolo in maiuscolo: DAEDALUS

2.Facciamo una somma di tutti i caratteri della parola DAEDALUS.Quindi:

 

I valori sono in hex

D

44+

A

41+

E

45+

D

44+

A

41+

L

4C+

U

55+

S

53=

Tot

243

 

3.Facciamo lo XOR del risultato con 5678h

243h XOR 5678h = 543Bh

 

SerialCodificato=NomeCodificato=543Bh.

SerialCodificato=543Bh.

 

Ora abbiamo SerialCodificato. Continuando il reversing di ALGORITMO 2 troveremo l'istruzione EDI XOR 1234h. Dovete sapere che l'operazione inversa dello XOR è...un'altro XOR (sorpresi eh?). Fate questa prova:

    1. Aprite la calcolatrice di Windoxs

    2. Andate a Visualizza=>Scientifica

    3. Premete su hex

    4. Fate questa operazione 215 XOR 4. Otterrete 211

    5. Adesso fato 4 XOR 211.Otterrete 215

Capito?

Adesso che avete compreso i misteri più reconditi della calcolatrice di Windows, potrete conoscere il valore di EDI prima dello XOR, facendo EBX XOR 1234h per ottenere EDI.

 

EDI XOR 1234h = EBX

EDI = EBX XOR 1234h

 

EBX abbiamo detto che è eguale a SerialCodificato (543Bh), quindi EDI = 543Bh XOR 1234h = 460Fh.

Questo valore chiamiamolo ValoreFineCiclo.

ValoreFineCiclo = 460Fh = 17935

 

A questo punto, esistono diversi modi per reversare l'algoritmo. Io ne ho individuato uno piuttosto semplice.

Esaminiamo un attimo cosa succede nel ciclo che effettua le operazioni matematiche:

 

Viene copiato in BL (e quindi in EBX) il codice ASCII del carattere indicato da ESI

Viene sottratto 30h a BL 

Viene moltiplicato EDI per 10    [Questo è importante]

Viene sommato EBX(BL diminuito di 30h) a EDI

 

Il nostro obbiettivo è trovare una combinazione di caratteri (il serial) che se utilizzata con l'algoritmo 2, abbia come risultato 460F(ValoreFineCiclo). Siccome l'algoritmo è abbastanza "aperto" (non ci sono costanti o altri marchingegni del genere per rendere più complicato l'algoritmo) significa che ad un nome probabilmente corrispondono più serial (ecco perché c'è più di un modo per reversare questo algoritmo). Ovviamente, quando c'è la disponibilità di intraprendere più strade è meglio scegliere quella che presenta meno complicanze. Per risolvere questo algoritmo, basterebbe sottrarre a ValoreFineCiclo una serie di numeri casuali in modo da azzerare ValoreFineCiclo. Tuttavia, l'uso di numeri completamente casuali (o meglio pseudocasuali) potrebbe non essere la scelta migliore per via dell'indeterminazione che ne segue. Per esempio, potrebbe succedere che il computer, ad un certo punto, cominci a generare numeri troppo grandi in rapporto a ValoreFineCiclo, facendo fallire tutto l'algoritmo ecc.  ecc. ecc...

Quindi, è meglio sfruttare qualche caratteristica speciale dell'algoritmo; per esempio il fatto che EDI viene moltiplicato per 10 a ogni ciclo. Possiamo progettare un algoritmo che sottragga a ValoreFineCiclo dei numeri che rendano ValoreFineCiclo divisibile per 10 (o meglio per 50), in modo da agevolare le operazioni e ridurre le approssimazioni. Applichiamo questo ragionamento al nostro esempio.

 

Noi sappiamo che ValoreFineCiclo è 17935 [460F in hex (per questa parte dell'algoritmo è meglio ragionare in decimale)].

Il multiplo di 50 più vicino a questo valore è 17900. Quindi noi sottraiamo 35 a  17935 per avere 17900 (questa è matematica da elementari ;) ).

Adesso dividiamo per 10 e otterremo 1790. Adesso il multiplo di 50 più vicino a 1790 è 1750, per cui sottraiamo 40.Schematizziamo il processo:

 

17935 - 35 = 17900

17900 / 10 = 1790

 

1790 - 40 = 1750

1750 / 10 = 175

 

175 - 25 = 150

150 / 10 = 15

 

15 - 15 = 0 [Fine Ciclo]

 

I numeri evidenziati sono i numeri che sono stati sottratti per reversare l'algoritmo. Tuttavia, nell'ALGORITMO 2 si legge che questi numeri, che sommati e moltiplicati danno ValoreFineCiclo, vengono ottenuti sottraendo 48 (30 in hex) al codice ASCII dei caratteri del serial. Quindi per ottenere i caratteri bisogna aggiungere 48 a ciascuno di questi numeri per ottenere il codice ASCII dei caratteri che fanno costituiscono il serial. Addizioniamo.

 

Numero Originale Codice ASCII = Numero Originale + 48 Carattere Corrispondente al Codice ASCII
35 83 S
40 88 X
25 73 I
15 63 ?

 

La serie presente nell'ultima colonna letta la contrario (?IXS) è un possibile serial per il nome Daedalus. Provate. Avviate Crackme.exe senza toccare Olly (!!!), andate a Help=>Register. Scrivete "Daedalus" e "?IXS". Toh! Il programma si congratula con voi per la vostra bravura e vi dice di provare il prossimo crackme. Riiiiiiiiicapitoliamo come funzia l'ALGORITMO DAEDALUS:

 

--ALGORITMO C0d3M!nd (BWHAHAHAHHAHA) --

1. Calcolati SerialCodificato applicando ALGORITMO 1 al tuo nick

2. Fai SerialCodificato XOR 1234h per ottenere ValoreFineCiclo

3. Sottrai a ValoreFineCiclo un numero N tale che il risultato sia il multiplo di 50 più vicino

4. Addiziona 48 ad N e scrivi il risultato su un foglio di carta igienica.

5. Ripeti dal punto 3 fino a quando non ottieni 0.

6. Scrivi accanto ad ogni numero che hai scritto sul foglio di carta igienica il carattere corrispondente

7. Leggi la sequenza di caratteri dal basso verso l'alto per ottenere il serial

--FINE ALGORITMO--

 

Scommetto che adesso che avete reversato il vostro primo programma vi sentite tutti agitati, eccitati, non vedete l'ora di farlo vedere agli amici. Solo che qualche amico bastardo, vi dirà "Qual'è il serial per il nome PincoPallino?". A questo punto voi dovreste fare a mano tutte le operazioni elencate nel mio algoritmo, perdendo circa 274 secondi e 26 centesimi. Ma caspita! Noi siamo reverser! Siamo maghi della programmazione! I linguaggi di programmazione nn hanno segreti per noi! Creiamo un keygen...

 

- CREAZIONE DEL KEYGEN (FASE FINALE)

Il mio tutorial, per quelle 1-2 persone che lo leggeranno, volge al termine. Questa è davvero l'ultima fase. Probabilmente non vedrete mai più il nome "C0d3M!nd" scritto nel titolo di una pagina Web sul cracking. Vabbè, lasciamo stare le smancerie, e passiamo alle cose serie. Siccome non sono un cracker famigerato, utilizzerò per il keygen il linguaggio C/C++ per creare applicazioni DOS, che è piuttosto snello. Eccolo (commentato, ovviamente):

 

#include <stdio.h>

void main(){
    char nome[11], seriale[11]=""; //LA STRINGA seriale VIENE AZZERATA APPENA DOPO ESSERE STATA DICHIARATA
    int i, carat;
    long int NomeCodificato=0, ValoreFineCiclo=0;
    puts("Inserisci il nome da usare: ");
    gets(nome);

    //INIZIO ALGORITMO 1
    for(i = 0;i < 11;i++){
    if(nome[i] > 90) //IL CARATTERE E' IN MAIUSCOLO?? (HA UN CODICE ASCII > 90)
        nome[i] = nome[i] - 32; //SE NO, METTILO IN MAIUSCOLO (DIMINUISCI IL CODICE ASCII DI 32)
    if(nome[i] > 0)
        NomeCodificato = NomeCodificato + nome[i]; //SOMMA IL CODICE ASCII A NOMECODIFICATO
    }
    //FINE ALGORITMO 1
    NomeCodificato = NomeCodificato ^ 22136; //VIENE FATTA L'OPERAZIONE NomeCodificato XOR 5678h
    ValoreFineCiclo = NomeCodificato ^ 4660; //VIENE FATTA L'OPERAZIONE ValoreFineCiclo = NomeCodificato XOR 1234h
    //REVERSING ALGORITMO 2
    carat = 0;
    for(i = 0;ValoreFineCiclo > 0;i++){ //INIZIALIZZAZIONE CICLO: IL CICLO TERMINA QUANDO VALOREFINECICLO=0
        carat = ValoreFineCiclo - int(ValoreFineCiclo / 100) * 100; //ESTRAGGO DA VALORE FINE CICLO LE PRIME 2 CIFRE
        if (carat>50) //SE LE 2 CIFRE SONO MAGGIORI DI 50, CALCOLO LA DIFFERENZA NECESSARIA AD AVVICINARE ValoreFineCiclo AL MULTIPLO DI 50
            carat= 50 - (100 - carat);
        seriale[i] = carat + 48; //AGGIUNGO 48 AL NUMERO OTTENUTO PER AVERE IL CODICE ASCII DEL CARATTERE DEL SERIAL
        ValoreFineCiclo = (ValoreFineCiclo - carat) / 10; //PREPARO ValoreFineCiclo PER LA PROSSIMA ITERAZIONE    
    }
    //FINE REVERSING ALGORITMO 2
    //ADESSO LA STRINGA seriale CONTIENE IL SERIALE SCRITTO AL CONTRARIO
    //STAMPIAMOLO ALLA ROVESCIA
    for(i = 10;i >= 0;i--){
        if (seriale[i] != 0) //SE IL CARATTERE PUNTATO DA i NON E' 0 ALLORA STAMPA IL CARATTERE SU SCHERMO
            printf("%c",seriale[i]);
    }
    printf("\n");
}

 

Io per la compilazione ho usato Microzoft Visual C++ 6.0, ma immagino che debba funzionare senza problemi anche su un'altro qualsiasi compilatore C. Ho cercato di essere il più chiaro possibile nella scelta dei nomi delle variabili, nei commenti, nell'indentazione, nella sintassi un po' più prolissa del solito, ecc ecc. Ho volutamente eliminato qualsiasi controllo riguardo la correttezza del nome o del serial (Questo fatelo voi).

Adesso facciamo una prova, proviamo a calcolare il serial per PincoPallino. Avviate il programma, scrivete "pincopallino" e in meno di qualche frazione di secondo avrete il vostro serial: non male eh?

Prima di terminare il tutorial, voglio farvi notare che gli scogli più duri da abbattere nel reversing di un algoritmo sono dovuti all'incomprensione di alcuni passaggi. Se continuerete col reversing, imparate a schematizzare in modo banale gli algoritmi, assicuratevi di aver capito alla perfezione cosa fanno e come lo fanno. Imparare a sintetizzare e schematizzare è fondamentale per il reversing.

Adesso smetto di annoiare lo sfortunato lettore che ha avuto l'enorme pazienza di giungere alla fine di un tutorial scritto in modo superficiale e in un cattivo italiano. Proprio così. FINISHHHHHHHHHHHHHHH!

                                                                                                                 C0d3M!nd

Note finali

Questo è il mio primo, e probabilmente ultimo, tutorial che ho scritto e scriverò. Credo che sia un po' confusionario, ma spero che se qualche reverser alle prime armi lo leggerà, questo qualcuno riesca a reversare il crackme 1 by Cruhead. Infine, ringrazio il mio amico Daedalus per essersi preso la briga di mandare questo tutorial al posto mio, e mi auguro che un giorno noi reverser saremmo trattati con una certa dignità e non come dei criminali, dei ladri. L'uomo ha conquistato la natura grazie alla sua sete di conoscenza. Privare l'uomo di una simile caratteristica non lo rende molto superiore a un'altra qualsiasi forma di animale. (Curiosità finale: provate a generare il serial per il nick CodeMind....notate qualcosa?Avete visto che coincidenza exxtrema). Che la FORZA sia con voi!

Disclaimer

Vorrei ricordare che il software va comprato e  non rubato, dovete registrare il vostro prodotto dopo il periodo di valutazione. Non mi ritengo responsabile per eventuali danni causati al vostro computer determinati dall'uso improprio di questo tutorial. Questo documento è stato scritto per invogliare il consumatore a registrare legalmente i propri programmi, e non a fargli fare uso dei tantissimi file crack presenti in rete, infatti tale documento aiuta a comprendere lo sforzo che ogni sviluppatore ha dovuto portare avanti per fornire ai rispettivi consumatori i migliori prodotti possibili.

Reversiamo al solo scopo informativo e per migliorare la nostra conoscenza del linguaggio Assembly.

Non ho nulla da aggiungere (by C0d3M!nd)