3D Mark 2005
From UIC
3D Mark 2005 - procedure di generazione e controllo del seriale
Contents |
| Infos | |
|---|---|
| Author: | Gotterdamerung |
| Email: | die.gotterdamerung(AT)gmail.com |
| Website: | |
| Date: | 05/08/2005 (dd/mm/yyyy) |
| Level: |
|
| Language: | Italian |
| Comments: | Que: Grazie gotter, bel tute!
Ma levami una curiosita'.... È 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 è visibile solo da DreamWeaver, Firefox non la visualizza ;p |
Introduzione
Ho comprato un po' 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
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!!)
Link e Riferimenti
Potete trovare questo famoso benchmark sul sito del produttore: http://www.futuremark.com/, la versione a cui faccio 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 po' 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'istruzione 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...
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...)(PS: ora editando ho notato che è uno dei primi tute riformattati del wiki, ovvero quello di Bender0 su Hex Workshop 4.23), ovviamente se il confronto risultasse positivo verremmo spediti verso il messaggio di errore, ma se il risultato non fosse quello aspettato? :)
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.
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.
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!
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.
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.
È 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!
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 po' 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
I documenti qui pubblicati sono da considerarsi pubblici e liberamente distribuibili, a patto che se ne citi la fonte di provenienza. Tutti i documenti presenti su queste pagine sono stati scritti esclusivamente a scopo di ricerca, nessuna di queste analisi è stata fatta per fini commerciali, o dietro alcun tipo di compenso. I documenti pubblicati presentano delle analisi puramente teoriche della struttura di un programma, in nessun caso il software è stato realmente disassemblato o modificato; ogni corrispondenza presente tra i documenti pubblicati e le istruzioni del software oggetto dell'analisi, è da ritenersi puramente casuale. Tutti i documenti vengono inviati in forma anonima ed automaticamente pubblicati, i diritti di tali opere appartengono esclusivamente al firmatario del documento (se presente), in nessun caso il gestore di questo sito, o del server su cui risiede, può essere ritenuto responsabile dei contenuti qui presenti, oltretutto il gestore del sito non è in grado di risalire all'identità del mittente dei documenti. Tutti i documenti ed i file di questo sito non presentano alcun tipo di garanzia, pertanto ne è sconsigliata a tutti la lettura o l'esecuzione, lo staff non si assume alcuna responsabilità per quanto riguarda l'uso improprio di tali documenti e/o file, è doveroso aggiungere che ogni riferimento a fatti cose o persone è da considerarsi PURAMENTE casuale. Tutti coloro che potrebbero ritenersi moralmente offesi dai contenuti di queste pagine, sono tenuti ad uscire immediatamente da questo sito.
Vogliamo inoltre ricordare che il Reverse Engineering è uno strumento tecnologico di grande potenza ed importanza, senza di esso non sarebbe possibile creare antivirus, scoprire funzioni malevoli e non dichiarate all'interno di un programma di pubblico utilizzo. Non sarebbe possibile scoprire, in assenza di un sistema sicuro per il controllo dell'integrità, se il "tal" programma è realmente quello che l'utente ha scelto di installare ed eseguire, né sarebbe possibile continuare lo sviluppo di quei programmi (o l'utilizzo di quelle periferiche) ritenuti obsoleti e non più supportati dalle fonti ufficiali.