mankind's SOARP crackme
(reversing completo)

Data

written by -=Zero_G=-

 

18/06/2004

UIC's Home Page

Published by Quequero


"To see a World in a Grain of Sand
And a Heaven in a Wild Flower,

Grazie mille zero, per la precisione e l'accuratezza del tutorial, continua cosi' mi raccomando.

Hold Infinity in the palm of your hand and Eternity in an hour."

- W. Blake -

 

Home page (in costruzione): http://xxxxxxx.xxx.xxx/
email: guido.b@gmail.com
MSN Messegner: j_petrucci@virgilio.it
 

Difficoltà

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

 

Salve a tutti! Questo è un tutorial per capire come funziona il SOARP crackme di mankind (acronimo di "Simulation of A Real Protection" (!)); lo scopo è intercettare la lettura del seriale un carattere alla volta, reversare l'algoritmo che lo convalida in base al nome inserito e, per i più volenterosi, creare anche un keygen adatto allo scopo. :-)
Alla fine vi propongo anche una soluzione alternativa che può sempre far comodo in queste situazioni...


Introduzione

Questo crackme esemplifica un tipo di protezione e di controllo abbastanza diffusi tra i programmi shareware che popolano la rete: ci si ritrova in una finestra dove inserire il nome ed il seriale, ma la verifica di qeust'ultimo non ha luogo dopo aver premuto il classico "OK" o "Unlock", ma proprio mentre lo digitiamo e l'avvenuta registrazione compare automaticamente dentro una label se il seriale è corretto in base al nome.
Vedremo anche che mankind ha usato una tecnica per mascherare le stringhe all'interno del codice per evitare attacchi di tipo "Dead Listing" ed ha implementato un algoritmo basato su somme e
XOR di stringhe per convalidare il seriale.

Tools usati

OllyDbg 1.10 [il nostro debugger preferito!]
un qualsiasi compilatore di un linguaggio qualsiasi per scrivere l'eventuale keygen...
CodeFusion Wizard 3.0 [se invece di un keygen volete fare una patch definitiva]

URL o FTP del programma

mankind's SOARP crackme [già che ci siete, potete farvi un giro in questo bel sito bello zeppo di crackme...]

Notizie sul programma

Beh, notizie sul programma... è un crackme! quindi non serve a nient'altro che farci divertire (o impazzire) un po' con il reversing... ;-)

Essay

OK, fuori non fa (ancora) troppo caldo, ormai fino a domani non ho più voglia di studiare, la musica c'è... siamo a posto, possiamo partire! :-)

Il file eseguibile che si trova nell'archivio che avete scaricato si chiama soarp1.exe, lanciamo OllyDbg e carichiamolo al suo interno; beh, in genere prima si usa un analizzatore tipo PEiD per capire se è stato inserito qualche tipo di protezione/crypter, ma stavolta vi risparmio la fatica e vi dico che questo è codice liscio liscio scritto in assembler (ah, finalmente un programmino veloce e piccolo!)

Per avere un'idea di come funziona, si può direttamente premere F9 per farlo partire e vedere che fa: la finestra è composta da due campi di testo ed un Label con scritto "NOT REGISTERED"; se proviamo a scrivere il nostro nome e qualche codice a caso, la situazione della Label (ovviamente) non cambia.
Bene, da dove cominciare allora?
Anche se non vi è un controllo esplicito, possiamo facilmente supporre che questo venga fatto ogni volta che noi inseriamo un carattere, e, probabilmente, appena il seriale sarà validato, la scritta della Label cambierà; in cosa cambierà? Boh, chi lo sa... magari ci potrà aiutare una ricerca di stringhe, proviamo...
Premendo il tasto destro del mouse nella finestra CPU di Olly scegliamo "Search for --> All referenced text strings" e diamo un'occhiata alla lista che viene fuori: c'è la caption della finestra, un paio di stringhe ASCII piene di 0 ed un nome ("HieCracker")... mah! :-/
Va bene, la tentazione è forte: riavviamo il prog con CTRL+F2 e scriviamo "HieCracker" e qualche numero a caso... vabbè, non poteva essere così semplice, no!? ;-)
Passiamo a controllare quali funzioni di sistema usa quest'aggeggio, così magari si vede viene letto il seriale man mano che lo scriviamo: sempre dopo aver premuto il tasto destro, scegliamo "Search for --> Name (label) in current module": in questa lista ci sono le "import" del programma, e lì vediamo varie funzioni per la manipolazione della stringhe (
strcmp, strlen, ecc...), ma anche una luce nel buio: USER32.GetDlgItemTextA. I veterani del debugging magari ci avrebbero pensato subito a metterci un breakpoint sopra (oltre a GetWindowTextA), ma almeno così siamo sicuri che è quella giusta e sicuramente verrà utilizzata dal programma. Se volete vedere in quali punti viene richiamata premete il destro (tasto molto utile in Olly!) e scegliete "View call tree" (2 chiamate in totale), oppure direttamente "set breakpoint on every reference" per avere un breakpoint settato su ogni call di questa funzione.

