Knight's crackme #2

Data

by Spider

 

6 Luglio 2005

UIC's Home Page

Published by Quequero


Grandissima cosa gli standard, ognuno dovrebbe avere il suo.

Grazie spi!

Perché i programmatori fanno confusione tra Natale e Halloween?
Perché 25 DEC = 31 OCT!

Signori si nasce, e io, modestamente, lo nacqui - Totò

Home page: http://bigspider.has.it o http://bigspider.cjb.net
E-mail: spider_xx87 (AT) hotmail (DOT) com
Nick, canale IRC frequentato: ^Spider^ su irc.azzurra.org
canali #crack-it e #asm

C'è vero progresso solo quando i vantaggi di una nuova tecnologia diventano per tutti - Henry Ford

Difficoltà

( )NewBies (X)Intermedio ( )Avanzato ( )Master

 

Scarica qui l'allegato con il keygen (sorgenti compresi).


Knight's crackme #2

Written by Spider

Introduzione

In questo tutorial vedremo come risolvere un crackme di Knight. Vediamo le regole:

Rulez:
Defeat it, write keygen working with all names and solution 
explaining how u did it and submit it at crackmes.de.
NO PATCHING ALLOWED!!!

Note:
I'm very proud to present you my second crackme.
Somebody claimed that first one was too easy. Check this one ;)
Actually algo is weak, but i think many of u won't manage to find
serial checking routine. Hope u'll see something new. Enjoy!

Dopo aver analizzato tutto il codice, il keygenning sarà un semplice esercizio di coding. 

Tools usati

Tools usati:

- IDA Pro 4.7.0
- Resource Hacker 3.4 (anche altri editor vanno bene)
- Microsoft Spy++
- Un debugger

Si presuppone nel lettore una conoscenza almeno di base dell'utilizzo dei tools elencati (in particolare, rinominerò alcune locazioni con IDA... Per evitare confusione, i nomi che ho dato io saranno in questo colore, mentre i commenti al codice saranno così).

Occhio al debugger: se ne può anche fare a meno, ma se usate OllyDbg potreste avere problemi con qualche Anti-OllyDbg contenuto nel crackme, mentre con softice non ci sono questi fastidi.

URL o FTP del programma

http://www.crackmes.de/users/knight/knights_crackme_2/

Notizie sul programma

Come avete capito è un crackme nome/serial... C'è altro da dire?? :-)

Essay

Nel readme.txt, l'autore ci dice "i think many of u won't manage to find serial checking routine", pertanto possiamo aspettarci di dover cercare qualche via più insolita per trovare la routine di check. Ma per esperienza so che in un programma è più difficile nascondere piuttosto che trovare ciò che è stato nascosto. Ovviamente (tentar non nuoce) val la pena di provare i soliti breakpoint su GetWindowTextA e GetDlgItemTextA, ma niente... Peraltro, quando clicchiamo su Check non viene visualizzata una MessageBox di errore, ma viene solo cambiato il testo della label in basso.

Allora IDA :-). Cercheremo di trovare il codice che gestisce l'evento "click su Check". Disassembliamo il crackme, e partiamo proprio dall'inizio, da WinMain. Poiché ci servirà dopo, prendiamo Resource Hacker per vedere gli ID dei controlli sulla finestra principale:

101 DIALOGEX 0, 0, 167, 81
STYLE DS_FIXEDSYS | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION "Knight's Crackme #2"
LANGUAGE LANG_LITHUANIAN, 0x1
FONT 8, "MS Shell Dlg", FW_NORMAL, FALSE, 1
{
   CONTROL "Check", 1004, BUTTON, BS_DEFPUSHBUTTON | BS_CENTER | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 7, 60, 50, 14 , 0x00020000
   CONTROL "Exit", 1005, BUTTON, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 109, 60, 50, 14 , 0x00020000
   CONTROL "", 1001, EDIT, ES_CENTER | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 36, 7, 124, 12 
   CONTROL "", 1002, EDIT, ES_CENTER | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP, 36, 25, 124, 12 
   CONTROL "Name:", -1, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 7, 11, 22, 8 
   CONTROL "Serial:", -1, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 7, 29, 22, 8 
   CONTROL "About", 1003, BUTTON, BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 58, 60, 50, 14 , 0x00020000
   CONTROL "", 1007, EDIT, ES_CENTER | ES_AUTOHSCROLL | WS_CHILD | WS_VISIBLE | WS_DISABLED | WS_BORDER | WS_TABSTOP, 36, 43, 124, 12 
   CONTROL "Status:", -1, STATIC, SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 7, 47, 22, 8 
}

Vediamo che il pulsante Check ha ID 1004, mentre le EditBox del nome e del serial hanno rispettivamente id 1001 e 1002, mentre è 1007 quello dell'edit Status. Veniamo al codice:

.text:00401469 ; __stdcall WinMain(x,x,x,x)
.text:00401469 _WinMain@16     proc near               ; CODE XREF: start+186p
.text:00401469
.text:00401469 hInstance       = dword ptr  4
.text:00401469
.text:00401469                 push    offset TopLevelExceptionFilter ; lpTopLevelExceptionFilter
.text:0040146E                 call    ds:SetUnhandledExceptionFilter
.text:00401474                 push    0               ; dwInitParam
.text:00401476                 push    offset sub_401200  ; lpDialogFunc
.text:0040147B                 push    0               ; hWndParent
.text:0040147D                 push    65h             ; lpTemplateName
.text:0040147F                 push    [esp+10h+hInstance] ; hInstance
.text:00401483                 call    ds:DialogBoxParamA ; Create a modal dialog box from a
.text:00401483                                         ; dialog box template resource
.text:00401489                 retn    10h
.text:00401489 _WinMain@16     endp

