Knight's crackme #2 |
||
Data |
by Spider |
|
6 Luglio 2005 |
Published by Quequero |
|
|
Grazie spi! |
Perché i programmatori fanno confusione tra Natale e Halloween? |
Signori si nasce, e io, modestamente, lo nacqui - Totò |
|
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).
Introduzione |
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!
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 |
Notizie sul programma |
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
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. :)