KeygenMe 2 by no!se
(reversing completo e vari trucchi anti-debug...)

Data

by "Zero_G"

 

22/08/2004

UIC's Home Page

Published by Quequero


"Siamo fatti della stessa sostanza dei sogni,

 

Sempre impeccabile, non potrei aggiungere altro per te :)

e la nostra breve vita è circondata dal sonno..."

- W. Shakespeare -

 

homepage (in costruzione): http://zerog.altervista.org
E-mail: [email protected]
 

Difficoltà

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

 

Salve a tutti!
Ultimamente mi sono dato ai crackme... sarà perché quando tocco qualche programma commerciale comincio a temere la punizione dello zio Bill? UAHUAHUAH!! :-D


Introduzione

Questo è un piccolo crackme scritto in Delphi, ma la sua particolarità è che possiede alcuni trucchi per evitare di farsi debuggare facilmente... cercheremo di evitarli e poi analizzeremo l'algoritmo di generazione del seriale per costruire un keygen.

Tools usati

URL o FTP del programma

Notizie sul programma

a che servirà mai un crackme??  :-p

Essay

Per rispettare l'approccio da reverser pulito ed ordinato, diamo l'eseguibile in pasto al nostro caro PEiD che subito ci dirà che è compresso con UPX... no problema! scaricatelo da http://protools.anticrack.de e con un semplice "upx -d KeygenMe_#2-n0!se.exe" otterrete il file originale (vi consiglio di rinominarlo in qualcosa di più semplice tipo KeygenMe.exe). Facile per ora, no?
[Dopo lo scompattamento, PEiD ci dice che è scritto in Delphi, ma, se volete provare con DeDe, non otterrete nulla per il motivo che fra poco vi spiegherò...]

Adesso possiamo farlo partire per vedere di cosa si tratta: due TextField (nome e seriale) e 3 pulsanti, uno dei quali serve per controllare se il codice corrisponde al nome immesso; per adesso può bastare, chiudiamolo e carichiamolo in OllyDbg. Da lì possiamo settare i soliti breakpoint scrivendo "bpx GetWindowTextA" e "bpx GetDlgItemTextA" nell'apposita CommandBar in basso a sinistra; se lo facciamo ripartire e scriviamo il nome ed un seriale fittizio, dopo aver premuto REGISTER succede una cosa strana: il programma si termina senza darci la possibilità di fare il debug! grrrr...
Cosa si è inventato il nostro amico(?) no!se per farci perdere tempo? sicuramente qualcosa che una volta rilevato Olly in memoria, chiude tutto senza pietà... come fare? :-/

Innanzitutto bisogna sapere che la funzione addetta alla terminazione di un processo è ExitProcess che sta dentro kernel32.DLL; bene, allora cominciamo proprio da quella e settiamoci sopra un bel breakpoint con "bpx ExitProcess". Se riavviamo il programma, stavolta, Olly si ferma prima e ci ritroviamo qui:

0040402F |. LEA EAX,[LOCAL.132]                 
00404035 |. CALL KeygenMe.00403AA8              ; scrive "Olly" in LOCAL.132
0040403A |. MOV EAX,[LOCAL.132]                 ; EAX = "Olly"
00404040 |. CALL KeygenMe.00403C1C              ; cerca se ci sono finestre con titolo "Olly"
00404045 |. TEST EAX,EAX                        ; se sì,
00404047 |. JE SHORT KeygenMe.00404050          ;  esegue ExitProcess
00404049 |. PUSH 0                              ;  /ExitCode = 0
0040404B |. CALL <JMP.&KERNEL32.ExitProcess>    ;  \ExitProcess
00404050 |> LEA EAX,[LOCAL.133]                 ; altrimenti
00404056 |. CALL KeygenMe.00403B24              ; scrive "URSo" in LOCAL.133
0040405B |. MOV EAX,[LOCAL.133]                 ; EAX = "URSo"
00404061 |. CALL KeygenMe.00403C1C              ; cerca se ci sono finestre con titolo "URSo"
00404066 |. TEST EAX,EAX                        ; se sì,
00404068 |. JE SHORT KeygenMe.00404071          ;  esegue ExitProcess
0040406A |. PUSH 0                              ;  /ExitCode = 0
0040406C |. CALL <JMP.&KERNEL32.ExitProcess>    ;  \ExitProcess
00404071 |> LEA EAX,[LOCAL.134]                 ; altrimenti
00404077 |. CALL KeygenMe.00403BA0              ; scrive "WinD" in LOCAL.134
0040407C |. MOV EAX,[LOCAL.134]                 ; EAX = "WinD"
00404082 |. CALL KeygenMe.00403C1C              ; cerca se ci sono finestre con titolo "WinD"
00404087 |. TEST EAX,EAX                        ; se sì,
00404089 |. JE SHORT KeygenMe.00404092          ;  esegue ExitProcess
0040408B |. PUSH 0                              ;  /ExitCode = 0
0040408D |. CALL <JMP.&KERNEL32.ExitProcess>    ;  \ExitProcess
00404092 |> CALL KeygenMe.00403DF4              ; passati i controlli si può passare ai vari GetWindowTextA

