Soluzione Crackme v1.00 by Cruehead |
||
Data |
by "C0d3M!nd" |
|
23/07/2005 |
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 |
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 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 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) 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
0040218E| 44 61 65 64 61
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 nn “Darth 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
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 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 |
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)