Riavviamo il tutto (CTRL+F2 e poi F9)  e cominciamo a scrivere il nome... per ora, calma piatta, quindi almeno il nome non viene letto mentre lo scriviamo; premiamo TAB o clicchiamo di sotto e scriviamo un seriale a caso: ZACK, stavolta l'abbiamo preso! :-) subito dopo il primo carattere ci ritroviamo in mezzo al disassemblato di Olly, più precisamente all'indirizzo 004010C3, sulla prima chiamata a GetDlgItemTextA.
NB: >>> [a destra sono evidenziate le API call ed i loro parametri così come le individua Olly] <<<

 004010B4  PUSH 65                              / Count = 65 (101.)
 004010B6  PUSH soarp1.00403054                 | Buffer = soarp1.00403054
 004010BB  PUSH 3E8                             | ControlID = 3E8 (1000.)
 004010C0  PUSH [ARG.1]                         | hWnd
 004010C3  CALL <JMP.&USER32.GetDlgItemTextA>   \  GetDlgItemTextA
 004010C8  PUSH soarp1.00403054
 004010CD  CALL soarp1.004010FC
 004010D2  JMP SHORT soarp1.004010F3
 004010D4  CMP EAX,2
 004010D7  JNZ SHORT soarp1.004010E2
 004010D9  PUSH 0                               / ExitCode = 0
 004010DB  CALL <JMP.&USER32.PostQuitMessage>   \  PostQuitMessage

Il parametro Buffer di GetDlgItemTextA ci dice in che area di memoria verrà copiato l'elemento letto: premiamo il destro alla riga 004010B6 e scegliamo "Follow in Dump --> Immediate Constant", premiamo F8 ed in basso a sinistra vedremo il comparire il nome nel memory dump (quando ho fatto il debug io ce l'avevo nell'indirizzo 00403054, ma questo può cambiare di volta in volta a causa dell'allocazione dinamica della memoria).
Perfetto, quindi questo
GetDlgItemTextA serviva a leggere il nome; notiamo anche che il PUSH a 004010C8 lo passa come argomento alla funzione a 004010FC.

Premiamo prima F8 e poi F7 per entrarci dentro e ci ritroviamo appunto a 004010FC dove troviamo una piccola routine che in sostanza contiene una strlen ed un JNZ:

 004010FC  PUSH EBP
 004010FD  MOV EBP,ESP
 004010FF  PUSHAD
 00401100  PUSH [ARG.1]                    / String
 00401103  CALL <JMP.&KERNEL32.lstrlenA>   \  lstrlenA
 00401108  OR EAX,EAX
 0040110A  JNZ SHORT soarp1.00401111
 0040110C  POPAD
 0040110D  LEAVE
 0040110E  RETN 4

