3D Mark 2005
procedure di generazione e controllo del seriale

Data

by "-=Götterdämerung=-"

 

05/08/2005

UIC's Home Page

Published by Quequero


Grazie gotter, bel tute!
Ma levami una curiosita'.... E' stata una scelta artistica quella di far derivare verso destra il tutorial man mano che si scende con la pagina oppure no? Nel dubbio l'ho lasciato cosi!
PS: ho notato che questa feature e' visibile solo da DreamWeaver, Firefox non la visualizza ;p



Gotterdamerung in IRCnet: #crack-it


Difficoltà

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

 


Introduzione

Ho comprato un pò di pezzi nuovi per il PC: scheda video, processore, scheda madre e RAM, quindi ho deciso che dovevo assolutamente testarli e ho scaricato questo spettacolare programma di benchmark. Quando ho visto la finestra di registrazione mi sono detto 'Beh, non so fare ancora nulla, ma potrebbe comunque servire da esercizio, proviamo!', e questo è il risultato..

Tools usati

Per questo tutorial vi servirà solamente OllyDBG, infatti debuggeremo e modificheremo il programma direttamente con questo versatilissimo debugger/decompiler.
Certo potrebbe essere _molto_ utile una connessione a banda larga, date le dimensioni del download... (283 MB!!)

URL o FTP del programma

Potete trovare questo famoso benchmark sul sito del produttore: http://www.futuremark.com/, la versione a cui facci riferimento in questo tutorial è la 1.2.0

Essay

Benissimo, siete pronti a cominciare? Scaricate la demo del programma, installatelo (alla richiesta del seriale lasciate in bianco) e una volta completata l'installazione lanciatelo.
Dopo lo splash screen apparirà una simpatica finestra che chiede solamente un seriale... bene, allora diamoglielo!
Lanciate OllyDBG e attaccate la finestra (da olly File -> Attach, poi scegliete dalla lista il processo con nome 3DMark05), per fortuna non ci appare nessun messaggio che indica che il file sia packato/criptato, quindi riusciremo a fare modifiche al programma senza doverlo unpackare (meno male, la lezione sul manual unpacking la devo ancora fare :D ).
Settate un breakpoint sulla funzione GetWindowTextA con il metodo che preferite, io ad esempio uso la command line di Que e bpx GetWindowTextA, quindi fate ripartire il programma con F9 e inserite un seriale a caso.
Dopo aver clickato sul bottone Register vi ritroverete in Olly, nel modulo USER32 (lo vedete dal titolo della finestra) che è quello che gestisce la maggior parte delle funzioni di windows, premete due volte ^F9 (Ctrl+F9, serve per eseguire il codice fino al successivo return) e poi F8 o F7 (per eseguire il comando successivo ovvero il RETN su cui vi troverete); a questo punto arriverete nel programma vero e proprio (lo vedete sempre dal titolo della finestra) in mezzo a questo codice:

...
0045C8BB   CALL   <JMP.&MFC71.#3761>
0045C8C0   LEA    ECX,SS:[ESP+4]      <= Ritorniamo qui
0045C8C4   CALL   DS:[<&MFC71.#876>]
0045C8CA   MOV    ESI,SS:[ESP+18]
0045C8CE   PUSH   EAX
0045C8CF   MOV    ECX,ESI
0045C8D1   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<char>,>;
0045C8D7   LEA    ECX,SS:[ESP+4]
0045C8DB   CALL   DS:[<&MFC71.#578>]
0045C8E1   MOV    ECX,SS:[ESP+8]
0045C8E5   MOV    EAX,ESI
0045C8E7   POP    ESI
0045C8E8   MOV    FS:[0],ECX
0045C8EF   ADD    ESP,10
0045C8F2   RETN   4
...

Questa parte però ci interessa poco... infatti quelle che vediamo sono tutte chiamate a funzioni che fanno parte di librerie esterne (MFC e MSVCP) e che quindi difficilmente comprenderanno la routine di generazione e controllo del seriale (fidatevi, è così in questo caso :D ), steppate oltre il RETN a 0045C8F2 premendo ^F9 e F8 e vi ritroverete in una parte del codice decisamente più importante:

...
0042452C   CALL   0045C880
00424531   LEA    ECX,SS:[EBP-4C]      <= Siamo arrivati qui
00424534   MOV    BYTE PTR SS:[EBP-4],2
00424538   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
0042453E   PUSH   0
00424540   PUSH   0064D34C
00424545   LEA    ECX,SS:[EBP-30]
00424548   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
0042454E   MOV    EDX,DS:[<&MSVCP71.std::basic_string<char,std::char_traits>
00424554   CMP    DS:[EDX],EAX
00424556   JE     SHORT 00424564
00424558   PUSH   EAX
00424559   PUSH   0
0042455B   LEA    ECX,SS:[EBP-30]
0042455E   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
00424564   MOV    EAX,SS:[EBP-1C]
00424567   PUSH   EAX
00424568   PUSH   0064D34C
0042456D   LEA    ECX,SS:[EBP-30]
00424570   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
00424576   MOV    ECX,DS:[<&MSVCP71.std::basic_string<char,std::char_traits>
0042457C   CMP    DS:[ECX],EAX
0042457E   JE     SHORT 00424591
00424580   MOV    EDX,SS:[EBP-1C]
00424583   SUB    EDX,EAX
00424585   PUSH   EDX
00424586   INC    EAX
00424587   PUSH   EAX
00424588   LEA    ECX,SS:[EBP-30]
0042458B   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
00424591   LEA    EAX,SS:[EBP-30]
00424594   PUSH   EAX
00424595   CALL   004104A0      <= Le CALL prima di questa sono tutte esterne al prog
0042459A   ADD    ESP,4
0042459D   TEST   AL,AL
0042459F   JE     00424643      <= se AL è 0 salta
004245A5   MOV    EAX,DS:[ESI+120]
004245AB   TEST   EAX,EAX
004245AD   MOV    BL,3
004245AF   MOV    SS:[EBP-4],BL
004245B2   JE     SHORT 004245BB
004245B4   MOV    ECX,ESI
004245B6   CALL   00424230
004245BB   PUSH   0064ACEC      ; ASCII "KeyCode"
004245C0   LEA    ECX,SS:[EBP-68]
004245C3   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
004245C9   LEA    ECX,SS:[EBP-30]
004245CC   PUSH   ECX
004245CD   LEA    EDX,SS:[EBP-68]
004245D0   PUSH   EDX
004245D1   MOV    BYTE PTR SS:[EBP-4],4
004245D5   CALL   0040FEF0
004245DA   ADD    ESP,8
004245DD   LEA    ECX,SS:[EBP-68]
004245E0   MOV    SS:[EBP-4],BL
004245E3   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
004245E9   PUSH   0064E79C      ; ASCII "Thank you for registering 3DMark05 Business!\n"
004245EE   LEA    ECX,SS:[EBP-68]
004245F1   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
004245F7   MOV    BYTE PTR SS:[EBP-4],5
004245FB   JMP    004246AD
00424600   PUSH   0064E75C      ; ASCII "Registration failed.\nYou do not have admin...
00424605   LEA    ECX,SS:[EBP-68]
00424608   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
0042460E   MOV    EDX,SS:[EBP-14]
00424611   PUSH   30
00424613   LEA    ECX,SS:[EBP-68]
00424616   PUSH   ECX
00424617   PUSH   EDX
00424618   MOV    BYTE PTR SS:[EBP-4],7
0042461C   CALL   0040AA40
00424621   ADD    ESP,0C
00424624   LEA    ECX,SS:[EBP-68]
00424627   MOV    BYTE PTR SS:[EBP-4],6
0042462B   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
00424631   MOV    EAX,00424637
00424636   RETN
...

Finalmente siamo arrivati alla routine che vogliamo attaccare, infatti all'indirizzo 00424595 viene chiamata la parte del programma che controlla che il nostro seriale sia nella forma corretta, ne genera uno valido e poi controlla che il nostro lo sia.
Quello che ci può far venire il sospetto che questa sia la chiamata giusta è il fatto che è la prima chiamata che troviamo che rimanga all'interno del codice del programma, quindi, visto che vi ho detto che il controllo non viene fatto esternamente, deve essere questa la funzione che ci serve. In generale comunque potete utilizzare un pò di intuito, zen, fortuna e, col tempo, esperienza.
Se utilizzate il plugin Ultra String Reference (click destro sul codice, Ultra String Reference -> Find ASCII) potrete anche notare che poche righe sotto la CALL incriminata ci sono delle stringhe piuttosto esplicative che riempiranno il mesasaggio di registrazione avvenuta, oppure un messaggio di errore nel caso in cui stiamo tentando di registrare il programma senza essere amministratori di sistema (che sicurezza che dà Windows...), inoltre nella parte successiva del codice (che non ho riportato per evitare troppa pesantezza) si possono notare anche il messaggio di registrazione per la versione Pro e il messaggio di seriale errato.
Il controllo che viene fatto su AL subito al ritorno dalla call viene utilizzato dal programma per controllare se il codice inserito è per una licenza Business oppure per una Professional e per spedirci eventualmente al messaggio di codice errato; al fine delle funzionalità del programma non ci sono differenze tra le due modalità di registrazione, a parte il fatto che la prima vi permetterà di pubblicare i risultati che otterrete, cosa che comunque vi sconsiglio visto come la otterrete, inoltre se proprio dobbiamo registrarlo, registriamolo per bene, no?
OK, allora diamoci dentro, arriviamo fino alla CALL in 00424595 premendo ripetutamente F8 ed entriamo al suo interno premendo F7.
Vi consiglio di settare un breakpoint sull'instruzione in cui arriverete e di disabilitare il bp su GetWindowTextA, in modo da poter ricominciare da qui nel caso in cui facciate ripartire il programma per eventuali tentativi.
All'interno della chiamata il codice di controllo del seriale non è molto complesso, ben di più invece lo è quello di generazione; da qui alla fine ho diviso l'intera CALL in più parti, in modo da poterla analizzare facilmente, vediamole una ad una:

...
004104A0   MOV    EAX,FS:[0]
004104A6   PUSH   -1
004104A8   PUSH   00630E41
004104AD   PUSH   EAX
004104AE   MOV    FS:[0],ESP
004104B5   SUB    ESP,40
004104B8   PUSH   EDI              <= Salva il valore in EDI
004104B9   MOV    EDI,SS:[ESP+54]  <= In ESP+54 c'è il seriale inserito
004104BD   MOV    ECX,EDI          <= fa puntare ECX al seriale
004104BF   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
004104C5   CMP    EAX,1D           <= confronta la lunghezza con 29
004104C8   JNZ    004107CC
004104CE   PUSH   5               <= carica la posizione
004104D0   MOV    ECX,EDI
004104D2   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
004104D8   CMP    BYTE PTR DS:[EAX],2D       <= e controlla che sia un meno
004104DB   JNZ    004107CC
004104E1   PUSH   0B
004104E3   MOV    ECX,EDI
004104E5   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
004104EB   CMP    BYTE PTR DS:[EAX],2D
004104EE   JNZ    004107CC
004104F4   PUSH   11
004104F6   MOV    ECX,EDI
004104F8   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
004104FE   CMP    BYTE PTR DS:[EAX],2D
00410501   JNZ    004107CC
00410507   PUSH   17
00410509   MOV    ECX,EDI
0041050B   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
00410511   CMP    BYTE PTR DS:[EAX],2D
00410514   JNZ    004107CC

Il primo blocco si occupa di controllare che il seriale inserito sia nella forma corretta e a sua volta si può dividere in due parti più piccole: la prima arriva fino a 004104C8 e nella sua esecuzione prima punta ECX alla zona di memoria in cui si trova il seriale, chiama una funzione (004104BF) che ne misura la lunghezza e la inserisce in EAX, quindi confronta EAX con 1Dh ovvero 29 decimale; se il seriale non è della lunghezza giusta ci fa saltare con l'istruzione successiva ad un messaggio di seriale errato.
La seconda parte del primo controllo va a cercare in alcune posizioni predefinite del seriale, che per la precisione sono i vari PUSH: 5h, Bh, 11h, 17h che corrispondono alle posizioni 6, 12, 18 e 24; ho scritto i commenti solo per il primo controllo, ma potete facilmente notare che hanno tutti la stessa identica struttura, e ogni volta il programma controlla che nella posizione specificata sia presente il carattere che corrisponde a 2Dh (CMP BYTE PTR DS:[EAX],2D) ovvero che il carattere in questione sia un meno '-'.
Adesso sappiamo quindi che il nostro seriale deve essere di 29 caratteri e ogni 5 caratteri deve esserci un meno, ma questo non sarà una grande sorpresa per chi ha osservato che poco dopo sono presenti, in ASCII, due seriali d'esempio...

0041051A   PUSH   ESI
0041051B   MOV    ESI,DS:[<&MSVCP71.??$?8DU?$char_traits@D@std@@V?$allocato>
00410521   PUSH   0064C90C      ; ASCII "QTDJK-AFKS2-J4AA5-FMRAA-YND8H"

00410526   PUSH   EDI
00410527   CALL   ESI           <= la funzione compara le due stringhe
00410529   ADD    ESP,8
0041052C   TEST   AL,AL         <= sono uguali?
0041052E   JNZ    004107B9      <= allora dai il messaggio di errore!
00410534   PUSH   0064C8EC      ; ASCII "B47QZ-XXVDT-CJCZT-CB473-5U8A0"

00410539   PUSH   EDI
0041053A   CALL   ESI
0041053C   ADD    ESP,8
0041053F   TEST   AL,AL
00410541   JNZ    004107B9

Passiamo quindi al secondo blocco e ci rendiamo conto che i due seriali non sono proprio di esempio, bensì vengono caricati in memoria e confrontati con quello da noi inserito... che gentili i programmatori della Futuremark!
Evidentemente questi devono essere dei seriali non regolari e molto usati per registrare in modo non legale il programma (copio spudoratamente un'espressione che ho trovato su un tutorial che ho letto per entrare nel mondo del cracking, non ricordo quale, ma chiedo scusa all'autore per aver deturpato le sue parole...), ovviamente se il confronto risultasse positivo verremmo spediti verso il messaggio di errore, ma se il risultato non fosse quello aspettato? :)

00410547   PUSH   EBX
00410548   LEA    ECX,SS:[ESP+30]
0041054C   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
00410552   LEA    ESI,SS:[ESP+30]
00410556   MOV    DWORD PTR SS:[ESP+54],0
0041055E   CALL   0040FD90       <= con questa chiamata viene generata una stringa
00410563   MOV    ECX,EDI
00410565   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
0041056B   PUSH   EAX
0041056C   LEA    ECX,SS:[ESP+24]
00410570   CALL   00410170       <= e questa prepara la memoria
00410575   MOV    ECX,EDI
00410577   MOV    BYTE PTR SS:[ESP+54],1
0041057C   XOR    EBX,EBX
0041057E   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
00410584   TEST   EAX,EAX
00410586   MOV    ESI,SS:[ESP+24]
0041058A   JBE    SHORT 004105C6
0041058C   LEA    ESP,SS:[ESP]

Nella terza parte viene preparata la memoria per la routine di controllo; questo blocco e i successivi due contengono la generazione e il controllo vero e proprio, viene infatti generata una stringa di 33 caratteri tramite la CALL in 0041055E, che verrà utilizzata nella parte successiva della funzione, quindi la CALL in 00410570 prepara un'intera zona di memoria su cui verranno scritti i risultati del controllo e inoltre con le altre CALL calcola diversi dati come la lunghezza del seriale (nonostante ci abbia già fatto un controllo...) e la lunghezza della stringa generata prima.
In effetti il programma non genererà un seriale corretto, e quindi non potremo fare fishing (andare a 'pescare' il seriale nella memoria usata dal programma), ma si servirà di questa stringa e dell'area di memoria allocata, nell'algoritmo di controllo.


00410590   /PUSH    EBX
00410591   |MOV     ECX,EDI
00410593   |CALL    DS:[<&MSVCP71.std::basic_string<char,std::char_traits<ch>
00410599   |MOVSX   EAX,BYTE PTR DS:[EAX]
0041059C   |PUSH    0
0041059E   |PUSH    EAX
0041059F   |LEA     ECX,SS:[ESP+38]
004105A3   |CALL    DS:[<&MSVCP71.std::basic_string<char,std::char_traits<ch>
004105A9   |MOV     ECX,DS:[<&MSVCP71.std::basic_string<char,std::char_trait>
004105AF   |CMP     EAX,DS:[ECX]      <= qui avviene il controllo del valore
004105B1   |JNZ     SHORT 004105B6
004105B3   |OR      EAX,FFFFFFFF      <= se è sbagliato in memoria viene scritto -1
004105B6   |MOV     DS:[ESI+EBX*4],EAX   <= se giusto invece viene scritto direttamente
004105B9   |MOV     ECX,EDI
004105BB   |INC     EBX
004105BC   |CALL    DS:[<&MSVCP71.std::basic_string<char,std::char_traits<ch>
004105C2   |CMP     EBX,EAX
004105C4   \JB      SHORT 00410590

Questa è la vera e propria routine di controllo del seriale, che ovviamente si presenta come un ciclo; ad ogni ripetizione viene caricato in EAX il nostro seriale tramite la call in 00410593 e poi ne viene estratta la prima lettera, e cancellata dalla locazione originale spostando la stringa di un byte a sinistra (00410599), praticamene ad ogni ciclo toglie la prima lettera, facendo si che al ciclo successivo la prima lettera sarà in realtà quella che al ciclo precedente era la seconda, certo è più facile fare un paio di cicli e vederlo che dirlo... e scusate se vi ho ingarbugliato le idee...
Successivamente, facendo un pesante uso delle funzioni esterne del modulo MSVCP71, viene generato un valore dal seriale e la stringa generata nella parte precedente; questo valore viene poi memorizzato nella zona di memoria preparata se risulta essere esatto, mentre altrimenti viene memorizzato il valore -1 (FFFFFFFF).
Se volete comprendere l'algoritmo di controllo per trovare un seriale valido dovrete addentrarvi in queste funzioni... e buona fortuna!
Le ultime quattro righe realizzano il ciclo: la CALL calcola la lunghezza del seriale (sempre 1D), questa viene confrontata con EBX che viene incrementato di uno ad ogni esecuzione del ciclo (due istruzioni sopra) e se risulta maggiore salta all'inizio del ciclo.


004105C6   PUSH   EBP
004105C7   LEA    ECX,SS:[ESP+34]
004105CB   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
004105D1   MOV    EDI,DS:[ESI+4]
004105D4   MOV    EDX,DS:[ESI]
004105D6   MOV    EBP,DS:[ESI+8]
004105D9   MOV    ECX,EAX
004105DB   MOV    EAX,DS:[ESI+C]
004105DE   ADD    EAX,EDI
004105E0   ADD    EAX,EDX
004105E2   ADD    EAX,EBP
004105E4   XOR    EDX,EDX
004105E6   DIV    ECX
004105E8   LEA    ECX,SS:[ESP+34]
004105EC   MOV    EDI,EDX
004105EE   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
004105F4   MOV    EBX,DS:[ESI+18]
004105F7   MOV    EDX,DS:[ESI+24]
004105FA   MOV    EBP,DS:[ESI]
004105FC   MOV    ECX,EAX
004105FE   MOV    EAX,DS:[ESI+20]
00410601   ADD    EAX,EBX
00410603   MOV    EBX,DS:[ESI+1C]
00410606   ADD    EAX,EDX
00410608   MOV    EDX,DS:[ESI+8]
0041060B   ADD    EAX,EBP
0041060D   ADD    EAX,EBX
0041060F   ADD    EAX,EDX
00410611   XOR    EDX,EDX
00410613   DIV    ECX
00410615   LEA    ECX,SS:[ESP+34]
00410619   MOV    EBX,EDX
0041061B   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
00410621   MOV    EBP,DS:[ESI+38]
00410624   MOV    EDX,DS:[ESI+30]
00410627   MOV    ECX,EAX
00410629   MOV    EAX,DS:[ESI+C]
0041062C   ADD    EAX,EBP
0041062E   MOV    EBP,DS:[ESI+3C]
00410631   ADD    EAX,EDX
00410633   MOV    EDX,DS:[ESI+4]
00410636   ADD    EAX,EBP
00410638   MOV    EBP,DS:[ESI+34]
0041063B   ADD    EAX,EDX
0041063D   ADD    EAX,EBP
0041063F   XOR    EDX,EDX
00410641   DIV    ECX
00410643   LEA    ECX,SS:[ESP+34]
00410647   MOV    EBP,EDX
00410649   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
0041064F   MOV    EDX,DS:[ESI+50]
00410652   MOV    ECX,EAX
00410654   MOV    EAX,DS:[ESI+40]
00410657   ADD    EAX,EDX
00410659   ADD    EAX,DS:[ESI+48]
0041065C   ADD    EAX,DS:[ESI+54]
0041065F   ADD    EAX,DS:[ESI+4C]
00410662   XOR    EDX,EDX
00410664   DIV    ECX
00410666   LEA    ECX,SS:[ESP+34]
0041066A   MOV    SS:[ESP+10],EDX
0041066E   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
00410674   MOV    EDX,DS:[ESI+10]
00410677   MOV    ECX,EAX
00410679   MOV    EAX,DS:[ESI+40]
0041067C   ADD    EAX,EDX
0041067E   ADD    EAX,DS:[ESI+28]
00410681   ADD    EAX,DS:[ESI+4]
00410684   XOR    EDX,EDX
00410686   DIV    ECX
00410688   LEA    ECX,SS:[ESP+34]
0041068C   MOV    SS:[ESP+14],EDX
00410690   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
00410696   MOV    EDX,DS:[ESI+C]
00410699   MOV    ECX,EAX
0041069B   MOV    EAX,DS:[ESI+40]
0041069E   ADD    EAX,EDX
004106A0   ADD    EAX,DS:[ESI+58]
004106A3   ADD    EAX,DS:[ESI+28]
004106A6   XOR    EDX,EDX
004106A8   DIV    ECX
004106AA   LEA    ECX,SS:[ESP+34]
004106AE   MOV    SS:[ESP+18],EDX
004106B2   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
004106B8   MOV    EDX,DS:[ESI+18]
004106BB   MOV    ECX,EAX
004106BD   MOV    EAX,DS:[ESI+58]
004106C0   ADD    EAX,EDX
004106C2   ADD    EAX,DS:[ESI+60]
004106C5   ADD    EAX,DS:[ESI]
004106C7   ADD    EAX,DS:[ESI+40]
004106CA   XOR    EDX,EDX
004106CC   DIV    ECX
004106CE   LEA    ECX,SS:[ESP+34]
004106D2   MOV    SS:[ESP+1C],EDX
004106D6   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
004106DC   MOV    EDX,DS:[ESI+64]
004106DF   MOV    ECX,EAX
004106E1   MOV    EAX,DS:[ESI+1C]
004106E4   ADD    EAX,EDX
004106E6   ADD    EAX,DS:[ESI+58]
004106E9   ADD    EAX,DS:[ESI+8]
004106EC   ADD    EAX,DS:[ESI+60]
004106EF   XOR    EDX,EDX
004106F1   DIV    ECX
004106F3   LEA    ECX,SS:[ESP+34]
004106F7   MOV    SS:[ESP+20],EDX
004106FB   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
00410701   MOV    EDX,DS:[ESI+6C]
00410704   MOV    ECX,EAX
00410706   MOV    EAX,DS:[ESI+30]
00410709   ADD    EAX,EDX
0041070B   ADD    EAX,DS:[ESI+58]
0041070E   ADD    EAX,DS:[ESI+20]
00410711   ADD    EAX,DS:[ESI+64]
00410714   XOR    EDX,EDX
00410716   DIV    ECX

Questa parte della CALL prepara i registri calcolando quali siano i valori corretti che si dovrebbero ottenere con un seriale valido; i registri verranno poi usati nella parte successiva, per essere confrontati con i risultati scritti in memoria.
La parte esaminata è anche l'ultima che riguarda la generazione dei valori da controllare, insieme ai due 'pezzi' precedenti; per via dell'uso piuttosto massiccio delle librerie esterne lo studio dell'algoritmo, come ho già detto, risulta piuttosto astioso!



00410718   CMP    DS:[ESI+10],EDI
0041071B   JNZ    SHORT 00410789
0041071D   CMP    DS:[ESI+28],EBX
00410720   JNZ    SHORT 00410789
00410722   CMP    DS:[ESI+40],EBP
00410725   JNZ    SHORT 00410789
00410727   MOV    EAX,SS:[ESP+10]
0041072B   CMP    DS:[ESI+58],EAX
0041072E   JNZ    SHORT 00410789
00410730   MOV    ECX,SS:[ESP+14]
00410734   CMP    DS:[ESI+60],ECX
00410737   JNZ    SHORT 00410789
00410739   MOV    EAX,SS:[ESP+18]
0041073D   CMP    DS:[ESI+64],EAX
00410740   JNZ    SHORT 00410789
00410742   MOV    ECX,SS:[ESP+1C]
00410746   CMP    DS:[ESI+68],ECX
00410749   JNZ    SHORT 00410789
0041074B   MOV    EAX,SS:[ESP+20]
0041074F   CMP    DS:[ESI+6C],EAX
00410752   JNZ    SHORT 00410789
00410754   CMP    DS:[ESI+70],EDX
00410757   JNZ    SHORT 00410789

Per l'appunto, questa è l'ultima parte importante della funzione di controllo e penso che sia talmente semplice da non necessitare di commenti: vengono effettuati ben nove controlli di fila, caricando di volta in volta nel registro di turno il valore corretto calcolato nella parte precedente e confrontandolo con quelli ottenuti tramite il seriale da noi inserito e scritti in memoria (da ESI+10 a ESI+70, è la memoria allocata tre blocchi fa), ovviamente qualora uno dovesse risultare non valido verremmo spediti alla beggar off tramite i relativi JNZ, potete infatti notare che tutti i salti puntano alla stessa istruzione, che in realtà è una picola routine che vedremo alla fine e che fa sì che al ritorno da questa funzione venga presentato il messaggio di seriale errato.


00410759   LEA    ECX,SS:[ESP+24]
0041075D   CALL   004432B0
00410762   LEA    ECX,SS:[ESP+34]
00410766   MOV    DWORD PTR SS:[ESP+58],-1
0041076E   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
00410774   POP    EBP
00410775   POP    EBX
00410776   POP    ESI
00410777   MOV    AL,1   <= questo setta AL a 1
00410779   POP    EDI
0041077A   MOV    ECX,SS:[ESP+40]
0041077E   MOV    FS:[0],ECX
00410785   ADD    ESP,4C
00410788   RETN
00410789   LEA    ECX,SS:[ESP+24]
0041078D   CALL   004432B0
00410792   LEA    ECX,SS:[ESP+34]
00410796   MOV    DWORD PTR SS:[ESP+58],-1
0041079E   CALL   DS:[<&MSVCP71.std::basic_string<char,std::char_traits<cha>
004107A4   POP    EBP
004107A5   POP    EBX
004107A6   POP    ESI
004107A7   XOR    AL,AL   <= questo invece lo azzera
004107A9   POP    EDI
004107AA   MOV    ECX,SS:[ESP+40]
004107AE   MOV    FS:[0],ECX
004107B5   ADD    ESP,4C
004107B8   RETN
...

Eccoci quindi alla fine, la parte fino al RETN in 00410788 viene eseguita se tutto è andato bene e quindi abbiamo inserito il seriale corretto; vengono ricaricate le condizioni in cui era il programma prima della CALL che abbiamo esaminato e notate come AL venga settato a 1 in 00410777, per soddisfare il controllo che avevamo visto all'inizio e quindi mostrare il messaggio di registrazione avvenuta.
Se invece qualcosa è andato male si finisce nella parte successiva (l'istruzione è quella a cui puntano tutti i JNZ dello scorso blocco); vengono di nuovo ripristinate le condizioni iniziali, ma AL questa volta viene XORato con se stesso, un metodo comune per portare il suo valore a 0 e fare si che il controllo di cui sopra fallisca, portandoci infine al messaggio di seriale errato.

Adesso che abbiamo analizzato attentamente l'intero controllo potete scegliere uno dei tanti metodi di attacco, il più semplice è senz'altro quello di prendere uno dei due seriali che gli autori del programma ci hanno detto di non usare e NOPpare il jump di controllo, oppure trasformarlo in un JZ, permettendovi così di ottenere una facile registrazione; in alternativa potete NOPpare tutti i JNZ dell'ultima parte, in modo da rendere vani tutti i controlli e poter inserire un qualsiasi seriale che sia nella forma corretta (ricordo 5 gruppi di 5 caratteri separati da meno) come potrebbe essere ad esempio -=Göt-terdä-merun-g_RuL-Ez!=- (quanto sono modesto, eh? :D ), oppure se volete sbizzarrirvi con un seriale fantasioso potete NOPpare anche i jump relativi ai controlli iniziali sulla lunghezza del seriale e la presenza dei meno, infine, lavorino da nulla... potete sbizzarrirvi a trovare un seriale analizzando tutta la routine di generazione e di controllo, questo è sicuramente il metodo che vi porterà via più tempo.
Dopo aver scelto il vostro metodo preferito modificate il codice direttamente da Olly: evidenziate la riga che avete intenzione di modificare e premete spazio, apparirà una finestra in cui inserire il codice con cui volete sostituire l'originale.
Una volta effettuate tutte le modifiche necessarie fate click destro sulla finestra del codice e selezionate 'Copy to Executable -> All modifications', quindi selezionate 'Copy All' dalla finestra di dialogo e chiudete la finestra che appare successivamente (con evidenziate le modifiche), clickate 'Si' e scegliete un nome per il file patchato.
E' importante effettuare le modifiche al file e non registrarsi solo cambiando un paio di flags durante l'esecuzione perché ogni volta che 3D Mark viene riavviato o tutte le volte che si seleziona un'opzione utilizzabile solamente nelle versioni registrate, il controllo viene nuovamente effettuato.

Ecco fatto, avete finito, adesso fate puntare la shortcut sul vostro desktop all'eseguibile patchato e mettete alla prova il vostro PC!



See ya on the last days...
-=Götterdämerung=-

Note finali

Un ringraziamento a tutta la UIC, in particolare a Quequero e AndreaGeddon per i loro tutorial e corsi per niubbi che mi hanno aiutato tantissimo, infatti questo tutorial è fatto con un pò meno fortuna e decisamente più studio di quello precedente :)
Un ringraziamento speciale anche alla Futuremark per questo bell'esercizio durato all'incirca una settimana.

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 che ogni sviluppatore 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