Le funzioni a 00403AA8, 00403B24 e a 00403BA0 caricano delle stringhe che verranno poi ricercate nei titoli delle finestre attive mediante la funzione 00403C1C; in caso di esito positivo, il processo viene terminato. Avete capito l'arcano? Se in giro c'è Olly, Softice o Win32Dasm, lui se ne accorge; per bypassare questi controlli basta cambiare i tre JE in JMP (li ho sottolineati nel listato). Fatelo (sapete come, no?), salvate il file patchato e ricaricaricatelo nel debugger.

Se inseriamo di nuovo il nome ed il seriale e poi premiamo Register, il programma esce lo stesso! :-( Perché? eppure abbiamo evitato i controlli sul debugger... sì, ma solo quelli sui titoli delle finestre; c'è anche un altro modo per accorgersi della sua presenza: la funzione IsDebuggerPresent. Se ci mettete un breakpoint sopra come abbiamo fatto per ExitProcess vi ritroverete qui:

00403DF4 /$ CALL <JMP.&KERNEL32.IsDebuggerPresent>    ; IsDebuggerPresent
00403DF9 |. CMP AL,1
00403DFB |. JNZ SHORT KeygenMe.00403E07
00403DFD |. XOR EAX,EAX
00403DFF |. MOV DWORD PTR DS:[4066E4],EAX
00403E04 |. MOV AL,1
00403E06 |. RETN
00403E07 |> XOR EAX,EAX
00403E09 |. MOV DWORD PTR DS:[4066E4],DF48ABC8
00403E13 \. RETN
beh, ormai avete capito... se cambiate il JNZ in JMP bypasserete anche questo controllo; alternativamente, potete anche scaricarvi il plugin IsDebugPresent da http://ollydbg.win32asmcommunity.net/stuph/ e scegliete HIDE dal corrispondente menu Plugins.
Vi ricordo che questo va fatto ogni volta che fate ripartire il crackme all'interno di Olly.
 
Bene, a questo punto possiamo passare all'analisi dell'algoritmo di validazione del seriale; assicuratevi che ci siano sempre i breakpoint su GetWindowTextA e riavviate tutto.
004040DC > XOR ESI,ESI                          ; ESI = 0
004040DE . XOR EBX,EBX                          ; EBX = 0
004040E0 . LEA EAX,DWORD PTR SS:[EBP-10C]
004040E6 . PUSH EAX                             ; /String = "Zero_G"
004040E7 . CALL <JMP.&KERNEL32.lstrlenA>        ; \lstrlenA
004040EC . MOV EDI,EAX
004040EE . LEA EAX,DWORD PTR SS:[EBP-20C]
004040F4 . PUSH EAX                             ; /String = "Zero_G"
004040F5 . CALL <JMP.&KERNEL32.lstrlenA>        ; \lstrlenA
004040FA . TEST EAX,EAX                         ; se il campo del nome è vuoto,
004040FC . JE KeygenMe.00404272                 ;   non fare niente ed esci dalla funzione
00404102 . CMP EDI,4                            ; altrimenti controlla che sia più di 4 caratteri
00404105 . JGE SHORT KeygenMe.0040411C          ;   se è più corto, stampa messaggio d'errore
00404107 . PUSH KeygenMe.004042A8               ; /Text = "Please enter more than 4 characters"
0040410C . MOV EAX,DWORD PTR DS:[4066A0]        ; |
00404111 . PUSH EAX                             ; |hWnd = INVALID_HANDLE
00404112 . CALL <JMP.&user32.SetWindowTextA>    ; \SetWindowTextA
00404117 . JMP KeygenMe.00404272                ; esci dalla funzione

Non so perché carichi due volte in memoria il nome inserito... comunque subito dopo possiamo vedere i due controlli che servono per assicurarsi che sia composto da almeno 4 caratteri (il JGE significa "maggiore o uguale"); in caso positivo, si prosegue avanti fino a

0040411C > MOV EAX,EDI                      ; EAX = length(name)
0040411E . SAR EAX,1                        ; EAX = EAX / 2
00404120 . JNS SHORT KeygenMe.00404125      ; se EAX è senza segno    [flag SF = 0, quindi è positivo]
00404122 . ADC EAX,0                        ; sommagli 0 con riporto  [serve per fare il valore assoluto]
00404125 > MOV [LOCAL.1],EAX                ; LOCAL.1 = EAX           [contiene la lunghezza diviso 2]
00404128 . MOV EAX,EDI                      ; EAX = EDI               [la lunghezza originale]
0040412A . AND EAX,80000001                 ; EAX = EAX && 80000001
0040412F . JNS SHORT KeygenMe.00404136      ;                         [0 se la lunghezza è pari, 1 se è dispari]
00404131 . DEC EAX                          ; EAX = EAX - 1
00404132 . OR EAX,FFFFFFFE                  ; EAX = EAX || FFFFFFFE
00404135 . INC EAX                          ; EAX = EAX + 1
00404136 > MOV [LOCAL.2],EAX                ; LOCAL.2 = EAX           [contiene la lunghezza modulo 2]
00404139 . MOV EAX,[LOCAL.1]                ; EAX = LOCAL.1
0040413C . TEST EAX,EAX                     ; if(EAX <= 0)
0040413E . JLE SHORT KeygenMe.00404160      ; goto next_step

Alla fine abbiamo LOCAL.1 che contiene la lunghezza diviso 2 e LOCAL.2 quella originale modulo 2; più avanti troviamo invece un loop che lavora sul seriale a partire dal secondo carattere

00404140 . LEA EDX,DWORD PTR SS:[EBP-10B]  
00404146 > / XOR ECX,ECX                   
00404148 . | MOV CL,BYTE PTR DS:[EDX]       
0040414A . | IMUL ECX,DWORD PTR SS:[EBP-4]   
0040414E . | ADD ESI,ECX                   
00404150 . | XOR ESI,0CC                   
00404156 . | TEST ESI,ESI                  
00404158 . | JGE SHORT KeygenMe.0040415C   
0040415A . | NEG ESI                       
0040415C > | INC EDX                       
0040415D . | DEC EAX                       
0040415E .^\ JNZ SHORT KeygenMe.00404146   

per semplicità, ve lo traduco in C (in assembly può essere utile, ma evidenziarvi la sintassi del C forse confonde soltanto, quindi lo lascio così):

for(int i = 1; i <= [lOCAL.1]; i++)
{
  ESI = ( ESI + name[i]*[LOCAL.1] ) ^ 0xCC;
  ESI = abs( ESI );
}

alla fine (dopo 0040415E), in ESI avremo un valore che è il risultato di questo calcolo effettuato su tutto il nome inserito.

Subito dopo c'è un altro loop che, dopo alcune inizializzazioni, va da 00404174 a 0040418F.

00404160 > MOV EDX,DWORD PTR SS:[EBP-4]
00404163 . MOV EAX,EDI
00404165 . SUB EAX,DWORD PTR SS:[EBP-8]
00404168 . SUB EAX,EDX
0040416A . JL SHORT KeygenMe.00404191
0040416C . INC EAX
0040416D . LEA EDX,DWORD PTR SS:[EBP+EDX-10C]
00404174 > / XOR ECX,ECX
00404176 . | MOV CL,BYTE PTR DS:[EDX]
00404178 . | IMUL ECX,DWORD PTR SS:[EBP-8]
0040417C . | IMUL ECX,EBX
0040417F . | MOV EBX,ECX
00404181 . | XOR EBX,0DD
00404187 . | TEST EBX,EBX
00404189 . | JGE SHORT KeygenMe.0040418D
0040418B . | NEG EBX
0040418D > | INC EDX
0040418E . | DEC EAX
0040418F .^\ JNZ SHORT KeygenMe.00404174

senza incasinare troppo il listato con i commenti, traduco anche questo in C, così magari si capisce meglio:

for(int i = [LOCAL.1]; i < length(name); i++)
{
  EBX = ( EBX * name[i] * [lOCAL.2] ) ^ 0xDD;
  EBX = abs( EBX );
}

al termine del loop, in EBX ci trovate un altro valore che viene subito confrontato con quello presente in ESI (l'avevamo calcolato prima e nessuno l'ha toccato, vi ricordate?); se EBX > ESI viene chiamata la funzione 00403958, altrimenti la 00403980

00404191 > CMP EBX,ESI
00404193 . JLE SHORT KeygenMe.0040419E
00404195 . CALL KeygenMe.00403958
0040419A . MOV EDI,EAX
0040419C . JMP SHORT KeygenMe.004041A5
0040419E > CALL KeygenMe.00403980
004041A3 . MOV EDI,EAX

La funzione 00403958 restituisce il valore 1CC7D16F, mentre la 00403980 restituisce FA91E187: per completezza vi riporto comunque lo snippet delle due funzioni, ma non fanno altro che eseguire delle operazioni su alcuni valori esadecimali statici che si trovano nel segmento dati compreso tra 004066D8 e 004066E4 (potete verificarlo anche voi facendo "Follow in Dump"); l'unica cosa che le differenzia è l'ordine con cui vengono fatte le operazioni.

00403958 /$ MOV EAX,DWORD PTR DS:[4066D8]
0040395D |. IMUL DWORD PTR DS:[4066DC]
00403963 |. ADD EAX,DWORD PTR DS:[4066E0]
00403969 |. XOR EAX,DWORD PTR DS:[4066E4]
0040396F |. MOV EDX,DWORD PTR DS:[4066D8]
00403975 |. OR EDX,DWORD PTR DS:[4066DC]
0040397B |. ADD EAX,EDX
0040397D \. RETN
0040397E MOV EAX,EAX
00403980 /$ MOV EAX,DWORD PTR DS:[4066DC]
00403985 |. IMUL DWORD PTR DS:[4066E0]
0040398B |. ADD EAX,DWORD PTR DS:[4066E4]
00403991 |. OR EAX,DWORD PTR DS:[4066D8]
00403997 |. MOV EDX,DWORD PTR DS:[4066E4]
0040399D |. XOR EDX,DWORD PTR DS:[4066E0]
004039A3 |. ADD EAX,EDX
004039A5 \. RETN

Tornando a dove eravamo, oltre ad EBX e ESI, adesso abbiamo un nuovo valore anche in EDI (1CC7D16F o FA91E187 a seconda del valore degli altri due registri) di cui viene fatto il modulo (valore assoluto) poche righe dopo (caro no!se, ma se in EDI ci metti due costanti, perché dopo ti devi girare le scatole a farci un test per il valore assoluto se sai già quanto valgono? :-) ).