Qui vediamo che viene installato un Exception Handler; se non sapete cos'è non è un problema, non ci serve, <messaggio promozionale>ma vi consiglio di andarvi a leggere il tutorial di Quequero</messaggio promozionale>. :-P Poche righe dopo viene chiamata l'API DialogBoxParamA che crea e visualizza la finestra; la Dialog Procedure che gestisce i messaggi è la sub_401200, che con un sovrumano sforzo di fantasia rinominiamo DlgProc:

.text:00401200 ; BOOL __stdcall DlgProc(HWND,UINT,WPARAM,LPARAM)
.text:00401200 DlgProc         proc near               ; CODE XREF: sub_409000+43p
.text:00401200                                         ; sub_409000+54p ...
.text:00401200
.text:00401200 hDlg            = dword ptr  8
.text:00401200 arg_4           = dword ptr  0Ch
.text:00401200 arg_8           = word ptr  10h
.text:00401200
.text:00401200                 push    ebp
.text:00401201                 mov     ebp, esp
.text:00401203                 mov     eax, off_408048
.text:00401208                 push    esi
.text:00401209                 xor     esi, esi
.text:0040120B                 cmp     byte ptr [eax], 14h
.text:0040120E                 jnz     short loc_401216
.text:00401210                 mov     off_408048, esi
.text:00401216
.text:00401216 loc_401216:                             ; CODE XREF: DlgProc+Ej
.text:00401216                 movzx   eax, [ebp+arg_8]
.text:0040121A                 mov     ecx, [ebp+arg_4] ;Mette in ecx il codice dell'evento
.text:0040121D                 mov     edx, eax
.text:0040121F                 xor     edx, ecx
.text:00401221                 cmp     edx, 2FDh
.text:00401227                 mov     byte ptr [esi], 1
.text:0040122A                 jnz     short loc_401233
.text:0040122C                 mov     byte ptr unk_408372, 1
.text:00401233
.text:00401233 loc_401233:                             ; CODE XREF: DlgProc+2Aj
.text:00401233                 sub     ecx, 10h
.text:00401236                 jz      loc_4012CC
.text:0040123C                 sub     ecx, 100h
.text:00401242                 jz      short loc_401299
.text:00401244                 dec     ecx
.text:00401245                 jnz     loc_4012D6
.text:0040124B                 sub     eax, 3EBh        ;3EBh = 1003
.text:00401250                 jz      short loc_40127B ;salta se il controllo clickato è il 1003
.text:00401252                 dec     eax
.text:00401253                 jz      short Check_Clicked ;salta se  è il 1004 (cioè Check)
.text:00401255                 dec     eax
.text:00401256                 jnz     short loc_401294 ;non salta se è il 1005
.text:00401258                 push    esi             ; nResult
.text:00401259                 push    [ebp+hDlg]      ; hDlg
.text:0040125C                 call    ds:EndDialog
.text:00401262                 jmp     short loc_401294
.text:00401264 ; ---------------------------------------------------------------------------
.text:00401264
.text:00401264 Check_Clicked:                          ; CODE XREF: DlgProc+53j
.text:00401264                 push    3EAh            ; nIDDlgItem ; 3EAh = 1002, l'edit Serial
.text:00401269                 push    [ebp+hDlg]      ; hDlg
.text:0040126C                 call    ds:GetDlgItem
.text:00401272                 push    eax             ; hWnd
.text:00401273                 call    ds:SetFocus
.text:00401279                 jmp     short loc_401294
.text:0040127B ; ---------------------------------------------------------------------------
.text:0040127B
.
.[[CUT]]
.
.text:00401294
.text:00401294 loc_401294:                             ; CODE XREF: DlgProc+56j
.text:00401294                                         ; DlgProc+62j ...
.text:00401294                 xor     eax, eax
.text:00401296                 inc     eax
.text:00401297                 jmp     short loc_4012D8
.text:00401299 ; ---------------------------------------------------------------------------
.text:00401299
.
.[[CUT]]
.
.text:004012D8
.text:004012D8 loc_4012D8:                             ; CODE XREF: DlgProc+97j
.text:004012D8                 pop     esi
.text:004012D9                 pop     ebp
.text:004012DA                 retn    10h
.text:004012DA DlgProc         endp

Per brevità ho tagliato un po' di cose inutili, però quello che vediamo è che se viene clickato Check, l'unica cosa che il programma fa è di chiamare SetFocus per dare il focus all'editbox del Serial (infatti vediamo che il cursore si sposta lì). Tutto qui? Ovviamente no, altrimenti il programma non avrebbe modo di controllare il serial; in qualche modo ciò deve innescare qualcos'altro. vengono chiamate due API: GetDlgItem non ci interessa, si limita solo a ritornare dati (un handle). Perciò il colpevole deve essere SetFocus. Guardiamo in un'API reference per vedere se ha qualche particolarità utile; e la troviamo:

"The SetFocus function sends a WM_KILLFOCUS message to the window that loses the keyboard focus and a WM_SETFOCUS message to the window that receives the keyboard focus"

Perciò vengono mandati due messaggi, WM_KILLFOCUS a quello che perde il focus e WM_SETFOCUS a quello che l'acquista, cioè la casella di testo del Serial. Noi qui gestiamo solo i messaggi della dialog, perciò, per intercettare i messaggi dell'editbox, deve essere stata subclassata. Ci viene in aiuto Microsoft Spy++: apriamo il crackme e Spy++, clicchiamo su Search --> Find Window... e trasciniamo il Finder Tool sul pulsante Check del crackme. Clicchiamo OK e facciamo doppio click sulla riga in cui il programma si è posizionato (corrispondente al pulsante). Compare una finestra con le proprietà del pulsante, tra cui leggiamo:

Window Proc:       77D2F422

Da voi potrebbe essere diverso, ma quello che conta è che è un indirizzo fuori dal nostro programma, perciò nulla di fatto. Se ripetiamo la procedura con una qualunque delle editbox, troveremo invece:

Window Proc:       00409000 (subclassed)

Bingo! Chissà perché le editbox sono subclassate... forse per gestire il messaggio WM_SETFOCUS??? :-)

Torniamo ad IDA:

.cdata:00409000 unk_409000      db  83h ; â    ; DATA XREF: TopLevelExceptionFilter:loc_4012F5w
.cdata:00409001                 db  7Dh ; }
.cdata:00409002                 db  26h ; &
.cdata:00409003                 db  0Bh
.cdata:00409004                 db    3
.cdata:00409005                 db  0Ah
.cdata:00409006                 db  83h ; â
.
.
.

Il codice non è stato analizzato da IDA. Ci posizioniamo su 00409000 e clicchiamo C per farlo disassemblare:

.cdata:00409000 loc_409000:                             ; DATA XREF: TopLevelExceptionFilter:loc_4012F5w
.cdata:00409000                 cmp     dword ptr [ebp+26h], 0Bh
.cdata:00409004                 add     ecx, [edx]
.cdata:00409006                 or      dword ptr [ecx+ecx-78A7F5F7h], 51h
.cdata:0040900E                 sub     al, [edi]
.cdata:00409010                 sub     ecx, [esi+ebx]
.cdata:00409013                 nop
.cdata:00409014                 push    esp
.cdata:00409015                 adc     eax, 18619219h
.cdata:0040901A                 sbb     bl, [ebx]
.cdata:0040901C                 pushf
.cdata:0040901D                 and     [esp+ebx*4+60h], ch

Brutta cosa, era quasi meglio prima! :) Qui abbiamo solo istruzioni senza senso. Ma questo DEVE essere codice, se è una Window Procedure, perciò è segno che è stato criptato. Fortunatamente IDA riporta le XREFs, perciò Undefiniamo di nuovo questo codice premendo U ed andiamo a perlustrare all'indirizzo 004012F5:

.text:004012DD ; LONG __stdcall TopLevelExceptionFilter(struct _EXCEPTION_POINTERS *)
.text:004012DD TopLevelExceptionFilter proc near       ; DATA XREF: WinMain(x,x,x,x)o
.text:004012DD
.text:004012DD arg_0           = dword ptr  10h
.text:004012DD
.text:004012DD                 push    ebx
.text:004012DE                 push    esi
.text:004012DF                 push    edi
.text:004012E0                 mov     edi, [esp+arg_0]
.text:004012E4                 mov     eax, [edi]
.text:004012E6                 mov     eax, [eax]
.text:004012E8                 cmp     eax, 0C0000094h
.text:004012ED                 jnz     loc_40141B
.text:004012F3                 xor     eax, eax
.text:004012F5
.text:004012F5 loc_4012F5:                             ; CODE XREF: TopLevelExceptionFilter+24j
.text:004012F5                 xor     byte ptr ds:loc_409000[eax], al
.text:004012FB                 inc     eax
.text:004012FC                 cmp     eax, 19Ch
.text:00401301                 jl      short loc_4012F5
.
.
.

Siamo nell'Exception Handler, che sarà stato chiamato da qualche eccezione precedente (se non ve ne eravate accorti da soli, andate a rivedere la riga 00401227...). In particolare qui abbiamo il ciclo di decrypt, basato su un semplice XOR; scriviamo il solito script IDC per leggere il codice in chiaro:

static decrypt()
{
	auto xorvalue, i;
	xorvalue = 0;
	for (i = 0; i < 0x19C; i++)
	{
		PatchByte(0x409000+i, Byte(0x409000+i)^xorvalue);
		xorvalue = (xorvalue+1)&0xFF;
	}
}

Salviamo come decrypt.idc, quindi lo apriamo da File --> IDC File...; comparirà una piccola barra degli strumenti che ci consente di compilare lo script. Fatto ciò, clicchiamo File --> IDC Command... ed inseriamo il comando "decrypt();" (senza apici) e clicchiamo OK. Ritorniamo all'indirizzo 00409000 e pigiamo C per disassemblare ed ecco stavolta il codice in chiaro, che adesso analizzeremo passo per passo. Per comodità, andiamo all'indirizzo 00409000 e clicchiamo Edit --> Functions --> Create function, così IDA riconoscerà lo stackframe.

.cdata:00409000                 cmp     [esp+arg_4], 7
.cdata:00409005                 jnz     loc_409196

[esp+4]] contiene il messaggio, e guarda caso 0x7 è uguale a WM_SETFOCUS; è la conferma che siamo nel posto giusto.

.cdata:0040900B                 push    ebx
.cdata:0040900C                 mov     ebx, [esp+4+arg_0]
.cdata:00409010                 cmp     ebx, dword_40830C
.cdata:00409016                 jnz     loc_409195
.cdata:0040901C                 cmp     byte_408372, 0
.cdata:00409023                 jz      loc_409195

Se guardiamo le XREFs di dword_40830C, l'unica in scrittura è qua:

.text:00401388                 push    3EAh            ; nIDDlgItem
.text:0040138D                 push    hDlg            ; hDlg
.text:00401393                 mov     dword_408310, eax
.text:00401398                 call    esi ; GetDlgItem
.text:0040139A                 push    dword_40808C
.text:004013A0                 mov     dword_40830C, eax