Cosa succede qui dentro? lo strlen restituisce la lunghezza di una stringa (e questo il caro mankind se lo poteva anche risparmiare, dato che già il GetDlgItemTextA lascia in EAX la lunghezza della stringa letta, ma forse è solo per confonderci un po'...) e se non è uguale a 0 (JNZ) salta a 00401111; se la stringa è vuota, cioè non abbiamo scritto nulla, si prosegue fino al RETN che riporta il controllo a 004010D2, dove dopo un JMP verso 004010F3 si riparte daccapo attendendo il nuovo carattere. Spostando l'attenzione sull'indirizzo puntato dal JNZ cioè 00401111, ci accorgiamo che c'è l'altra chiamata a GetDlgItemTextA che, senza tanta fantasia, stavolta si occuperà di leggere il seriale; in ogni caso, è bene controllare, quindi proseguiamo con F8 fino a 00401123, subito dopo la call: controllando ancora una volta l'indirizzo Buffer controlliamo nel dump della memoria dove è finito il nostro seriale (o meglio dove finirà, dato che per ora ci trovate solo il primo carattere).
[CONSIGLIO: controllate spesso lo stato delle stringhe in memoria, perché a volte vengono trasformate direttamente "sul posto", e questo spesso aiuta a capire gli algoritmi di generazione dei seriali]

Ok, se tutto è andato a buon fine, dovreste trovarvi all'indirizzo 00401128, dove c'è un LEA: se gli operandi di un'istruzione fanno riferimento ad indirizzi degni di nota come le stringhe, queste vengono riportate nel riquadro delle informazioni di Olly in basso. In questo caso, vediamo che in EAX ci viene caricato l'indirizzo (00403036) di quella strana stringa che avevamo visto all'inizio ("HieCracker"), dopodiché, dall'istruzione 0040112E fino alla 00401151, viene caricata una serie di numeri sopra questa stringa in memoria; infatti, se controlliamo passo passo quello che succede nell'area di memoria intorno a 00403036, vediamo che "HieCracker" viene sostituito con un'altra stringa ("CrackThis1") un carattere ASCII alla volta: alla fine di questa sostituzione, EAX, che prima puntava ad "HieCracker", adesso riporta "CrackThis1". Si tratta di un metodo abbastanza ingegnoso per mascherare delle stringhe che altrimenti verrebbero individuato subito con una ricerca appropriata.
All'istruzione
00401155, c'è un altro LEA che stavolta (sempre tenendo d'occhio il riquadro in basso di Olly) carica quella serie di 0 in EAX e poi, dall'istruzione 0040115B fino alla 00401172, copia altri caratteri ASCII nella zona di memoria dove erano contenuti prima, con lo stesso effetto di mascherare un'altra stringa ("Kanal23"); dato che questi indirizzi sono delle costanti nel programma, il tutto lo potete comodamente osservare sempre dall'indirizzo 00403036 in poi, nel memory dump. Arrivati a 0040117E, c'è una chiamata a strcpy, che ha come argomenti l'indirizzo dove è contenuto il nostro nome ed un'altra area di memoria, dove questo verrà prontamente ricopiato (verificate con "Follow in Dump").

A questo punto, per il programma tutto è pronto per validare o meno il codice (che per adesso è composto da un solo carattere), perché il campo del nome non è vuoto ed è stato inserito almeno un carattere del seriale; adesso capiamo facilmente che è questo il controllo che viene effettuato ogni volta che ne aggiungiamo uno, e non appena il codice sarà giusto, qualcosa cambierà sicuramente all'interno della finestra del programma.
Inizializzazioni escluse, la procedura di controllo va da
004011BB a 00401266, compreso lo strcmp finale; vediamo cosa fa questa routine dopo lo strcpy di 0040117E...

 00401183  XOR ESI,ESI                     ESI = 0;  [i]
 00401185  XOR EDI,EDI                     EDI = 0;  [j]
 00401187  XOR ECX,ECX                     ECX = 0; 
 00401189  XOR EBP,EBP                     EBP = 0;  [k]
 0040118B  PUSH soarp1.00403126            / String = "Zero_G"
 00401190  CALL <JMP.&KERNEL32.lstrlenA>   \  lstrlenA
 00401195  MOV DWORD PTR DS:[40311E],EAX   EAX = strlen(name);
 0040119A  PUSH soarp1.00403036            / String2 = "CrackThis1"
 0040119F  PUSH soarp1.0040318B            | String1 = soarp1.0040318B
 004011A4  CALL <JMP.&KERNEL32.lstrcpyA>   \  lstrcpyA
 004011A9  LEA EAX,DWORD PTR DS:[40318B]   EAX = CrackThis1;
 004011AF  LEA EBX,DWORD PTR DS:[403041]   EBX = Kanal23;
 004011B5  LEA EDX,DWORD PTR DS:[403126]   EDX = name;