004041A5 > TEST EDI,EDI                   ; se EDI >= 0
004041A7 . JGE SHORT KeygenMe.004041AB    ; prosegui avanti
004041A9 . NEG EDI                        ; altrimenti EDI = -EDI  [complemento a due]

Nella parte successiva, spiccano delle stringhe strane, come se fossero delle parti di un seriale; ed infatti lo sono! Il seriale corretto è così costruito "RK302(EDI)Q-ZX(ESI)A-PM(EBX)OU", dove le parentesi indicano il risultato ASCII della funzione 004038F8 applicata al valore contenuto nel registro indicato (più precisamente si tratta della funzione wsprintf() con l'opzione di formattazione %u per ogni valore).
Man mano che procedete con gli step, vedrete i PUSH che accumulano alternatamente nello stack un pezzo fisso ed un valore ASCII (nella stack window in basso a destra potete osservare il seriale giusto mentre viene costruito, ricordatevi che si legge dal basso in alto).

004041AB > PUSH KeygenMe.004042D4            ; ASCII "RK302"
004041B0 . LEA EDX,DWORD PTR SS:[EBP-220]
004041B6 . MOV EAX,EDI
004041B8 . CALL KeygenMe.004038F8            ; EAX = toAscii(EDI)
004041BD . PUSH DWORD PTR SS:[EBP-220]
004041C3 . PUSH KeygenMe.004042E4            ; ASCII "Q-ZX"
004041C8 . LEA EDX,DWORD PTR SS:[EBP-224]
004041CE . MOV EAX,ESI
004041D0 . CALL KeygenMe.004038F8            ; EAX = toAscii(ESI)
004041D5 . PUSH DWORD PTR SS:[EBP-224]
004041DB . PUSH KeygenMe.004042F4            ; ASCII "A-PM"
004041E0 . LEA EDX,DWORD PTR SS:[EBP-228]
004041E6 . MOV EAX,EBX
004041E8 . CALL KeygenMe.004038F8            ; EAX = toAscii(EBX)
004041ED . PUSH DWORD PTR SS:[EBP-228]
004041F3 . PUSH KeygenMe.00404304            ; ASCII "OU"
004041F8 . LEA EAX,DWORD PTR SS:[EBP-21C]
004041FE . MOV EDX,7                         ; EDX = 7  [è il numero di parti del codice]
00404203 . CALL KeygenMe.00403180            ; concatena tutto in una sola stringa
00404208 . MOV EAX,DWORD PTR SS:[EBP-21C]    ; EAX = good_serial
0040420E . CALL KeygenMe.0040321C
00404213 . MOV EDX,EAX                       ; EDX = EAX
00404215 . LEA EAX,DWORD PTR SS:[EBP-C]
00404218 . CALL KeygenMe.004030E0
0040421D . MOV EAX,DWORD PTR SS:[EBP-C]
00404220 . CALL KeygenMe.00403134
00404225 . PUSH EAX
00404226 . MOV EAX,DWORD PTR SS:[EBP-C]
00404229 . PUSH EAX                          ; nello stack ci va il seriale corretto
0040422A . LEA EAX,DWORD PTR SS:[EBP-22C]
00404230 . LEA EDX,DWORD PTR SS:[EBP-20C]    ; EDX = entered_serial
00404236 . MOV ECX,100                      
0040423B . CALL KeygenMe.0040311C
00404240 . MOV EAX,DWORD PTR SS:[EBP-22C]    ; |
00404246 . PUSH EAX                          ; |S1 = "123456"
00404247 . CALL <JMP.&shell32.StrCmpNA>      ; \StrCmpNA

Dopo varie elucubrazioni per memorizzare nei vari registri d'appoggio il seriale giusto e quello immesso, quando vi ritroverete all'indirizzo 00404247, finalmente vediamo il nostro bel codicione in chiaro nello stack, pronto per essere "pescato", perché è quello che corrisponde al nostro nome! :-) se volete, appuntatevelo da qualche parte...
Signore e signori, poco più giù ecco a voi un bel good/bad boy jump (sfido chiunque a non riconoscerlo...)