Perciò dword_40830C contiene l'handle del controllo che ha ID 0x3EA = 1002, ossia quello del serial. Il programma si assicura che il messaggio WM_SETFOCUS sia stato mandato all'edit del seriale (infatti la window procedure è condivisa tra 3 editbox). Per quanto riguarda il byte_408372, se vi ricordate (e comunque potete vederlo seguendo le XREFs con IDA) viene settato ad 1 dentro DlgProc; ciò serve al programma per distinguere il messaggio WM_SETFOCUS generato tramite l'API SetFocus da un eventuale messaggio generato, per esempio, con un click dell'utente.

.cdata:00409029                 push    ebp
.cdata:0040902A                 push    esi
.cdata:0040902B                 push    edi
.cdata:0040902C                 mov     edi, offset dword_408334
.cdata:00409031                 push    edi             ; LPARAM
.cdata:00409032                 push    20h             ; WPARAM
.cdata:00409034                 push    0Dh             ; UINT
.cdata:00409036                 push    dword_408328    ; LPARAM
.cdata:0040903C                 mov     byte_408372, 0
.cdata:00409043                 call    off_408048
.cdata:00409049                 mov     esi, offset unk_408354
.cdata:0040904E                 push    esi             ; WPARAM
.cdata:0040904F                 push    1Eh             ; UINT
.cdata:00409051                 push    0Dh             ; HWND
.cdata:00409053                 push    ebx             ; char *
.cdata:00409054                 call    off_408048

off_408048 punta a sub_401200, che ha tutto l'aspetto di una window procedure. D'altronde se cerchiamo la XREF in scrittura alla dword_408328 arriviamo qui:

.text:00401369                 push    3E9h            ; nIDDlgItem
.text:0040136E                 push    hDlg            ; hDlg
.text:00401374                 call    esi ; GetDlgItem
.text:00401376                 push    3EFh            ; nIDDlgItem
.text:0040137B                 push    hDlg            ; hDlg
.text:00401381                 mov     dword_408328, eax

0x3E9 = 1001, dunque dword_408328 contiene l'hwnd dell'edit Name; d'altronde WM_GETTEXT = 0x0D; perciò capiamo subito che le righe 00409031-00409043 prelevano il nome inserito (con un limite di 0x20 = 32 caratteri) e lo salvano all'indirizzo 00408334. Allo stesso modo, le righe 00409049-00409056 prelevano il Serial (limite di 0x1E = 30 caratteri) e lo salvano all'indirizzo 00408354. Con l'ennesimo immane sforzo di fantasia rinominiamo le due locazione come Nome e Serial, e facciamo in modo che IDA li consideri array di bytes, secondo le dimensioni limite.

.cdata:0040905A                 push    edi             ; char *
.cdata:0040905B                 call    off_408044
.cdata:00409061                 push    esi             ; char *
.cdata:00409062                 mov     bl, al
.cdata:00409064                 call    off_408044
.cdata:0040906A                 cmp     bl, 5
.cdata:0040906D                 pop     ecx
.cdata:0040906E                 pop     ecx
.cdata:0040906F                 jb      loc_40914F

off_408044 punta a strlen; quindi mette in bl la lunghezza del nome e in al quella del serial; quella del nome deve essere almeno 5 caratteri.

.cdata:00409075                 shl     al, 3
.cdata:00409078                 add     al, 7
.cdata:0040907A                 or      al, 10h
.cdata:0040907C                 not     al
.cdata:0040907E                 test    al, al
.cdata:00409080                 jnz     loc_40914F

Alla fine di questo codice, per passare il controllo, deve essere al = 0; posto al = x, la sequenza richiede che NOT((x*8 + 7) OR 16) = 0. Negando entrambi i membri (e ricordando che 2 NOT si annullano e che NOT 0 = 255, lavorando ad 8 bit), si ottiene:

(x*8 + 7) OR 16 = 255

Poiché 1610 = 10002, allora un OR con 16 può lasciare invariato o aggiungere 16. Perciò vale una delle due:

x*8 + 7 = 255           e           x*8 + 7 + 16 = 255

Risolvendo le due equazioni si ottiene x = 29 o x = 31; ma la lunghezza del seriale è minore 30 caratteri, perciò solo x = 29 è accettabile, e quindi il serial è esattamente 29 caratteri.

.cdata:00409086                 call    off_408038
.cdata:0040908C                 test    eax, eax
.cdata:0040908E                 jz      loc_40914F
.cdata:00409094                 call    off_40803C
.cdata:0040909A                 test    eax, eax
.cdata:0040909C                 jz      loc_40914F

Ci sono due call che devono ritornare non-zero per passare il check. Vediamo cosa c'è nella prima:

.text:00401037 sub_401037      proc near               ; CODE XREF: sub_409000+86p
.text:00401037                                         ; sub_409000+157p ...
.text:00401037
.text:00401037 var_4           = dword ptr -4
.text:00401037
.text:00401037                 push    ebp
.text:00401038                 mov     ebp, esp
.text:0040103A                 push    ecx
.text:0040103B                 push    ebx
.text:0040103C                 xor     ebx, ebx
.text:0040103E                 xor     ecx, ecx
.text:00401040                 mov     bl, Serial+5
.text:00401046                 mov     cl, Serial+0Bh
.text:0040104C                 shl     ebx, 4
.text:0040104F                 or      cl, bl
.text:00401051                 xor     cl, bh
.text:00401053                 not     cl
.text:00401055                 test    cl, cl
.text:00401057                 jnz     short loc_401073
.text:00401059                 mov     bl, Serial+11h
.text:0040105F                 mov     cl, Serial+17h
.text:00401065                 shl     ecx, 4
.text:00401068                 xor     ch, bl
.text:0040106A                 or      ch, cl
.text:0040106C                 inc     ch
.text:0040106E                 setz    al
.text:00401071                 jmp     short loc_401075
.text:00401073 ; ---------------------------------------------------------------------------
.text:00401073
.text:00401073 loc_401073:                             ; CODE XREF: sub_401037+20j
.text:00401073                 xor     eax, eax
.text:00401075
.text:00401075 loc_401075:                             ; CODE XREF: sub_401037+3Aj
.text:00401075                 mov     [ebp+var_4], eax
.text:00401078                 mov     eax, [ebp+var_4]
.text:0040107B                 pop     ebx
.text:0040107C                 leave
.text:0040107D                 retn
.text:0040107D sub_401037      endp