I primi quattro XOR azzerano i registri ESI, EDI, ECX e EBP che di solito vengono usati come contatori o indici, quindi conviene sfruttare la possibilità di commentare le righe in Olly (doppio click nella parte destra della riga) e rinominarli con delle lettere, così dopo capiamo meglio quale stiamo usando, come si vede qui sopra.
Come al solito, lo strlen restituisce la lunghezza della stringa pushata e questo valore (6 nel mio caso) viene subito dopo copiato da EAX nell'area di memoria
0040311E, e lo stesso avviene con la stringa che abbiamo scoperto prima ("CrackThis1"), che però finisce in 0040318B; entrambe le troviamo nei rispettivi indirizzi nel memory dump, separate da circa 80 parole di memoria.
>>> NB: Da ora in avanti userò direttamente le lettere
i, j, k per gli indici e name, serial, CrackThis1 e Kanal23 per le stringhe; HieCracker e gli zeri non esistono più, perché servivano solo per mascherare le altre due. Infine, stringa[i] indica l'i-esimo carattere della stringa.

Qui di seguito è riportato il primo loop che viene eseguito subito dopo la copia delle stringhe; a destra ho aggiunto i commenti così si capisce meglio che fa questa procedura (ricordatevi che AL corrisponde alle prime due cifre da destra di EAX, e lo stesso vale per CL con ECX e DL con EDX).
Ho convertito i valori esadecimali in decimali perché erano praticamente tutti indici, quindi credo che così si capisca meglio; i valori tra parentesi graffe sono sempre in decimale.

 004011BB  MOV CL,BYTE PTR DS:[EAX+ESI]       CL = CrackThis1[i];
 004011BE  MOV DL,BYTE PTR SS:[EDX+EBP]       DL = name[k];
 004011C2  MOV AL,BYTE PTR DS:[EBX+EDI]       AL = Kanal23[j];
 004011C5  ADD AL,DL                          AL = AL + DL;
 004011C7  ADD AL,CL                          AL = AL + CL;
 004011C9  SUB AL,2                           AL = AL - 2;
 004011CB  XOR AL,9                           AL = AL xor 9;
 004011CD  XOR ECX,ECX                        ECX = 0;
 004011CF  MOV CL,AL                          CL = AL;
 004011D1  LEA EAX,DWORD PTR DS:[ESI+40318B]  EAX = CrackThis1[i];
 004011D7  MOV BYTE PTR DS:[EAX],CL           [EAX] = CL;
 004011D9  LEA EAX,DWORD PTR DS:[40318B]      EAX = CrackThis1;
 004011DF  INC ESI                            i++;
 004011E0  CMP ESI,0A                         if (i == 10)
 004011E3  JNZ SHORT soarp1.004011E7           |
 004011E5  XOR ESI,ESI                         \ i = 0;
 004011E7  LEA EBX,DWORD PTR DS:[403041]      EBX = Kanal23;         
 004011ED  INC EDI                            j++;
 004011EE  CMP EDI,7                          if (j == 7)
 004011F1  JNZ SHORT soarp1.004011F5           |
 004011F3  XOR EDI,EDI                         \ j = 0;
 004011F5  LEA EDX,DWORD PTR DS:[403126]      EDX = name; [ancora?]
 004011FB  INC EBP                            k++;
 004011FC  CMP EBP,DWORD PTR DS:[40311E]      if (k < length[name])    
 00401202  JL SHORT soarp1.004011BB             continue loop;