0040424C . TEST EAX,EAX
0040424E . JNZ SHORT KeygenMe.00404262
00404250 . PUSH KeygenMe.00404308              ; /Text = "Good serial !!!"
00404255 . MOV EAX,DWORD PTR DS:[4066A0]       ; |
0040425A . PUSH EAX                            ; |hWnd = 0096013C
0040425B . CALL <JMP.&user32.SetWindowTextA>   ; \SetWindowTextA
00404260 . JMP SHORT KeygenMe.00404272
00404262 > PUSH KeygenMe.00404318              ; /Text = "Fake serial !!!"
00404267 . MOV EAX,DWORD PTR DS:[4066A0]       ; |
0040426C . PUSH EAX                            ; |hWnd = 0096013C
0040426D . CALL <JMP.&user32.SetWindowTextA>   ; \SetWindowTextA
00404272 > XOR EAX,EAX

Poco prima potevamo fare serial fishing, adesso sapete anche dov'è che si può fare una patch... ve lo devo dire? nooooo.... dai lo so che lo sapete... no? via giù... basta sostituire il JNZ a 0040424E con 2 NOP (è quello sottolineato). ;-p

Dato che l'algoritmo è totalmente reversibile, per costruire un keygen basta munirsi di un qualsiasi compilatore del vostro linguaggio preferito e riscrivere ad alto livello quelle operazioni che abbiamo visto prima (da 004040DC a 00404247), stampando infine la stringa risultante; per (vostra) comodità vi riassumo (sempre in C) i vari passi...