Il programma prende il 6° carattere e l'12° ed effettua un controllo incrociato, quindi prende il 18° ed il 24° ed effettua un piccolo controllo incrociato simile. Come vedete il seriale è quindi della forma xxxxxXxxxxxXxxxxxXxxxxxXxxxxx; inizialmente, provando un po' con carta e penna, avevo trovato che mettendo 'K' al posto delle X, questo primo controllo viene passato. Ma c'era un problema: le 'K' sono ingombranti, e lo spazio è appena sufficiente per inserire il seriale. Mettendo 'K', spesso non riuscivo più ad inserire tutti i 29 caratteri del serial. Ma come vedete, le X sono messe ad intervalli regolari, perciò possiamo aspettarci che sia considerato un separatore. Qual è il separatore più comune nei seriali? Ovviamente il trattino '-'; e provando vediamo che anche lui passa il check :) perciò il serial è della forma:

xxxxx-xxxxx-xxxxx-xxxxx-xxxxx

Vediamo la seconda call:

.text:0040107E sub_40107E      proc near               ; CODE XREF: sub_409000+86p
.text:0040107E                                         ; sub_409000+94p ...
.text:0040107E                 push    esi
.text:0040107F                 push    edi
.text:00401080                 mov     edi, offset Serial       ;edi punta al serial
.text:00401085                 mov     ecx, offset dword_408314 ;ecx ad una dword dove sarà
                                                                ;salvato il risultato
.text:0040108A
.text:0040108A loc_40108A:                             ; CODE XREF: sub_40107E+53j
.text:0040108A                 and     dword ptr [ecx], 0 ;azzera il risultato parziale
.text:0040108D                 xor     esi, esi           ;esi sarà usato come contatore
.text:0040108F
.text:0040108F loc_40108F:                             ; CODE XREF: sub_40107E+45j
.text:0040108F                 mov     eax, [ecx]       ;prende la dword del risultato parziale
.text:00401091                 mov     dl, [edi+esi]    ;preleva un carattere del serial
.text:00401094                 shl     eax, 4           ;shifta a sinistra di 4 bit (che equivale
                                                        ;ad aggiungere uno 0 esadecimale)
.text:00401097                 cmp     dl, 30h
.text:0040109A                 mov     [ecx], eax
.text:0040109C                 jb      short loc_4010AC
.text:0040109E                 cmp     dl, 39h
.text:004010A1                 ja      short loc_4010AC
.text:004010A3                 movzx   edx, dl            ;è un numero
.text:004010A6                 lea     eax, [edx+eax-30h] ;sottrae 30h e aggiunge al risultato
.text:004010AA                 jmp     short loc_4010BD
.text:004010AC ; ---------------------------------------------------------------------------
.text:004010AC
.text:004010AC loc_4010AC:                             ; CODE XREF: sub_40107E+1Ej
.text:004010AC                                         ; sub_40107E+23j
.text:004010AC                 cmp     dl, 41h
.text:004010AF                 jb      short loc_4010D9
.text:004010B1                 cmp     dl, 46h
.text:004010B4                 ja      short loc_4010D9 ;salta se non è una lettera compresa fra A ed F
                                                        ;(e allora il seriale è errato)
.text:004010B6                 movzx   edx, dl
.text:004010B9                 lea     eax, [edx+eax-37h] ;sottrae 37h e aggiunge al risultato
.text:004010BD
.text:004010BD loc_4010BD:                             ; CODE XREF: sub_40107E+2Cj
.text:004010BD                 inc     esi              ;incrementa il contatore
.text:004010BE                 cmp     esi, 5           ;siamo arrivati a 5 caratteri?...
.text:004010C1                 mov     [ecx], eax       ;salva il risultato
.text:004010C3                 jl      short loc_40108F ;...no? ripeti
.text:004010C5                 add     ecx, 4           ;andiamo alla dword successiva
.text:004010C8                 add     edi, 6           ;vai 6 caratteri più avanti
.text:004010CB                 cmp     ecx, offset dword_408328 ;abbiamo già salvato 5 dword??...
.text:004010D1                 jl      short loc_40108A         ;...no? ripeti
.text:004010D3                 xor     eax, eax
.text:004010D5                 inc     eax              ;eax = 1 vuol dire "tutto OK" ;)
.text:004010D6
.text:004010D6 loc_4010D6:                             ; CODE XREF: sub_40107E+5Dj
.text:004010D6                 pop     edi
.text:004010D7                 pop     esi
.text:004010D8                 retn

Il programma prende ogni blocco di 5 caratteri del seriale, verifica che ci siano solo numeri e lettere A, B, C, D, E, F, quindi converte la stringa in numero esadecimale. ecx, nelle varie iterazioni del ciclo, punterà quindi a dword_408314, dword_408318, dword_40831C, dword_408320, dword_408324. Queste dword conterranno il nostro seriale in formato numerico (le nostre incognite), perciò le rinominiamo con le ultime lettere dell'alfabeto, nell'ordine v, w, x, y, z.