Avete capito cosa succede nel loop? La stringa CrackThis1 viene sovrascritta un carattere alla volta con un altro carattere che risulta dalla somma del suo valore ASCII con quello del nome inserito e della stringa Kanal23, ed il valore finale viene decrementato di 2 e xorato con 9; questo avviene per tutta la lunghezza del nome che abbiamo immesso nel programma.
Arrivati all'indirizzo 00401204, in EAX troverete l'area di memoria (0040318B) dove è contenuto il risultato di queste operazioni sulla stringa CrackThis1 (nel mio caso con "Zero_G" è venuto EF 3F 36 38 3D C2 68 69 73 31, che in ASCII corrisponde a "'?68=†his1"); qui si capisce che il seriale sarà sempre lungo 10 caratteri, dato che nasce da una trasformazione di CrackThis1 che è lungo 10.

Alla termine della procedura precedente, troviamo un MOV che scrive 0A {10} in un indirizzo di memoria e poi in ECX, che viene usato come indice per scorrere la stringa precedentemente calcolata (quella in 0040318B) che indicherò con new_string; i numeri con cui vengono confrontati i caratteri li ho lasciati in esadecimale, tanto alla fine il tutto viene riportato in ASCII, quindi è inutile consocere il valore intermedio.
>>> NB: apprezzate il mio sforzo di ASCII-art per evidenziare i jumps! ;-)

 00401204  MOV DWORD PTR DS:[403122],0A       temp = 10;
 0040120E  MOV ECX,DWORD PTR DS:[403122]      ECX = temp;
 00401214  XOR ESI,ESI                        i = 0;
 00401216  TEST ECX,ECX                       if (ECX <= 0)
 00401218  JLE SHORT soarp1.00401255            exit from loop; ─────────────────┐
 0040121A  / MOV CL,BYTE PTR DS:[ESI+40318B]  CL = new_string[i]; ═══════════╕ 
 00401220  | CMP CL,30                        if (CL < 30)                    │  │
 00401223  | JL SHORT soarp1.0040123F           goto skip13; ──────┐          │  │
 00401225  | CMP CL,2F                        if (CL <= 2F)        │          │  │
 00401228  | JBE SHORT soarp1.00401233          goto point1; ──────      │  │
 0040122A  | CMP CL,3A                        if (CL > 3A)         │   │      │  │
 0040122D  | JNB SHORT soarp1.00401233          goto point1; ──────┤      │  │
 0040122F  | JLE SHORT soarp1.0040124A        else goto point2; ───┐   │  │
 00401231  | JMP SHORT soarp1.0040123F        goto skip13; ────────┤   │  │   │  │
 00401233  | CMP CL,40                        if (CL <= 40) ═══════╛  │   │  │
 00401236  | JBE SHORT soarp1.0040123F          goto skip13; ──────┤      │   │  │
 00401238  | CMP CL,5B                        if (CL > 5B)         │      │   │  │
 0040123B  | JNB SHORT soarp1.0040123F          goto skip13; ──────┤      │   │  │
 0040123D  | JLE SHORT soarp1.0040124A        else goto point2; ───────┤   │  │
 0040123F  | ADD CL,13                        CL = CL + 13 ═══════╛      │   │  │
 00401242  | MOV BYTE PTR DS:[ESI+40318B],CL  new_string[i] = CL;         │   │  │
 00401248  | JMP SHORT soarp1.0040121A        continue loop; ──────────────┤  │
 0040124A  | MOV ECX,DWORD PTR DS:[403122]    ECX = 0A ══════════════════╛   │  │
 00401250  | INC ESI                          i++;                            │  │
 00401251  | CMP ESI,ECX                      if (i < ECX)                    │  │
 00401253  \ JL SHORT soarp1.0040121A           continue loop; ───────────────┘  ▼

Così a prima vista può sembrare complicato, ma basta un'occhiata in più per capire che ogni elemento di new_string (la stringa di 10 'caratteri' ottenuta tramite somme tra il nome inserito, Kanal23 e CrackThis1) viene incrementato di 13 {19} [attenzione che le somme sono modulo FF+1 {256}, perché CL è 1 byte! FF + 1 fa 00!], fino a che non ricade all'interno del range ASCII delle lettere maiuscole e dei numeri, cioè compreso tra 30 e 5B, esclusi i caratteri speciali da 3A a 40; i vari controlli per fasce (da 30 a 3A [numeri], da 3A a 40 [caratteri speciali], da 40 a 5B [lettere maiuscole]) servono proprio a saltare il secondo intervallo e ricadere su uno degli altri due.
Il
CMP alla fine del loop serve per controllare quando è stata raggiunta la fine della stringa.