void KeyGen()
{
  char[] name;

  scanf("Inserire il nome: ",&name)

  int length = strlen( name );
 
  if( length < 4 )
  {
    strcpy( serial, "**il nome deve essere almeno 5 caratteri!**" );
    return;
  }

  int local1 = length/2;
  int local2 = length%2;
  int ESI = 0;

  for( int i = 1; i <= local1; i++ )
  {
    ESI = ( ESI + name[i]*local1 ) ^ 0xCC;
    ESI = abs( ESI );
  }

  int EBX = 0;

  for( int i = local1; i < length; i++ )
  {
    EBX = ( EBX * name[i]*local2 ) ^ 0xDD;
    EBX = abs( EBX );
  }

  int EDI = EBX > ESI ? 0x1CC7D16F : 0xFA91E187;
  EDI = abs( EDI );

  wsprintf(serial, "RK302%uQ-ZX%uA-PM%uOU", EDI, ESI, EBX );
}

Beh, direi cha abbiamo capito praticamente tutto di questo crackme (un keygenme a sentire no!se), ma... possiamo fare di più!! sento già le voci.. "ma che vole 'sto pazzo?? non gli basta un keygen?? è tutto il pomeriggio che leggo numeri e simboli senza senso, ora spengo il computer e mando a quel paese lui e tutta la UIC..." ;-p