Andiamo avanti con il codice:

.cdata:004090A2                 movzx   eax, bl
.cdata:004090A5                 push    eax             ; char *
.cdata:004090A6                 call    off_408040

Se vi ricordate, bl conteneva la lunghezza del nome, che viene passata come parametro alla call. Vediamola:

.text:004010DD sub_4010DD      proc near               ; CODE XREF: sub_409000+86p
.text:004010DD                                         ; sub_409000+94p ...
.text:004010DD
.text:004010DD var_4           = dword ptr -4
.text:004010DD arg_0           = dword ptr  8
.text:004010DD
.text:004010DD                 push    ebp
.text:004010DE                 mov     ebp, esp
.text:004010E0                 push    ecx
.text:004010E1                 and     [ebp+var_4], 0
.text:004010E5                 push    ebx
.text:004010E6                 mov     ebx, [ebp+arg_0]
.text:004010E9                 mov     ecx, ebx
.text:004010EB                 sar     ecx, 1
.text:004010ED                 test    bl, 1
.text:004010F0                 jz      short loc_4010FB
.text:004010F2                 movzx   eax, Nome[ecx]
.text:004010F9                 jmp     short loc_40110C
.text:004010FB ; ---------------------------------------------------------------------------
.text:004010FB
.text:004010FB loc_4010FB:                             ; CODE XREF: sub_4010DD+13j
.text:004010FB                 lea     edx, Nome[ecx]
.text:00401101                 movzx   eax, byte ptr [edx-1]
.text:00401105                 movzx   edx, byte ptr [edx]
.text:00401108                 add     eax, edx
.text:0040110A                 sar     eax, 1
.text:0040110C
.text:0040110C loc_40110C:                             ; CODE XREF: sub_4010DD+1Cj
.text:0040110C                 mov     dword_408308, eax
.text:00401111                 mov     dword_408304, eax
.text:00401116                 lea     eax, [ecx+1]
.text:00401119                 jmp     short loc_401126
.text:0040111B ; ---------------------------------------------------------------------------
.text:0040111B
.text:0040111B loc_40111B:                             ; CODE XREF: sub_4010DD+4Bj
.text:0040111B                 movzx   ecx, Nome[eax]
.text:00401122                 add     [ebp+var_4], ecx
.text:00401125                 inc     eax
.text:00401126
.text:00401126 loc_401126:                             ; CODE XREF: sub_4010DD+3Cj
.text:00401126                 cmp     eax, ebx
.text:00401128                 jl      short loc_40111B
.text:0040112A                 mov     eax, [ebp+var_4]
.text:0040112D                 shl     eax, 0Ch
.text:00401130                 mov     ecx, eax
.text:00401132                 shr     ecx, 10h
.text:00401135                 or      cx, ax
.text:00401138                 mov     eax, dword_408304
.text:0040113D                 and     eax, 1
.text:00401140                 xor     ecx, eax
.text:00401142                 mov     [ebp+var_4], ecx
.text:00401145                 mov     eax, [ebp+var_4]
.text:00401148                 mov     ecx, dword_408308
.text:0040114E                 add     dword_408304, eax
.text:00401154                 imul    ecx, eax
.text:00401157                 mov     dword_408308, ecx
.text:0040115D                 mov     eax, [ebp+arg_0]
.text:00401160                 shr     eax, 1
.text:00401162                 mov     ecx, [ebp+arg_0]
.text:00401165                 and     ecx, 1
.text:00401168                 not     ecx
.text:0040116A                 and     ecx, 1
.text:0040116D                 sub     eax, ecx
.text:0040116F                 mov     [ebp+arg_0], eax
.text:00401172                 and     [ebp+var_4], 0
.text:00401176                 xor     eax, eax
.text:00401178                 cmp     [ebp+arg_0], eax
.text:0040117B                 pop     ebx
.text:0040117C                 jle     short loc_401191
.text:0040117E
.text:0040117E loc_40117E:                             ; CODE XREF: sub_4010DD+B2j
.text:0040117E                 movzx   ecx, Nome[eax]
.text:00401185                 xor     ecx, 20h
.text:00401188                 add     [ebp+var_4], ecx
.text:0040118B                 inc     eax
.text:0040118C                 cmp     eax, [ebp+arg_0]
.text:0040118F                 jl      short loc_40117E
.text:00401191
.text:00401191 loc_401191:                             ; CODE XREF: sub_4010DD+9Fj
.text:00401191                 mov     eax, [ebp+var_4]
.text:00401194                 shl     eax, 0Ch
.text:00401197                 mov     ecx, eax
.text:00401199                 shr     ecx, 10h
.text:0040119C                 or      cx, ax
.text:0040119F                 mov     eax, ecx
.text:004011A1                 mov     ecx, dword_408304
.text:004011A7                 mul     ecx
.text:004011A9                 mov     edx, dword_408308
.text:004011AF                 xor     eax, ecx
.text:004011B1                 xor     ecx, eax
.text:004011B3                 xor     edx, eax
.text:004011B5                 xor     edx, ecx
.text:004011B7                 xor     ecx, edx
.text:004011B9                 xor     edx, ecx
.text:004011BB                 xor     eax, ecx
.text:004011BD                 xor     ecx, eax
.text:004011BF                 xor     ecx, edx
.text:004011C1                 mov     dword_408300, edx
.text:004011C7                 mov     dword_408304, ecx
.text:004011CD                 mov     dword_408308, eax
.text:004011D2                 leave
.text:004011D3                 retn
.text:004011D3 sub_4010DD      endp

