A:link { TEXT-DECORATION: underline } A:visited { TEXT-DECORATION: underline } A:active { COLOR: red; TEXT-DECORATION: underline } A:hover { COLOR: #8080ff; TEXT-DECORATION: underline }
GuZuRa's MatMe2 Serial Fishing |
||
Data |
by Spider |
|
24 settembre 2002 |
UIC's Home Page |
Published by Quequero |
Cu si 'ncazza senza mutivu... |
Qualche mio eventuale commento sul tutorial :))) |
...si scazza senza sudisfazioni |
|
|
|
Difficoltà |
( )NewBies (X)Intermedio ( )Avanzato ( )Master |
Il crackme di GuZuRa promette di essere un interessante crackme matematico.
Introduzione |
Tools usati |
Essay |
Iniziamo l'analisi del crackme. Per trovare la routine di check del serial è sufficiente mettere un breakpoint su GetWindowTextA e GetDlgItemTextA, le due API più amate dal reverser... mettiamo un seriale a caso e premiamo ok... SoftICE ovviamente breakka immediatamente, premiamo F12 e ci troviamo subito dopo la chiamata a GetDlgItemTextA; osserviamo i parametri e notiamo subito che il serial inserito viene memorizzato all'indirizzo 004056E0. Dopo aver premuto F12 ci ritroveremo su una call, seguita da un cmp eax,1 e poco dopo da un jnz... Chiaramente è la chiamata alla routine di check del serial :)
.text:00401170 sub_401170 proc near ; CODE XREF: .text:004010EAp
.text:00401170 call sub_401180
.text:00401175 neg eax
.text:00401177 sbb eax, eax
.text:00401179 neg eax
.text:0040117B retn
.text:0040117B sub_401170 endp
Un po' piccolo come controllo, non credete? :P Infatti è solo una funzione "ponte". Il vero check si trova nella sub_401180 che invece è un po' più voluminosa. Qui notiamo invece:
.text:00401175 neg eax
.text:00401177 sbb eax, eax
.text:00401179 neg eax
Queste tre righe sono molto comuni nei programmi scritti in C. Con esse eax rimane invariato se è uguale a zero, altrimenti viene messo ad 1.
Bene, prendiamo IDA e disassembliamo MatMe2.exe, che ovviamente IDA sbrana in 2 secondi dato che il crackme occupa solo 28 kb :) Rinominiamo come serial la locazione 004056E0, e come CheckSerial la sub_401170 (per una guida ai comandi di IDA vi rimando al tutorial di Quequero).
Entriamo in CheckSerial e iniziamo l'analisi:
.text:00401180 CheckSerial proc near ; CODE XREF: sub_401170p .text:00401180 .text:00401180 var_29 = byte ptr -29h .text:00401180 var_28 = dword ptr -28h .text:00401180 var_24 = dword ptr -24h .text:00401180 var_20 = dword ptr -20h .text:00401180 var_1C = dword ptr -1Ch .text:00401180 var_18 = dword ptr -18h .text:00401180 var_14 = dword ptr -14h .text:00401180 var_10 = dword ptr -10h .text:00401180 var_C = dword ptr -0Ch .text:00401180 var_8 = dword ptr -8 .text:00401180 .text:00401180 sub esp, 2Ch .text:00401183 mov al, byte_4050C4 ;Mette in al il carattere ' ' (spazio) .text:00401188 push ebx .text:00401189 push ebp .text:0040118A push esi .text:0040118B lea ecx, [esp+38h+var_29] ;ecx punta a null (carattere 0) .text:0040118F push edi .text:00401190 push ecx ; const char * .text:00401191 push offset Serial ; char * .text:00401196 mov [esp+44h+var_29], al .text:0040119A xor esi, esi ;azzera esi che farà da contatore .text:0040119C call _strtok ;Prima chiamata a strtok .text:004011A1 add esp, 8 .text:004011A4 test eax, eax ;se la chiamata a strtok è fallita... .text:004011A6 jz short loc_4011D0 ;...interrompe il ciclo .text:004011A8 lea edi, [esp+3Ch+var_28] .text:004011AC .text:004011AC loc_4011AC: ; CODE XREF: CheckSerial+4Ej .text:004011AC push eax ; const char * .text:004011AD call sub_4013FB ;funzione ponte per richiamare atol .text:004011B2 lea edx, [esp+40h+var_29] ;edx punta al carattere ' '(spazio) .text:004011B6 mov [edi], eax .text:004011B8 push edx ; const char * .text:004011B9 push 0 ; char * .text:004011BB inc esi ;incrementa il contatore .text:004011BC add edi, 4 .text:004011BF call _strtok .text:004011C4 add esp, 0Ch .text:004011C7 cmp esi, 0Ah ;se abbiamo eseguito 10 volte... .text:004011CA jz short loc_4011D0 ;...interrompe il ciclo .text:004011CC test eax, eax ;se l'ultima call a strtok è fallita... .text:004011CE jnz short loc_4011AC ;...interrompe il ciclo
Questo spezzone di codice si occupa semplicemente di effettuare il parsing del seriale inserito. Come vediamo facilmente, il seriale è diviso in tante parti separate da spazi. Poiché il ciclo viene eseguito 10 volte, potremmo concludere che il serial sia diviso in 10 parti... però qualcosa non va:
.text:00401180 var_28 = dword ptr -28h .text:00401180 var_24 = dword ptr -24h .text:00401180 var_20 = dword ptr -20h .text:00401180 var_1C = dword ptr -1Ch .text:00401180 var_18 = dword ptr -18h .text:00401180 var_14 = dword ptr -14h .text:00401180 var_10 = dword ptr -10h .text:00401180 var_C = dword ptr -0Ch .text:00401180 var_8 = dword ptr -8
Ogni parte del seriale viene convertita in numero tramite la funzione atol (e ciò ci fa intuire anche che il seriale sia composto da soli numeri) e memorizzata in un array nello stack a partire da var_28, quindi abbiamo var_28, var_24, var_20, var_1C, var_18, var_14, var_10, var_0C, var_08... solo 9 variabili! Se inseriamo un seriale di 10 parti l'ultima viene praticamente ignorata, mentre i seriali corretti sono di 9 parti. Poiché la conversione in numero decimale è senz'altro il preludio alle operazioni matematiche che verranno dopo, assegniamo delle lettere da a ad i (rinominando le variabili da IDA) per facilitarci le cose.
La parte successiva è piuttosto corposa, ed è composta da una serie di 8 check sulle prime 8 parti del serial (ho eliminato le parti inutili per chiarezza):
.text:004011D0 mov ecx, [esp+3Ch+a] .text:004011D4 cmp ecx, 3 ; a deve essere maggiore di 3 .text:004011D7 jg short loc_4011E3 .text:[...] .text:004011E3 mov eax, [esp+3Ch+b] .text:004011E7 cmp eax, 3 ; b deve essere maggiore di 3 .text:004011EA jg short loc_4011F6 .text:[...] .text:004011F6 mov edi, [esp+3Ch+c] .text:004011FA lea edx, [eax+4] .text:004011FD cmp edi, edx ; c deve essere maggiore di b+4 .text:004011FF jg short loc_40120B .text:[...] .text:0040120B cmp [esp+3Ch+d], edi ; d deve essere maggiore di c .text:0040120F jg short loc_40121B .text:[...] .text:0040121B cmp [esp+3Ch+e], 2 ; e deve essere maggiore o uguale a 2 .text:00401220 jge short loc_40122C .text:[...] .text:0040122C mov ebp, [esp+3Ch+f] .text:00401230 cmp ebp, 3 ; f deve essere maggiore di 3 .text:00401233 jg short loc_40123F .text:[...] .text:0040123F mov esi, [esp+3Ch+g] .text:00401243 cmp esi, 5 ; g deve essere maggiore di 5 .text:00401246 jg short loc_401252 .text:[...] .text:00401252 mov ebx, [esp+3Ch+h] .text:00401256 cmp ebx, 3 ; h deve essere maggiore di 3 .text:00401259 jg short loc_401265
Bene, adesso sappiamo il formato generale del nostro serial:
a b c d e f g h i
con:
a > 3
b > 3
c > b + 4
d > c
e => 2
f > 3
g > 5
h > 3
Proseguiamo l'analisi della routine:
.text:00401265 loc_401265: ; CODE XREF: sub_401180+D9j .text:00401265 push 0 .text:00401267 push 0 .text:00401269 push eax ; eax = b .text:0040126A push ecx ; ecx = a .text:0040126B call calcola .text:00401270 mov r1, eax .text:00401275 mov eax, [esp+4Ch+d] .text:00401279 push 0 .text:0040127B push 0 .text:0040127D push eax ; eax = d .text:0040127E push edi ; edi = c .text:0040127F call calcola .text:00401284 mov ecx, [esp+5Ch+e] .text:00401288 push 0 .text:0040128A push 0 .text:0040128C push ebp ; ebp = f .text:0040128D push ecx ; ecx = e .text:0040128E mov r2, eax .text:00401293 call calcola [. . .] .text:004012AE mov r3, eax
Ho già rinominato alcune locazioni, per rendere il codice più chiaro. Questo spezzone di codice richiama tre volte una funzione (che ho chiamato calcola: la analizzeremo tra poco) passando come argomenti le prime 6 parti del serial, a due a due (prima a e b, poi c e d, infine e ed f). I risultati vengono poi salvati in tre dword che ho rinominato r1, r2 ed r3. Vediamo adesso la funzione calcola (anche qui ho rinominato qualcosa):
.text:00401310 ; Attributes: bp-based frame .text:00401310 .text:00401310 calcola proc near ; CODE XREF: sub_401180+EBp .text:00401310 ; sub_401180+FFp ... .text:00401310 .text:00401310 x = dword ptr 8 .text:00401310 y = dword ptr 0Ch .text:00401310 flag = byte ptr 10h .text:00401310 newRetAddress = dword ptr 14h .text:00401310 .text:00401310 push ebp .text:00401311 mov ebp, esp .text:00401313 pop ebp .text:00401314 cmp [esp-4+flag], 1 .text:00401319 jz short loc_401326 .text:0040131B mov eax, [esp+0] ; eax = return address .text:0040131E mov [esp-4+newRetAddress], eax .text:00401322 inc [esp-4+flag] .text:00401326 .text:00401326 loc_401326: ; CODE XREF: calcola+9j .text:00401326 add esp, 4 .text:00401329 mov eax, [esp-8+y] ; eax = y .text:0040132D cmp eax, 0 ; se y è 0... .text:00401330 jz short loc_401363 ; ...usciamo dalla call .text:00401332 mov ecx, 2 .text:00401337 cdq .text:00401338 div ecx ; divide y per 2 .text:0040133A cmp edx, 1 .text:0040133D jz short loc_401348 ; salta se il resto è 1 .text:0040133F mov eax, [esp-8+x] ; eax = x .text:00401342 dec eax ; decrementa x .text:00401343 mov [esp-8+x], eax .text:00401346 jmp short loc_401355 .text:00401348 ; --------------------------------------------------------------------------- .text:00401348 .text:00401348 loc_401348: ; CODE XREF: calcola+2Dj .text:00401348 mov eax, [esp-8+x] .text:0040134B mov ecx, 3 .text:00401350 mul ecx ; moltiplica x per 3 .text:00401352 mov [esp-8+x], eax .text:00401355 .text:00401355 loc_401355: ; CODE XREF: calcola+36j .text:00401355 mov ecx, [esp-8+y] .text:00401359 dec ecx ; decrementa y .text:0040135A mov [esp-8+y], ecx .text:0040135E call calcola ; ripete tutto .text:00401363 .text:00401363 loc_401363: ; CODE XREF: calcola+20j .text:00401363 mov eax, [esp-8+newRetAddress] .text:00401367 push eax .text:00401368 mov eax, [esp-4+x] ; eax = return value .text:0040136C retn .text:0040136C calcola endp .text:0040136C
Questa è quella sorta di "tail ricorsione"
che Guzura ci aveva preannunciato nel readme. In effetti ci sono un po' di
smanettamenti con lo stack che servono in pratica per riutilizzare le stesse
variabili nello stack ad ogni ricorsione, evitando così di sprecare stack
inutilmente, che, per un numero molto elevato di ricorsioni, avrebbe anche
potuto provocare il crash del programma. La funzione risulta a prima vista
(ossia prima di rinominare gli argomenti e commentare il tutto) un po'
ingarbugliata, ma aiutandoci eventualmente con l'insostituibile supporto del debugger, il
codice diventa pian piano più chiaro mano mano che si commenta e si rinominano
le locazioni.
La funzione appena vista prende due soli argomenti (che ho chiamato x e y),
mentre gli altri due sono inutili ai nostri scopi perché servono solo alla
funzione per quella tail ricorsione appena vista.
Schematizzando, è questo l'algoritmo seguito dalla funzione:
se y è 0 finisce tutto
se y è pari x = x - 1
se y è dispari x = x * 3
decrementa y
ripeti
Poiché su questa funzione gravita tutto l'algoritmo, facciamo un po' di analisi matematica su input e output della funzione :) Niente paura, non è necessaria nessuna conoscenza matematica particolare, bastano qualche calcolo e un po' di intuito. Per facilitarci, creiamo un programmino che ci generi una lista di valori ritornati dalla funzione (che ribattezziamo funzione G(x,y), ovvero funzione GuZuRa :P):
x = 1; y = 1; r = 3
x = 1; y = 2; r = 0
x = 2; y = 2; r = 3
x = 1; y = 3; r = 6
x = 2; y = 3; r = 15
x = 3; y = 3; r = 24
x = 1; y = 4; r = -3
x = 2; y = 4; r = 6
x = 3; y = 4; r = 15
x = 4; y = 4; r = 24
x = 1; y = 5; r = 15
x = 2; y = 5; r = 42
x = 3; y = 5; r = 69
x = 4; y = 5; r = 96
x = 5; y = 5; r = 123
x = 1; y = 6; r = -12
x = 2; y = 6; r = 15
x = 3; y = 6; r = 42
x = 4; y = 6; r = 69
x = 5; y = 6; r = 96
x = 6; y = 6; r = 123
x = 1; y = 7; r = 42
x = 2; y = 7; r = 123
x = 3; y = 7; r = 204
x = 4; y = 7; r = 285
x = 5; y = 7; r = 366
x = 6; y = 7; r = 447
x = 7; y = 7; r = 528
Possiamo subito notare alcune cose importanti:
Bene, questo è tutto quanto risulta dalle mie 5 pagine di appunti sulla funzione :-)
Riprendiamo l'analisi del corpo della routine di check del serial:
.text:00401298 mov edx, r1 .text:0040129E mov edi, r2 .text:004012A4 mov ecx, edx ; ecx = r1 .text:004012A6 add esp, 30h .text:004012A9 imul ecx, esi ; ecx = r1 * g .text:004012AC add ecx, edi ; ecx = r1 * g + r2 .text:004012AE mov r3, eax .text:004012B3 imul ecx, esi ; ecx = (r1 * g + r2) * g .text:004012B6 add ecx, eax ; ecx = (r1 * g + r2) * g + r3 .text:004012B8 mov dword_40572C, esi .text:004012BE cmp ecx, 63627 ; ecx deve essere uguale a 63627 .text:004012C4 mov dword_405730, ecx .text:004012CA jz short loc_4012D6 ; salta se abbiamo passato il check .text:004012CC pop edi .text:004012CD pop esi .text:004012CE pop ebp .text:004012CF xor eax, eax .text:004012D1 pop ebx .text:004012D2 add esp, 2Ch .text:004012D5 retn .text:004012D6 ; --------------------------------------------------------------------------- .text:004012D6 .text:004012D6 loc_4012D6: ; CODE XREF: sub_401180+14Aj .text:004012D6 imul edx, ebx ; edx = r1 * h .text:004012D9 add edx, edi ; edx = r1 * h + r2 .text:004012DB xor ecx, ecx .text:004012DD imul edx, ebx ; edx = (r1 * h + r2) * h .text:004012E0 add edx, eax ; edx = (r1 * h + r2) * h + r3 .text:004012E2 mov eax, [esp+3Ch+i] ; eax = i .text:004012E6 cmp edx, eax ; edx deve essere uguale ad i .text:004012E8 pop edi .text:004012E9 setz cl
Bene bene... siamo nel cuore dell'algoritmo di check. Dobbiamo trovare un seriale che soddisfi queste due equazioni:
(r1 * g + r2) * g + r3 = 63627
(r1 * h + r2) * h + r3 = i
Osserviamo che, una volta trovati valori validi
per la prima equazione, possiamo facilmente ricavare valori validi per la
seconda. Il problema si riduce dunque a trovare una soluzione per la prima
equazione. Attenzione, però: per r1, r2 ed r3 dobbiamo rispettare le condizioni
viste prima riguardo agli elementi di G. Una volta trovata una soluzione
dell'equazione troveremo per r1, r2 ed r3 tre coppie (a,b), (c,d), (e,f) di
valori tali che G(a, b) = r1; G(c; d) = r2; G( e; f) = r3. Infine cercheremo una
combinazione di valori validi anche per h ed i.
Scriviamo dunque una prima bozza di bruteforcer, tenendo conto del fatto che
tutti gli elementi di G sono multipli di 3 e che:
a > 3
b > 3
per cui r1 = G(a, b) >= G(3, 3) >= 24
c > b+4 ovvero, per b = 4, c > 8
d > c ovvero, per c = 9, d > 9
per cui r2 = G(c, d) >= G(8, 9) >= 1824
e >= 2
f > 3
per cui r3 = G(e, f) >= G(2, 3) >= 6
g > 5
Ed ecco la prima bozza del brute, che andremo poi ad ottimizzare:
#include <stdio.h> void main() { int g; int r1, r2, r3; printf("GuZuRa's MatMe2 Bruteforcer - by Spider\n"); for (r1 = 24; r1 <= 100000; r1+=3) { for (r2 = 1824; r2 <= 100000; r2+=3) { for (r3 = 6; r3 <= 100000; r3+=3) { for (g = 6; g <= 100000; g++) { if ((r1 * g + r2) * g + r3 == 63627) { printf("r1 = %d; r2 = %d; r3=%d; g=%d\n",r1,r2,r3,g); } } } } } }
Il numero di combinazioni che dovremmo controllare in questo modo è chiaramente eccessivo (100000 è un numero alto, inserito perché non conosciamo i valori massimi di r1, r2, r3 e g). Possiamo tuttavia diminuire le combinazioni calcolando il valore massimo che possa avere ciascuna incognita, e questo basandoci sui valori minimi che invece già conosciamo. Se infatti diamo a 3 delle 4 incognite il valore minimo che esse possono avere e poi risolviamo l'equazione, otteniamo il valore massimo ottenibile dall'altra incognita. Per cui:
(maxr1 * 6 + 1824) * 6 + 6 = 63627
36*maxr1 + 10944 + 6 - 63627 = 0
36*maxr1=52677
24 <= r1 < 1464
=================================
(24 * 6 + maxr2) * 6 + 6 = 63627
864 + 6*maxr2 + 6 = 63627
maxr2 = 10460
1824 <= r2 < 10460
=================================
(24 * 6 + 1824) * 6 + maxr3 = 63627
11808 + maxr3 = 63627
maxr3 = 51820
6 <= r3 < 51820
=================================
(24 * g + 1824) * g + 6 = 63627
24g2 + 1824g - 63621 = 63627
g = (-1824 + SQR(18242 - 4 * 24
* (-63621)))/(2*24) = (-1824 + ~3072)/48 = 26
6 <= g < 26
Possiamo infine migliorare notevolmente il nostro bruteforcer calcolando, con la proprietà vista prima, la differenza tra 2 elementi consecutivi dell'insieme G per r1, r2 ed r3. Infatti:
r1 = G(a, b), e poiché b > 3 possiamo calcolare la differenza minima tra due possibili soluzioni come 3(3+1)/2 = 32 = 9
Analogamente
r2 = G(c, d), e poiché d > 9 possiamo calcolare la differenza minima tra due possibili soluzioni come 9(9+1)/2 = 35 = 243
r3 = G(e, f), e poiché f > 3 possiamo calcolare la differenza minima tra due possibili soluzioni come 3(3+1)/2 = 32 = 9
Con questi ultimi accorgimenti riduciamo il numero di combinazioni da controllare circa allo 0.13% rispetto a prima... niente male, no? :)
Ecco la versione definitiva del brute:
#include <stdio.h> void main() { int g; int r1, r2, r3; printf("GuZuRa's MatMe2 Bruteforcer - by Spider\n"); for (r1 = 24; r1 <= 1464; r1+=9) { for (r2 = 1824; r2 <= 10460; r2+=243) { for (r3 = 6; r3 <= 51820; r3+=9) { for (g = 6; g <= 26; g++) { if ((r1 * g + r2) * g + r3 == 63627) { printf("r1 = %d; r2 = %d; r3=%d; g=%d\n",r1,r2,r3,g); } } } } } }
Compiliamo, eseguiamo.... ed ecco una caterva di soluzioni valide... prendiamo
la prima, così restiamo sui numeri piccoli (per fermare il bruteforcer potete
utilizzare il tasto "pausa", oppure modificate il bruteforcer in modo
che si fermi dopo aver trovato qualche soluzione):
r1 = 24; r2 = 1824; r3=6027; g=24
Bene, sappiamo che r1 = 24 significa che a
= 4 e b = 4 (non possiamo ovviamente mettere a = 3 e b = 3 per le condizioni
viste prima). Sappiamo anche che r2 = 1824 significa che c = 8 e d = 9, ma
siccome d è dispari e noi vogliamo c > b+4, poniamo c = 8+1= 9 e d = 9 + 1 =
10, che non cambia il valore di r2. Non sappiamo però come scomporre 6027, e a
questo scopo scriviamo due righe di codice in C:
#include <stdio.h> int G(int x, int y) { while (y) { if (y%2) x *= 3; else x--; y--; } return x; } void main() { int x, y; for (x = 1; x < 1000;x++) { for (y = 1; y < 1000;y++) { if (G(x,y) == 6027) printf("x = %d; y = %d\n", x, y); } } }
Compiliamo ed eseguiamo, e dopo una manciata di secondi il programma ci comunica le due soluzioni trovate:
x = 670; y = 3
x = 671; y = 4
Ovviamente prendiamo in considerazione solo
la seconda, dato che vogliamo f > 3.
Sappiamo quindi che:
a = 4
b = 4
c = 9
d = 10
e = 671
f = 4
g = 24
Adesso dobbiamo trovare dei valori di h ed i che soddisfino l'equazione vista
prima:
(r1 * h + r2) * h + r3 = i
Per far ciò assegniamo un valore qualunque ad h (4, per restare sui numeri piccoli), e calcoliamo i come:
i = (r1 * h + r2) * h + r3 = (24 * 4 + 1824) * 4 + 6027 = 13707
ed ecco il serial completo:
4 4 9 10 671 4 24 4 13707
Lo inseriamo nella editbox del MatMe2 ed ecco
finalmente comparire la messagebox di congratulazioni :)
Spider
Note finali |
Saluti a GuZuRa che ha fatto il crackme e che ha distrutto il mio Pythagoras in
pochi nanosecondi :D
Disclaimer |
Vorrei ricordare che il software va comprato e non rubato, dovete inviare a GuZuRa i soldi necessari per la registrazione del crackme, così poi io e GuZuRa dividiamo e diventiamo ricchi.