Reverse engineering e cracking di uno shareware

In questo articolo mostriamo un semplice reverse-engineering e crack di uno shareware (non ne riporterò il nome, ho oscurato parzialmente le immagini 🙂 ). Il gioco permette un massimo di 30 partite dopodichè dovremo registrare il prodotto.

Lo scopo del tutorial sarà quello di aggirare tale limitazione “registrando” a modo nostro il gioco. Per prendere un po’ di dimestichezza con l’assembly delle architetture x86 vedere questi link:

http://en.wikibooks.org/wiki/X86_Assembly

http://www.cs.virginia.edu/~evans/cs216/guides/x86.html

http://win32assembly.programminghorizon.com/links.html

Andremo ad usare il famoso debugger Windows OllyDbg 1.1.

Picture 1

 

Il debugger deve avere il plugin della command line di Quequero, scaricabile qui.

Una volta avviato il programma abbiamo la seguente schermata:

1_01

sulla barra in alto notiamo la scritta “Non registrato, versione di valutazione (30 partite)” a conferma del fatto che in effetti la limitazione consiste proprio nel numero massimo di partite che è possibile giocare. Se clicchiamo su “?” notiamo la voce di menu “Registrazione” la quale ci porta a questo popup:

2_01

Da questo popup capiamo che la registrazione è il classicissimo sistema [username,chiave], dove “username” è scelto dall’utente e “chiave” è appunto il seriale che ci viene fornito acquistando regolarmente il programma. Inserendo dei dati a caso otteniamo:

3_01

A questo punto abbiamo capito molto di piu’: il seriale è funzione dell’username e da qualche parte nel codice deve esistere un controllo di corrispondenza tra il seriale introdotto da noi e quello calcolato dal programma stesso. Ciò significa che esiste un punto all’interno del programma dove viene effettuato un controllo tra il seriale inserito con quello calcolato: dall’esito di tale controllo abbiamo due possibilità: se il seriale inserito e’ errato triggeriamo il messaggio di errore (imbocchiamo sempre questa strada se non conosciamo il seriale) altrimenti otteniamo la registrazione corretta del gioco.

Il nostro scopo sarà quello di individuare tale punto e modificarlo in maniera tale da forzare sempre la seconda via (quella della registrazione) indipendentemente dal seriale che inseriamo.

Iniziamo ora ad indagare più approfonditamente nel codice:

Il messaggio di errore che otteniamo quando inseriamo un seriale errato non è altro (in questo caso) che il risultato di una chiamata alla MessageBoxA, la tipica funzione API di notifica degli errori registrata all’interno di User32.dll.

Per una lista completa delle API Windows vedere questo link

Come inizio, per avvicinarci al punto in cui viene controllato il seriale, possiamo intanto cercare di piazzare un breakpoint sulla call che invoca MessageBoxA e poi controllare nei dintorni del codice.

Apriamo OllyDbg, carichiamo l’eseguibile e attendiamo che il debugger finisca l’analisi del codice macchina.
A questo punto premiamo Alt+F1 per far apparire la linea di comando e piazziamo il breakpoint sulla chiamata a MessageBoxA, cioè scriviamo

bpx MessageBoxA

Adesso premiamo F9 per far partire il programma. Comparirà la schermata inziale. Andiamo su “?” e poi su “Registrazione“, inseriamo i soliti dati a caso e clicchiamo su “Ok”. Il debugger blocca il programma alla locazione 77D5050B all’interno del modulo USER32.dll di Windows, modulo che contiene molte delle API del sistema operativo tra cui la nostra MessageBoxA.

A questo punto osserviamo come è stato caricato lo stack (finestra in basso a destra di ollydbg); questi sono gli ultimi dati inseriti:

73D9DE23  CALL to MessageBoxA from MFC42.73D9DE1D
00110526  |hOwner = 00110526 (''Registra ***********'',class=''#32770'',parent=00050514)
0041C82C  |Text = "Mi dispiace, registrazione non corretta."
0041C860  |Title = "Errore"
00000000  \\Style = MB_OK|MB_APPLMODAL
004182C8  ********.004182C8
00406124  RETURN to ********.00406124 from <JMP.&MFC42.#4224>
0041C82C  ASCII "Mi dispiace, registrazione non corretta."
0041C860  ASCII "Errore"

E’ chiaro che sono stati appena caricati i dati relativi ad una finestra di errore e che è stata invocata la funzione MessageBoxA nella libreria USER32.dll. A noi non interessa tanto ispezionare il codice da qui in poi (sappiamo già che steppando in avanti con F8 verrà visualizzato il messaggio di errore), quanto invece vedere in che punto del modulo del programma è stata fatta la richiesta di visualizzazione del popup, perchè è proprio in quel punto che avviene il controllo.

Per far questo osserviamo che nello stack, alla settima riga, è presente (ovviamente) l’indirizzo di ritorno al chiamante, ovvero l’indirizzo 00406124:

00406124  RETURN to ********.00406124 from &lt;JMP.&amp;MFC42.#4224&gt;

Evidenziamo questa riga, premiamo il tasto destro del mouse e selezioniamo “Follow in disassembler“. Il debugger ci mostra il punto di chiamata che il nostro programma invoca in caso di errore. Notiamo che tale chiamata è inserita all’interno di una funzione che inizia a partire da 00406111. Eccola qua:

00406111 PUSH 0
00406113   PUSH ********.0041C860  ;ASCII "Errore"
00406118   PUSH ********.0041C82C  ;ASCII "Mi dispiace, registrazione non corretta."
0040611D   MOV ECX,EBP
0040611F   CALL <JMP&MFC42.#4224>;           ; punto di chiamata a USER32.DLL
00406124   MOV ECX,DWORD PTR SS:[ESP+30C]
0040612B   POP EDI
0040612C   POP EBP
0040612D   MOV DWORD PTR FS:[0],ECX
00406134   ADD ESP,310
0040613A   RETN

La funzione non fa altro che caricare lo stack con i parametri da passare alla MessageBoxA e cioe “MB_OK|APPL_MODAL”, la stringa di titolo (“Errore”) e la stringa del corpo del messaggio (“Mi dispiace, registrazione non corretta”). Questo è il punto del programma in cui i dati relativi al messaggio di errore vengono inseriti nello stack. Osservando bene il listato prodotto dal debugger notiamo che l’istruzione alla locazione 00406111 (PUSH 0) è stata raggiunta tramite un salto di qualche tipo (simbolo “>” accanto a “PUSH 0”). Scorrendo in alto nel codice individuiamo i punti di salto, e cioè:

00405FB7   JB ********.00406111

e, un pò più in basso:

0040605F   JE ********.00406111

Questo significa che esistono due punti di salto che ci fanno raggiungere il messaggio di errore! Il primo salto (JB) è preceduto da un test un pò sospetto per via del valore immediato 6:

CMP ECX,6

Per indagare sulla natura di questo controllo occorre risalire tutta la funzione e piazzare un breakpoint all’inizio, e cioè alla locazione 00405F60. Fatto questo chiudiamo il programma e riavviamo l’esecuzione con F9. Andiamo in “?” e di nuovo in “Registrazione” ed inseriamo altri dati a caso. Premendo “Ok” ci fermiamo correttamente in 00405F60, cioè all’inizio della nostra funzione. Steppando con F8 osserviamo che in ECX viene effettivamente caricata la lunghezza in caratteri dell’username da noi inserito. A questo punto è chiaro che lo scopo delle due istruzioni<

00405FB4   CMP ECX,6
00405FB7   JB ********.00406111

è quello di controllare la lunghezza dell’username. Nel caso in cui questa risultasse strettamente inferiore (JB = Jump if Below) a 6 verrebbe invocato il salto giungendo in 00406111, cioè al messaggio di errore. Ecco quindi svelato un primo controllo: l’username deve essere almeno di 6 caratteri.

Chiudiamo il programma, e riavviamo con F9. Ritorniamo alla registrazione avendo cura di introdurre un username a caso ma lungo almeno 6 caratteri e premiamo “Ok”. Il debugger ci riporta alla funzione di prima ma adesso siamo sicuri di superare il controllo, ed infatti steppando oltre con F8 così avviene.

Superato il controllo entriamo nella parte più interessante e cioè quella in cui presumibilmente viene generato il seriale e poi confrontato con quello da noi inserito. Analizziamo in dettaglio tutta la funzione e per ogni parte ne diamo una breve descrizione: L’entry-point della funzione è all’indirizzo 00405F60. All’inizio vengono invocate due CALL alla API GetWindowTextA per il recupero dell’username e del seriale introdotto nei form:

00405F60   MOV EAX,DWORD PTR FS:[0]
00405F66   PUSH -1
00405F68   PUSH ********.00415BCB
00405F6D   PUSH EAX
00405F6E   MOV DWORD PTR FS:[0],ESP
00405F75   SUB ESP,304
00405F7B   LEA EAX,DWORD PTR SS:[ESP+4]
00405F7F   PUSH EBP
00405F80   MOV EBP,ECX
00405F82   PUSH EDI
00405F83   PUSH 100
00405F88   MOV ECX,DWORD PTR SS:[EBP+60]
00405F8B   PUSH EAX
00405F8C   CALL <JMP.&MFC42.#3873>         ; GetWindowTextA (recupero username)
00405F91   LEA ECX,DWORD PTR SS:[ESP+20C]
00405F98   PUSH 100
00405F9D   PUSH ECX
00405F9E   MOV ECX,DWORD PTR SS:[EBP+64]
00405FA1   CALL <JMP.&MFC42.#3873>        ; GetWindowsTextA (recupero seriale)
00405FA6   LEA EDI,DWORD PTR SS:[ESP+C]

A questo punto viene utilizzata l’istruzione REPNE SCAS per ottenere la lunghezza della stringa puntata da ES:[EDI], cioe’ l’username. La lunghezza viene determinata semplicemente andando a contare il numero di caratteri prima del terminatore \0 di stringa.

00405FAA   OR ECX,FFFFFFFF                 ; inizializza ECX
00405FAD   XOR EAX,EAX                    ; EAX = 0
00405FAF   REPNE SCAS BYTE PTR ES:[EDI]   ; trova il terminatore di stringa
00405FB1   NOT ECX                        ; complemento a uno...
00405FB3   DEC ECX                        ; ...di ECX

Adesso ECX contiene la lunghezza dell’username. Le prossime due istruzioni verificano che sia superiore o uguale a 6, altrimenti si va in 00406111, cioè al messaggio di errore:

00405FB4   CMP ECX,6
00405FB7   JB ********.00406111

Fatto ciò viene generato il seriale a partire dall’username, ad esempio con l’username “gianluca” viene generato il seriale “QWHMEFKC”:

00405FBD   MOV EAX,DWORD PTR DS:[41D1C8]
00405FC2   PUSH EBX
00405FC3   LEA EDX,DWORD PTR SS:[ESP+10]
00405FC7   PUSH ESI
00405FC8   PUSH EDX
00405FC9   LEA ECX,DWORD PTR DS:[EAX+FC]
00405FCF   CALL ********.004010FA
00405FD4   MOV ECX,DWORD PTR DS:[41D1C8]
00405FDA   ADD ECX,0FC
00405FE0   CALL ********.00401438
00405FE5   MOV EAX,DWORD PTR DS:[41D1C8]
00405FEA   LEA EDX,DWORD PTR SS:[ESP+114]
00405FF1   PUSH 100
00405FF6   PUSH EDX
00405FF7   LEA ECX,DWORD PTR DS:[EAX+FC]
00405FFD   CALL ********.00401168

Si fanno ora puntare i registri ESI e EAX rispettivamente al seriale inserito da noi e quello generato internamente:

00406002   LEA ESI,DWORD PTR SS:[ESP+114]
00406009   LEA EAX,DWORD PTR SS:[ESP+214]

Adesso avviene il controllo di eguaglianza tra le due stringhe. Questo pezzo di codice controlla passo a passo le coppie di caratteri corrispondenti nei due seriali. Se risultano tutti identici si salta alla locazione 00406034 altrimenti si va in 00406038:

00406010   MOV DL,BYTE PTR DS:[EAX]      ; carica la prima coppia di caratteri
00406012   MOV BL,BYTE PTR DS:[ESI]
00406014   MOV CL,DL
00406016   CMP DL,BL
00406018   JNZ SHORT ********.00406038   ; se i caratteri sono diversi esci
0040601A   TEST CL,CL
0040601C   JE SHORT ********.00406034    ; prima stringa terminata? se si esci
0040601E   MOV DL,BYTE PTR DS:[EAX+1]    ; carica la seconda coppia di caratteri
00406021   MOV BL,BYTE PTR DS:[ESI+1]
00406024   MOV CL,DL
00406026   CMP DL,BL
00406028   JNZ SHORT ********.00406038   ; se i caratteri sono diversi esci
0040602A   ADD EAX,2
0040602D   ADD ESI,2                     ; avanze di 2 in entrambe le stringhe
00406030   TEST CL,CL
00406032   JNZ SHORT ********.00406010   ; prossimi due caratteri

Veniamo adesso al punto critico della fase di registrazione. Come abbiamo già accennato, quando il precedente pezzo di codice termina può portarci o in 00406034 oppure in 00406038. Nel primo caso il registro EAX viene caricato con 0 (XOR EAX,EAX) e poi si prosegue in 0040603D. Nell’altro caso il registro EAX viene inizializzato con -1. In ogni caso poi si prosegue a partire da 0040603D. EAX diviene quindi la flag del controllo di prima: se vale 0 è stato inserito un seriale corretto, se vale -1 il seriale era sbagliato.

00406034   XOR EAX,EAX                    ; OK! Il seriale era giusto e quindi
00406036   JMP SHORT ********.0040603D    ; poniamo EAX = 0 e saltiamo in 0040603D
00406038   SBB EAX,EAX                    ; Il seriale era sbagliato e quindi
0040603A   SBB EAX,-1                     ; poniamo EAX = -1 e andiamo avanti

Il programma ora prosegue controllando il valore di EAX e agendo di conseguenza. Se vale 0 allora verrà invocata la registrazione del programma, se vale -1 verrà lanciato il popup di errore.

0040603D   MOV EDX,DWORD PTR DS:[41D1C8]
00406043   XOR ECX,ECX
00406045   TEST EAX,EAX
00406047   SETE CL
0040604A   MOV DWORD PTR DS:[EDX+73C],ECX
00406050   MOV EAX,DWORD PTR DS:[41D1C8]
00406055   POP ESI
00406056   POP EBX
00406057   MOV ECX,DWORD PTR DS:[EAX+73C]
0040605D   TEST ECX,ECX
0040605F   JE ********.00406111              ; vai al messaggio di errore
00406065   LEA ECX,DWORD PTR SS:[ESP+C]      ; da qui in poi abbiamo...
00406069   PUSH ECX                          ; ...il processo di registrazione
0040606A   LEA ECX,DWORD PTR DS:[EAX+70C]
00406070   CALL &lt;JMP.&amp;MFC42.#860&gt;
00406075   MOV EAX,DWORD PTR DS:[41D1C8]
0040607A   LEA EDX,DWORD PTR SS:[ESP+C]
0040607E   PUSH EDX
0040607F   LEA ECX,DWORD PTR DS:[EAX+FC]
00406085   CALL ********.00401537
0040608A   PUSH ********.0041C87C            ;  ASCII "************"
0040608F   LEA ECX,DWORD PTR SS:[ESP+C]
00406093   CALL &lt;JMP.&amp;MFC42.#537&gt;
00406098   PUSH ********.0041C86C            ;  ASCII " - Registrato"
0040609D   LEA ECX,DWORD PTR SS:[ESP+C]
004060A1   MOV DWORD PTR SS:[ESP+318],0
004060AC   CALL &lt;JMP.&amp;MFC42.#941&gt;
004060B1   PUSH ********.0041C868            ;  ASCII " a "
004060B6   LEA ECX,DWORD PTR SS:[ESP+C]
004060BA   CALL &lt;JMP.&amp;MFC42.#941&gt;
004060BF   LEA ECX,DWORD PTR SS:[ESP+C]
004060C3   PUSH ECX
004060C4   LEA ECX,DWORD PTR SS:[ESP+C]
004060C8   CALL &lt;JMP.&amp;MFC42.#941&gt;
004060CD   MOV EDX,DWORD PTR SS:[ESP+8]
004060D1   MOV ECX,DWORD PTR DS:[41D1C8]
004060D7   PUSH EDX
004060D8   CALL &lt;JMP.&amp;MFC42.#6199&gt;
004060DD   PUSH 0
004060DF   MOV ECX,EBP
004060E1   CALL &lt;JMP.&amp;MFC42.#2645&gt;
004060E6   LEA ECX,DWORD PTR SS:[ESP+8]
004060EA   MOV DWORD PTR SS:[ESP+314],-1
004060F5   CALL &lt;JMP.&amp;MFC42.#800&gt;
004060FA   POP EDI
004060FB   POP EBP
004060FC   MOV ECX,DWORD PTR SS:[ESP+304]
00406103   MOV DWORD PTR FS:[0],ECX
0040610A   ADD ESP,310
00406110   RETN                              ; fine registrazione

Forzare la registrazione è abbastanza semplice: è sufficiente forzare EAX = 0 anche nel caso di seriale errato. Abbiamo infatti scoperto che EAX = 0 è sinonimo di “I seriali erano identici” e quindi basta imporlo in luogo dell’istruzione che pone EAX = -1.

In pratica sarà sufficiente modificare il seguente codice:

00406038   SBB EAX,EAX
0040603A   SBB EAX,-1

nel seguente:

00406038   XOR EAX,EAX
0040603A   NOP
0040603B   NOP
0040603C   NOP

Operazione facile e indolore dato che le istruzioni di rimpiazzo non occupano più spazio di quelle da sostituire (sempre 5 byte in tutto). Con questa modifica imponiamo EAX = 0 sia nel caso di seriale corretto che errato e quindi forziamo il programma ad effettuare la registrazione. Ecco uno snapshot della registrazione; notare il codice macchina in rosso corrispondente alla parte modificata:

4_01_01

11
Leave a Reply

avatar
7 Comment threads
4 Thread replies
0 Followers
 
Most reacted comment
Hottest comment thread
5 Comment authors
Daniele GiustoGiuseppeGianlucaGiuseppeJosh Recent comment authors
Daniele Giusto
Daniele Giusto

Buonasera Gianluca, ho visto la sua guida molto interessante.
ho provato ad applicare la sua guida, ma non riesco a trovare il crack per un programma datato.
Se le invio il file exe riuscirebbe gentilmente a risolvermi il problema?
Rimango in attesa du una risposta e porgo distinti saluti.

Daniele Giusto

Giuseppe
Giuseppe

come devo fare per fare l’upload

Giuseppe
Giuseppe

Guarda ti mando subito su gmail l’eseguibile secondo me visto quello che riesci a fare per te questa è una cavolata io di assembler sono negato se ci riesci mi rimandi poi l’eseguibile modificato da sovrascivere ok

Giuseppe
Giuseppe

Gianluca vuoi che ti passo l’eseguibile per posta sono circa 10 mb

Giuseppe
Giuseppe

Ciao Gianluca! ho visto la tua guida molto interessante.
io avrei un software di mixaggio molto carino il mio problema è che non riesco a trovare il crack io lo uso a casa per mescolare le mie canzoni il problema è che ogni tanto presenta un nag screen che mi dice che il programma terminerà fra trenta minuti infatti in effetti si chiude dopo questo lasso di tempo.
Tu riusciresti gentilmente a rimuovermi tale nag screen e relativo timer che mi chiude il programma?
nell’attesa ti una tua gentile risposta ti porgo distinti saluti.

Ciao Giuseppe