sì, certo che basta, anzi è senza dubbio la soluzione migliore, però, come era già successo altre volte, senza fare troppa fatica a SCRIVERE un keygen, è possibile trasformare il programmuzzo malefico nel suo stesso generatore di chiavi. (il tanto decantato keygen injection) di solito è meglio sempre lasciare il programma integro, ma di sicuro si imparano un sacco di cose in questo modo...

Diamo un'occhiata a cosa succedeva alla fine della generazione del seriale giusto (3 code snippets più in alto): all'indirizzo 00404203, la chiamata alla funzione 00403180 concatenava i 7 pezzi del seriale e poneva il risultato nello stack (nello slot di memoria compreso tra i vari frammenti e le stringhe usate per l'anti-debug... date un'occhiata allo stack in basso a destra); a 00404208 questo finisce in EAX, poi da EAX in EDX e poi i vari LEA riscrivono gli indirizzi dentro i registri ed il seriale scompare (la funzione 00403134 legge il seriale da EAX e lo sovrascrive con la sua lunghezza, '21' in esadecimale); il PUSH EAX a 00404225 mette nello stack la lunghezza del seriale... però attenzione, a 00404226 il seriale giusto viene ricaricato dallo stack in EAX e poi pushato (infatti quando siete steppate fino a 0040422A, lo trovate in cima allo stack); infine, a 00404240, il nostro seriale viene pushato un attimo prima di fare StrCmpNA().
Come saprete, dopo che si esce da una funzione, lo stack dei parametri ad essa relativo viene svuotato, ed infatti, appena proseguiamo a
0040424C, non c'è più traccia dei due seriali, né nei registri, né nello stack... ma non è così! no!se non si è ricordato che aveva copiato nello stack (NON pushato) anche il seriale completo, senza cancellarlo prima di passarlo come parametro a StrCmpNA(); vi ricordate dove l'aveva messo? La chiave di volta è la chiamata alla funzione 00403180, che scrive il seriale intero non in cima allo stack, ma più in basso, insieme ad i pezzi che lo compongono. Per sapere dove si trova, basta ritornare all'istruzione subito successiva alla call, cioè il MOV EAX,DWORD PTR SS:[EBP-21C] che si trova a 00404208: questo ci dice da quale punto dello stack (SS sta per StackSegment) viene prelevato il seriale per copiarlo in EAX. L'avete visto? è EBP-21C, cioè si prende il puntatore alla base dello stack per questa funzione (BP sta per BasePointer) e si calcola un offset di -21C; senza stare a fare troppi conti, se durante il debug cliccate su quell'istruzione, Olly traduce nella status-pane in basso gli indirizzi assoluti:

Stack SS:[0012FABC]=009600EC, (ASCII "RK302482857327Q-ZX1056A-PM481869287OU")
EAX=00404208 (KeygenMe.00404208)

siccome nessuno lo ha cancellato da lì, potete tranquillamente scorrere lo stack (sempre nella stack-window in basso a destra) e verificarne la presenza; non è detto che vi venga lo stesso indirizzo assoluto, perché l'allocazione è dinamica ed il BP viene aggiustato di conseguenza, ma è l'indirizzo relativo EBP-21C che vi interessa; infatti, se scorrete più in giù, ritroverete lo schemetto good/bad boy che abbiamo visto prima, però... guardate bene cosa succede: per avvertirci che il seriale è sbagliato, viene utilizzata la funzione SetWindowTextA() che scrive il messaggio "Fake serial !!!" nel TextBox del programma...
Ma allora, perché non farsi stampare direttamente il seriale giusto al posto del messaggio d'errore? La stringa da scrivere è quella che viene pushata all'indirizzo
00404262, e dopo 00404229 abbiamo il nostro bel seriale in cima allo stack: saltando direttamente da 0040422A a 00404267, in EAX verrebbe caricato l'handle della finestra e poi pushato (istruzioni 00404267 e 0040426C): perfetto, avremmo il seriale giusto e l'handle corretto come parametri di SetWindowTextA()!

Ecco il codice modificato:

00404226 . MOV EAX,DWORD PTR SS:[EBP-C]
00404229 . PUSH EAX
0040422A   JMP SHORT KeygenMe.00404267        ; questa è l'istruzione da modificare
0040422C   NOP                                ; i NOPs li aggiunge automaticamente Olly
0040422D   NOP                                ; ...
0040422E   NOP                                ; ...
0040422F   NOP                                ; ...
00404230 . LEA EDX,DWORD PTR SS:[EBP-20C]
    |    .
    |    .
00404260 . JMP SHORT KeygenMe.00404272
00404262 > PUSH KeygenMe.00404318             ; /Text = "Fake serial !!!"
00404267 . MOV EAX,DWORD PTR DS:[4066A0]      ; | col JMP si arriva qui
0040426C . PUSH EAX                           ; |hWnd = 0012FABC
0040426D . CALL <JMP.&user32.SetWindowTextA>  ; \SetWindowTextA

Se premete il destro e fate "Copy to Executable --> All Modifications", "Copy All" e poi "Save File" avrete il vostro bel keygen nato dallo stesso programma per cui serve! :-)

Un'ultima nota: forse vi starete chiedendo perché non abbiamo semplicemente sostituito il PUSH KeygenMe.00404318 ("Fake Serial !!!") con un PUSH DWORD PTR SS:[EBP-21C] (il seriale giusto nello stack)... bene, se provate a farlo, capirete il perché: l'istruzione originale è lunga 5 bytes (da 00404262 a 00404267), mentre quella che vorremmo inserire ne occupa 6 (se la sostituite vedrete che andrà da 00404262 a 00404268); i NOP invaderebbero anche l'istruzione successiva (MOV EAX,DWORD PTR DS:[4066A0]) rendendo errato l'handle hWnd.
Quando un byte fa la differenza...

-=Zero_G=-

Note finali

Beh, anche questo crackme ha avuto il trattamento completo: analisi, unpacking, reversing dell'algoritmo e, a scelta, serial fishing, patching o keygen injection. sono commosso... ;-p

Ringrazio tutta la UIC che ogni giorno scopro sempre di più essere un posto pieno di gente veramente valida.
In particolare saluto il capoccia Que, AndreaGeddon, LonelyWolf, Alfa, il giovanissimo Federico, folletto_burlone, rewil, giuseppe e tutti gli altri di cui adesso non ricordo il nome... siete tanti, sapete? ;-)

Come al solito, ringrazio i Dream Theater (mmmhh.. sai cosa gliene frega a loro se li ringrazio o no...) che ho avuto modo di sentire a Firenze, ed anche i miei chitarristi preferiti, Joe Satriani e Steve Vai, che dopo il Pistoia Blues hanno davvero lasciato il segno... :-D

Disclaimer

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

Noi reversiamo al solo scopo informativo e di miglioramento del linguaggio Assembly.