Bene, ora che abbiamo capito come è generato il seriale valido per il nostro nome, guardiamo cosa c'è subito dopo: un bel strcmp seguito da un JNZ!: eheh, il classico good/bay boy jump, che però non avremmo individuato con una semplice ricerca per stringhe tipo "REGISTERED" o "NOT REGISTERED", perché questo non sono dati statici ma vengono scritte un carattere alla volta tramite MOV in aree di memoria adiacenti; lo stesso è successo anche con le due stringhe (CrackThis1 e Kanal23) necessarie per calcolare il seriale (HieCracker era lì solo per confonderci...).

 00401255  PUSH soarp1.0040318B           / String2 = "NR68P49M11"  (correct serial for given name)
 0040125A  PUSH soarp1.004030B9           | String1 = "1"           (serial read from TextBox)
 0040125F  CALL <JMP.&KERNEL32.lstrcmpA>  \  lstrcmpA
 00401264  OR EAX,EAX
 00401266  JNZ SHORT soarp1.004012A9      good/bad boy jump!!

I due parametri di strcmp sono le stringhe da confrontare: una è il seriale corretto, l'altra è sono i caratteri che vengono letti dal TextBox ogni volta che ne aggiungiamo uno; infatti io avevo premuto "1" e per ora c'è soltanto quello.

Se il seriale corrisponde, si prosegue subito sotto il JNZ, altrimenti si va a 004012A9, ma in entrambi i casi notiamo che ci sono una lunga serie di MOV e poi un SetDlgItemTextA per la finestra del programma; dato che ormai il codice è sbagliato, usiamo intanto F8 per seguire che fa almeno in questo caso.
Le parentesi quadre indicano riferimenti alla memoria, in questo caso puntatori a stringhe.

 004012A9  LEA EAX,DWORD PTR DS:[403027]        EAX = ["00000000000000"]
 