Studiatevelo voi questo codice, io mi rifiuto :-Þ. In effetti è troppo lungo e noioso, ma a pensarci bene non ci serve a nulla analizzarlo: fa solo dei calcoli sul nome, e i risultati vengono salvati nelle 3 dword 408300, 408304 e 408308. Ci servono solo i risultati, e nel caso volessimo scrivere il keygen è sufficiente copiare ed incollare il codice direttamenter dal disassemblato. Le uniche cose interessanti da notare (che si possono vedere con una fase di debugging oppure analizzando l'ultima parte dell'algoritmo), sono il fatto che la dword_408304 è sempre della forma 0000xxxx, ed è un sottomultiplo della dword_408300. Per comodità, rinominiamo le tre dword rispettivamente come a, b, c (le ultime lettere le abbiamo usate per le incognite, le prime le usiamo per i dati noti :)). Per il nick "Spider" valgono a = 0x151AD564, b = 0x7073, c = 0x2CA52E.

Ci rimane l'ultimo blocco di codice, quello che contiene il controllo vero e proprio. Per semplicità, ho rinominato l'indirizzo 0040914F come beggar_off.

.cdata:004090AC                 mov     edx, v
.cdata:004090B2                 mov     ebp, z
.cdata:004090B8                 pop     ecx
.cdata:004090B9                 mov     ecx, w
.cdata:004090BF                 mov     esi, 0FFFh
.cdata:004090C4                 mov     eax, ecx        ; eax = w
.cdata:004090C6                 and     eax, esi        ; eax = ultime 3 cifre di w
.cdata:004090C8                 shl     eax, 0Ch        ; aggiunge 3 zeri ad eax
.cdata:004090CB                 xor     edx, eax        ; edx = v xor (risultato in eax)
.cdata:004090CD                 mov     eax, x          ; eax = x
.cdata:004090D2                 mov     edi, eax        ; edi = x
.cdata:004090D4                 and     edi, esi        ; edi = ultime 3 cifre di x
.cdata:004090D6                 mov     esi, c          ; esi = c
.cdata:004090DC                 shl     edi, 0Ch        ; aggiunge 3 zeri a edi
.cdata:004090DF                 xor     ebp, edi        ; ebp = z xor (risultato in edi)
.cdata:004090E1                 mov     edi, y          ; edi = y
.cdata:004090E7                 shr     eax, 0Ch        ; eax = prime 2 cifre di x
.cdata:004090EA                 imul    edi, eax        ; edi = y * (prime 2 cifre di x)
.cdata:004090ED                 shr     ecx, 0Ch        ; ecx = prime 2 cifre di w
.cdata:004090F0                 neg     edx             ; edx = -edx
.cdata:004090F2                 cmp     edi, esi        ; edi = y * (prime 2 cifre di x)
.cdata:004090F2                                         ; deve essere uguale a c
.cdata:004090F4                 mov     z, ebp          ; \
.cdata:004090FA                 mov     w, ecx          ;  |__ Non ci interessano perché
.cdata:00409100                 mov     x, eax          ;  |   non vengono più ripresi
.cdata:00409105                 mov     v, edx          ; /
.cdata:0040910B                 jnz     short beggar_off
.cdata:0040910D                 mov     edi, b          ; edi = b
.cdata:00409113                 mov     ebx, ecx        ; ebx = ecx = (prime due cifre di w)
.cdata:00409115                 sub     ebx, edi        ; ebx = (prime due cifre di w) - b
.cdata:00409117                 imul    ebx, ecx        ; moltiplica ebx per (prime due cifre di w)
.cdata:0040911A                 neg     esi             ; esi = -c
.cdata:0040911C                 cmp     ebx, esi        ; ebx deve essere uguale a -c
.cdata:0040911E                 jnz     short beggar_off
.cdata:00409120                 lea     esi, [edx+ebp]
.cdata:00409123                 imul    esi, edi
.cdata:00409126                 shr     esi, 1
.cdata:00409128                 cmp     esi, a          ; (edx + ebp) * b / 2 = a
.cdata:0040912E                 jnz     short beggar_off
.cdata:00409130                 mov     esi, y          ; esi = y
.cdata:00409136                 add     esi, eax        ; esi = y + (prime due cifre di x)
.cdata:00409138                 cmp     esi, edi        ; y + (prime 2 cifre di x) = b
.cdata:0040913A                 jnz     short beggar_off
.cdata:0040913C                 dec     edi             ; edi = b - 1
.cdata:0040913D                 imul    edi, ecx        ; edi = (b-1)*(prime 2 cifre di w)
.cdata:00409140                 add     edi, edx        ; edi = (b-1)*(prime 2 cifre di w) + edx
.cdata:00409142                 cmp     ebp, edi        ; (b-1)*(prime 2 cifre di w) + edx = ebp
.cdata:00409144                 jnz     short beggar_off
.cdata:00409146                 push    0Ah
.cdata:00409148                 mov     esi, offset unk_408070
.cdata:0040914D                 jmp     short loc_409156
.cdata:0040914F ; ---------------------------------------------------------------------------
.cdata:0040914F
.cdata:0040914F beggar_off:                             ; CODE XREF: sub_409000+6Fj

Possiamo vedere che ogni incognita è collegata alle altre da qualche relazione semplice (somme, moltiplicazioni, operazioni logiche). La matassa è un po' complicata, ma la debolezza è chiara: se troviamo anche una sola variabile, le altre seguiranno per passaggi successivi. Il mio punto di attacco è stato alla riga 4090F2. Il codice richiede che y * (prime due cifre di x) = c. Inoltre alla riga 409138 abbiamo: y + (prime 2 cifre di x) = b. Abbiamo 2 equazioni e 2 incognite. Mettendo a sistema otteniamo:

                  y  = (b + sqrt(b^2 - 4*c))/2
