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.
Il debugger deve avere il plugin della command line di Quequero, lo potete trovare cercando online "ollydbg quequero".
Una volta avviato il programma abbiamo la seguente schermata:
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:
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:
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 <JMP.&MFC42.#4224>
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 <JMP.&MFC42.#860>
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 <JMP.&MFC42.#537>
00406098 PUSH ********.0041C86C ; ASCII " - Registrato"
0040609D LEA ECX,DWORD PTR SS:[ESP+C]
004060A1 MOV DWORD PTR SS:[ESP+318],0
004060AC CALL <JMP.&MFC42.#941>
004060B1 PUSH ********.0041C868 ; ASCII " a "
004060B6 LEA ECX,DWORD PTR SS:[ESP+C]
004060BA CALL <JMP.&MFC42.#941>
004060BF LEA ECX,DWORD PTR SS:[ESP+C]
004060C3 PUSH ECX
004060C4 LEA ECX,DWORD PTR SS:[ESP+C]
004060C8 CALL <JMP.&MFC42.#941>
004060CD MOV EDX,DWORD PTR SS:[ESP+8]
004060D1 MOV ECX,DWORD PTR DS:[41D1C8]
004060D7 PUSH EDX
004060D8 CALL <JMP.&MFC42.#6199>
004060DD PUSH 0
004060DF MOV ECX,EBP
004060E1 CALL <JMP.&MFC42.#2645>
004060E6 LEA ECX,DWORD PTR SS:[ESP+8]
004060EA MOV DWORD PTR SS:[ESP+314],-1
004060F5 CALL <JMP.&MFC42.#800>
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:
ciao Gianluca! tempo fa acquistai un piccolo soft che però ha smesso di funzionare, il venditore e il suo sito sono spariti. ho provato con questa tua guida ma non riesco, in pratica appena avvio l'exe mi da "I/O error 1784".
potresti darmi una mano? grazie comunque, un saluto.
Ciao Giuseppe, certo mandami pure i dettagli per email
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
ciao Giuseppe! si fa tutto qui! :-) ma ovviamente solo se hai acquistato la regolare licenza d'uso! mandami una email al mio indirizzo di posta elettronica.. ciao!
Gianluca vuoi che ti passo l'eseguibile per posta sono circa 10 mb
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
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
Buonasera Gianluca, ho visto anchio la guida interessante. ho provato a seguire la sua guida ma non c´e verso di trovare questo crack che voglio, se ti mando il programma potrebbe gentilmente risolvere questo problema anche a me per favore?
rimango in attesa di una risposta !!!
Molti Saluti
Tarik
molto interessante. certo conoscere l'assembly aiuta tanto. complimenti.
Salve, ho provato un tutorial del genere in classe e i ragazzi si sono divertiti molto. Una squadra ha creato un file eseguibile dal C dove c'era una semplice istruzione if, mentre un'altra doveva invertire la condizione if.
Ci sono riusciti e sono molto contento che io abbia conosciuto questo sito, lo userò per le prossime lezioni di laboratorio.
Grazie mille
Sei bravo, grazie per questa lezione, vorrei sapere se sai cosa serve per creare crack per i giochi odierni!
è per pura curiosità, non voglio fare nulla di illegale, sono solo attratto da questo argomento
Buonasera Gianluca, la tua guida è molto interessante. Ho un programma che uso per lavoro comprato qualche anno fa, ho cambiato pc ed ho il seriale vecchio collegato ad un codice che credo sia legato al vecchio pc....tu potresti aiutarmi?
saluti
Antonio
@GG test (gianluca.ghettini.tester@gmail.com) risposta prova
@GG test (gianluca.ghettini.tester@gmail.com) prova risposta
ciao Gianluca avrei bisogno diun consiglio se da parte tua sei disposto?
@Antonio bondstreet.foreign@gmail.com Si certo, dimmi pure
bondstreet.foreign@gmail.com Si certo, dimmi pure
Ciao gianluca vorrei dei consigli, ho provato a eliminare due messaggi di registrazione online del seriale sul server, ma mi restituisce sempre degli errori, se ti va di sentirci in privato ti dirò quale programma si tratta.
Mi piacerebbe molto imparare per comprendere il funzionamento dei programmi!
Se dovessi acconsentire nel aiutarmi ti aggiungerò su whatsapp con il tuo numero in bacheca ok?
Se ti va di aiutarmi te ne sarò grato.