004012AF  MOV BYTE PTR DS:[EAX],4E             [EAX[0]] = 'N'
 004012B2  MOV BYTE PTR DS:[EAX+1],4F           [EAX[1]] = 'O'
 004012B6  MOV BYTE PTR DS:[EAX+2],54           [EAX[2]] = 'T'
 004012BA  MOV BYTE PTR DS:[EAX+3],20           [EAX[3]] = ' '
 004012BE  MOV BYTE PTR DS:[EAX+4],52           [EAX[4]] = 'R'
 004012C2  MOV BYTE PTR DS:[EAX+5],45           [EAX[5]] = 'E'
 004012C6  MOV BYTE PTR DS:[EAX+6],47           [EAX[6]] = 'G'
 004012CA  MOV BYTE PTR DS:[EAX+7],49           [EAX[7]] = 'I'
 004012CE  MOV BYTE PTR DS:[EAX+8],53           [EAX[8]] = 'S'
 004012D2  MOV BYTE PTR DS:[EAX+9],54           [EAX[9]] = 'T'
 004012D6  MOV BYTE PTR DS:[EAX+A],45           [EAX[10]]= 'E'
 004012DA  MOV BYTE PTR DS:[EAX+B],52           [EAX[11]]= 'R'
 004012DE  MOV BYTE PTR DS:[EAX+C],45           [EAX[12]]= 'E'
 004012E2  MOV BYTE PTR DS:[EAX+D],44           [EAX[13]]= 'D'
 004012E6  PUSH soarp1.00403027                 / Text = "00000000000000"     ("NOT REGISTERED")
 004012EB  PUSH 67                              | ControlID = 67 (103.)       (Label in alto nella finestra)
 004012ED  PUSH DWORD PTR DS:[403050]           | hWnd = 0010031C (' ManKind's SOARP KeyGenMe 1',class='#32770')
 004012F3  CALL <JMP.&USER32.SetDlgItemTextA>   \  SetDlgItemTextA
 004012F8  PUSH soarp1.00403027                 / String2 = "00000000000000"  ("NOT REGISTERED")
 004012FD  PUSH soarp1.0040318B                 | String1 = soarp1.0040318B   (entered_serial)
 00401302  CALL <JMP.&KERNEL32.lstrcpyA>        \  lstrcpyA
 00401307  LEA EAX,DWORD PTR DS:[403036]        EAX = ["CrackThis1"]
 
0040130D  MOV BYTE PTR DS:[EAX],0              [EAX [0]] = '0'
 00401310  MOV BYTE PTR DS:[EAX+1],0            [EAX [1]] = '0'
 00401314  MOV BYTE PTR DS:[EAX+2],0            [EAX [2]] = '0'
 00401318  MOV BYTE PTR DS:[EAX+3],0            [EAX [3]] = '0'
 0040131C  MOV BYTE PTR DS:[EAX+4],0            [EAX [4]] = '0'
 00401320  MOV BYTE PTR DS:[EAX+5],0            [EAX [5]] = '0'
 00401324  MOV BYTE PTR DS:[EAX+6],0            [EAX [6]] = '0'
 00401328  MOV BYTE PTR DS:[EAX+7],0            [EAX [7]] = '0'
 0040132C  MOV BYTE PTR DS:[EAX+8],0            [EAX [8]] = '0'
 00401330  MOV BYTE PTR DS:[EAX+9],0            [EAX [9]] = '0'
 00401334  LEA EAX,DWORD PTR DS:[403041]        EAX = ["Kanal23"]
 
0040133A  MOV BYTE PTR DS:[EAX],0              [EAX [0]] = '0'
 0040133D  MOV BYTE PTR DS:[EAX+1],0            [EAX [1]] = '0'
 00401341  MOV BYTE PTR DS:[EAX+2],0            [EAX [2]] = '0'
 00401345  MOV BYTE PTR DS:[EAX+3],0            [EAX [3]] = '0'
 00401349  MOV BYTE PTR DS:[EAX+4],0            [EAX [4]] = '0'
 0040134D  MOV BYTE PTR DS:[EAX+5],0            [EAX [5]] = '0'
 00401351  MOV BYTE PTR DS:[EAX+6],0            [EAX [6]] = '0'
 00401355  POPAD
 00401356  LEAVE
 00401357  RETN 4

Beh, alla luce di quello che abbiamo visto per il seriale, direi che qui si capisce abbastanza chiaramente cosa succede (oltretutto, mentre fate gli step, vedrete che le stringhe cambieranno in tempo reale direttamente nella decodifica di Olly): le sfilze di zeri vengono sovrascritte un carattere alla volta con la scritta "NOT REGISTERED" (quella che non riuscivamo a trovare con "Search for reference strings"), che viene poi passata al SetDlgItemTextA per scriverla nella Label (quella sopra la TextBox del nome) ed allo strcpy per sovrascriverla al seriale in memoria (controllate l'indirizzo 0040318B con "Follow in Dump" quando siete a 00401302); poi, da 00401307 a 00401334 ci sono i soliti MOV che sovrascrivono i nostri due amici CrackThis1 e Kanal23, in modo da non lasciare traccia nè del seriale giusto, nè del meccanismo con cui è stato generato.
Per completezza, potete anche guardare cosa succede nel caso in cui il seriale è giusto: l'unica cosa che cambia è la stringa che stavolta è "REGISTERED", perché comunque poi si jumpa a
004012F8 per lo strcpy e l'azzeramento delle altre stringhe.

Ottimo! Direi che abbiamo reversato l'intero procedimento! che volete di più? :-)

Adesso a voi la scelta (in ordine di qualità decrescente):
  1) un elegante keygen che genera il seriale giusto a partire da un nome;
      [data la natura delle operazioni che vengono eseguite sulle stringhe ed i vari
XOR vi consiglio di farlo in C, se potete]
  2) un tranquillo serial-fishing per il vostro nome;
      [unico problema: per ogni nome che inserite dovete rifare il debug per catturare il seriale]
  3) un violento patching per far accettare qualsiasi seriale come valido.
      [no comment]

eheheh, ma forse i più attenti avranno anche intravisto un'altra possibilità di intervento a metà strada tra la 2) e la 3)... ci avete pensato?