(prime 2 cifre di x) = (b - sqrt(b^2 - 4*c))/2

   oppure

(prime 2 cifre di x) = (b + sqrt(b^2 - 4*c))/2
                  y  = (b - sqrt(b^2 - 4*c))/2

Ovviamente se b + sqrt(b^2 - 4c) è maggiore di 0xFF (come risulterà nel caso del mio nick), solo la prima delle due soluzioni è valida, ma nell'eventuale keygen dobbiamo provarle entrambe (ed in effetti ho trovato alcuni nick in cui la prima soluzione fallirebbe e la seconda no). Per il mio seriale l'unica accettabile risulta (prime due cifre di x) = 102 = 0x66 e y = 28685 = 0x0700D. Il seriale comincia a prendere forma:

  v     w     x     y     z
xxxxx-xxxxx-66xxx-0700D-xxxxx

Possiamo trovare anche le prime due cifre di w: infatti indicando con w_ le prime due cifre di w, le righe 0040910D-40911E implicano:

w_^2 - b*w_ + c = 0

Risolvendo e prendendo solo la soluzione più piccola otteniamo:

w_ = (b - sqrt(b^2 - 4*c))/2

Ossia uguale alle prime 2 cifre di x. A questo punto abbiamo anche dati sufficienti per trovare edx ed ebp nelle righe 409120-409144. Abbiamo le seguenti 2 relazioni: 

(edx + ebp) * b / 2 = a
            e
(b-1)*w_ + edx = ebp

Risolvendo questo sistema in edx ed ebp (sapete come si fa, vero? ;)) otteniamo:

edx = (2*a - b*w_*(b - 1))/(2*b) = a/b - w_*(b - 1)/2
ebp = (2*a + b*w_*(b - 1))/(2*b) = a/b + w_*(b - 1)/2 

Per il mio nick i risultati vengono:

edx = -1455786 = 0xFFE9C956
ebp =  1480386 = 0x001696C2

Dalla riga 409138 otteniamo invece:

(prime 2 cifre di x) = b - y

E nel mio caso ottengo:

(prime 2 cifre di x) = 0x7073 - 0700D = 0x66

Quindi siamo qui:

  v     w     x     y     z
xxxxx-66xxx-66xxx-0700D-xxxxx

Ci manca solo di trovare alle ultime 3 cifre di w e  z insieme alle ultime 3 cifre si x (infatti i controlli sono incrociati). Osserviamo che conosciamo già il valore di edx dopo la riga 4090F0. Prima del neg era quindi edx = 1455786 = 0x37D0BB. Ma allora la riga 4090CB impone:

v xor (www000) = 0x1636AA

Dove www sono le ultime 3 cifre di w. Ma v ha 5 cifre, e il secondo membro 6, perciò la prima w deve essere uguale alla prima cifra del secondo membro:

v xor (1ww000) = 0x1636AA

A questo punto possiamo scegliere le altre 2 w a piacere e ottenere v di conseguenza; ad esempio, ponendo le ww a 00:

v = 0x1636AA xor 0x100000 = 0x636AA

E siamo quasi alla fine:

  v     w     x     y     z
636AA-66100-66xxx-0700D-xxxxx

Analogamente, il valore di ebp che abbiamo calcolato prima non viene mai modificato dopo la riga 4090DF, che implica:

z xor (xxx000) = 0x1696C2

Di nuovo possiamo uguagliare la sesta cifra e scegliere gli altri due a piacere (li metto anche a 0), ottenendo:

xxx = 0x100
z =
0x1696C2 xor  0x100000 = 0x696C2

E abbiamo quindi il seriale completo:

636AA-66100-66100-0700D-696C2

== Note sul keygenning ==

Pensavo di aver finito, ma durante la scrittura del keygen sono sorti alcuni problemi. Durante tutta la fase di reversing, assumevo sempre che a, b e c fossero generati in modo da garantire l'esistenza di un seriale valido, ma per certi nick, invece, alcune condizioni non si possono soddisfare. I problemi possono essere i seguenti:

edx = a/b - w_*(b - 1)/2;
ebp = a/b + w_*(b - 1)/2;


Qui può accadere che w_ e b - 1 siano entrambi dispari, perciò la divisione per 2 non viene intera e non si può trovare un valore valido per edx ed ebp. Inoltre, edx deve venire negativo, poiché dopo il neg edx di riga 4090F0 deve essere positivo, ma per alcuni nick, invece, viene un valore positivo. L'ultimo problema riguarda la riga 409128. Infatti il controllo è il seguente: (edx + ebp) * b / 2 == a. Ma edx e ebp sono in generale numeri grandicelli, e moltiplicandoli per b si corre il rischio di andare in overflow (ad esempio accade con il nick "Microsoft"). Comunque, se non si tralascia nulla, sono abbastanza pochi i nomi per cui non esiste un seriale valido (tra questi, "Quequero" :-D). Per le altre condizioni non ci dovrebbero essere problemi.

                                                                                                                 Spider

Note finali

Un saluto ad AndreaGeddon: non ho dimenticato la pazienza e la disponibilità con cui rispondevi alle mie (continue) e-mail quando ero ancora agli inizi.

Disclaimer

Vorrei ricordare che i crackme vanno crackati e non comprati, dovete registrare il vostro prodotto dopo aver capito la protezione. :P

Noi reversiamo perché è tanto tanto tanto divertente. :)