Avete mai sentito parlare di Keygen Injection?
E' una tecnica che permette di sostituire la stampa di messaggi con quella del seriale giusto, dirottando i
PUSH delle funzioni verso l'indirizzo del seriale, se è sempre presente in memoria.
Riuscite a vedere se è possibile fare una cosa del genere per questo crackme?

Guardate bene il PUSH a 004012E6: serve per passare a SetDlgItemTextA il "NOT REGISTERED" ottenuto cambiando gli zeri; ma abbiamo ancora quello giusto a disposizione? SI'! è sempre in 0040318B, come ci ricordano i parametri passati a strcmp in 0040125F. Ok, quindi per stamparlo basta pushare al SetDlgItemTextA quell'indirizzo al posto del puntatore a "NOT REGISTERED", vi torna?
Qui sotto riporto il
PUSH modificato (si fa doppio click nella parte sinistra della riga e poi "Assemble", dove potete usare direttamente gli mnemonici degli opcode); premendo il destro e scegliendo "Copy to Executable --> All modifications --> Copy all", poi destro ancora e "Save File" avrete un nuovo eseguibile in cui, dopo aver scritto il nome, non appena inserite un carattere del seriale farlocco, vi viene mostrato quello giusto in alto; ricopiatelo di sotto e a quel punto comparirà la scritta "REGISTERED". Vittoria! ;-)

 004012E6  PUSH soarp1.0040318B                 / Text = "NR68P49M11    (seriale corretto)
 004012EB  PUSH 67                              | ControlID = 67 (103.)    (Label in alto nella finestra)
 004012ED  PUSH DWORD PTR DS:[403050]           | hWnd = 0010031C (' ManKind's SOARP KeyGenMe 1',class='#32770')
 004012F3  CALL <JMP.&USER32.SetDlgItemTextA>   \  SetDlgItemTextA

Ovviamente il keygen è sempre la scelta migliore da un punto di vista tecnico, in quanto si lascia intatto il programma originale, ma comunque è bene conoscere anche altre strade per raggiungere il nostro scopo.

qualsiasi sia la protezione che vogliamo affrontare, proviamo varie alternative ed usiamo tutte le risorse a disposizione, perché spesso quello che separa l'arrendersi dalla vera soluzione è solo un po' di intuito e pazienza in più.

E' da notare che in fondo questo semplice crackme si è rivelato molto istruttivo: immagino che spesso avrete visto programmi che controllano i seriali man mano che vengono scritti e di cui non riuscivate a trovare stringhe significative, proprio perché mascherate e/o codificate in run-time.
L'algoritmo di generazione del seriale (quello che dovreste riscrivere per il keygen) è un po' strano per via del fatto che si basa su quelle due stringhe fisse, ma il procedimento dello
XOR e delle somme di caratteri per generare un'altra stringa valida in ASCII è abbastanza comune, quindi fatene tesoro.
E poi, quando se ne presenta l'occasione, il Keygen Injection, sebbene richieda ugualmente una patch, dà sempre una discreta soddisfazione! :-)

-=Zero_G=-

Note finali

Grazie a tutta la UIC e a Quequero per aver creato quello che oggi è il posto più bello dove sentirsi un reverser italiano. :-)
Grazie ai Dream Theater, agli Ayreon, agli Evergrey, ai Rhapsody ed agli Story of the Year per avermi tenuto compagnia mentre scrivevo questo tutorial, e spero lo continuino a fare anche in tanti altri momenti.
UN NON GRAZIE all'Università dove studio, che ultimamente mi sta spillando il sangue dalle vene e non mi lascia mai tempo per rilassarmi un minuto.
E per finire, il solito elogio ad Escher e Tolkien che non fa mai male...

Disclaimer

Vorrei ricordare che il software va comprato e non rubato, dovete registrare il vostro prodotto dopo il periodo di valutazione. Non mi ritengo responsabile per eventuali danni causati al vostro computer determinati dall'uso improprio di questo tutorial. Questo documento è stato scritto per invogliare il consumatore a registrare legalmente i propri programmi, e non a fargli fare uso dei tantissimi file crack presenti in rete, infatti tale documento aiuta a comprendere lo sforzo 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.