Carmageddon TDR2000
Die  SafeDisc  Die

Data

by "AndreaGeddon"

 

22/01/2001

UIC's Home Page

Published by Quequero


Ecco il mio primo tute scritto nel nuovo form!

Qualche mio eventuale commento sul tutorial :)))

Visto che non sei più Founder questa zona ti è interdetta :-P

<AndreaGeddon> e che siano buoni i commenti!!!
<Quequero> "Visto che non sei più Founder questa zona ti è interdetta :-P"
<Quequero> vabbuo allora non te lo commento :)
<AndreaGeddon> vabbè, concedo di commentare!
 
Per prima cosa non capisco perchè hai messo come livello "Master", ovviamente vuol dire che tu sei un master...Ma se NON ricordo male l'altro giorno mi chiedesti come si facevano le divisioni in asm....Quindi non so dove tu abbia copiato questa cosa....Poi il FOUNDER di fatto è mio visto che è come un boomerang, prima o poi torna, e cmq ieri sono andato al bowling e non c'eri, quindi ho vinto il Founder a tavolino per la SECONDA volta, cosa? Non vuoi ridarmelo? Vabbe, parlo con la madre di una certa amica mia...Voglio vedere poi come passi analisi :)
Stronzate a parte, complimenti per il tute che è di buon livello (anche se mi ha portato via quasi tutto il pomeriggio per leggerlo :) e cmq è spiegato molto bene, ancora complimenti andre :)

Un grazie a ZeroByte per la nuova veste della UIC :-)

....

Home page: http://www.andreageddon.com
E-mail: [email protected]
irc.azzurra.it   #crack-it

....

Difficoltà

( )NewBies ( )Intermedio ( )Avanzato (x)Master

 

Che dire, SafeDisc ormai è ben conosciuto. Con Carmageddon TDR2000 troviamo l'ultima versione del SafeDisc (nel momento in cui scrivo, ovvio), e devo dire che la IT è stata incasinata davvero bene. L'idea è sempre quella di usare il CD originale per decrittare le sezioni di codice e dati del relativo file .icd. La parte brutta verrà al momento di ripristinare la IT e IAT.


Carmageddon TDR2000
Die  SafeDisc  Die
Written by AndreaGeddon

Introduzione

Il classico tipo di gioco che piace a me, dove la violenza gratuita viene premiata :-) Un pò di sana violenza. Che peccato che con la mia Savage4 ho un difetto: i pedoni sono invisibili :-( Brutti bastardi, non lo scrivono mica sulla confezione! Vabbè, almeno posso divertirmi con SafeDisc...

Tools usati

SoftIce
IceDump
FrogSice
WDasm
un HexEditor
un PEditor

URL o FTP del programma

Boh, il gioco me l'ha prestato un amico.

Notizie sul programma

Il precedente Carmageddon aveva una vecchia versione di Safedisc, ora le cose sono cambiate. Per il decrypt delle sezioni di dati e codice non c'è problema. Per la IT invece la cosa si complica, perchè per poterla dumpare bisogna o modificare il loader per mettere nella IAT i giusti indirizzi (ma non è una buona soluzione), o vedere a mano come il Dplayer risolve le funzioni importate e calcolarsi a mano tutti i corretti valori per i moduli KERNEL32 e USER32 (quello che faremo). In particolare vedremo come bisogna decriptare gli OriginalFirstThunk per ricavarci i nomi delle funzioni (e quindi avere una IT valida), ed infine dovremo scrivere un call-fixer, visto che tramite la stessa call il programma richiama diverse API.

Essay

Bene, iniziamo il nostro attacco. Prima di tutto individuiamo la protezione. Se diamo una occhiata nella directory principale del gioco troveremo i file TDR2000.exe, TDR2000.icd, DPLAYERX.dll, e altri tipici del SafeDisc. Ora possiamo saricarci l'ultimo un-safedisc di R!sc e decrittare il gioco con un click (bada che al momento in cui scrivo l'ultimo unsafedisc disponibile non funziona con questo gioco), oppure possiamo scegliere la via del manual unpacking, ed è proprio questo che faremo. Innanzitutto un paio di informazioni generali sul SafeDisc: il principale .exe (337 kb) è solo il loader, che si occupa di verificare la presenza del CD e di decriptare il file del gioco vero proprio: il file .icd (2.581 kb). Per decriptare il file .icd, il loader usa come parametro un valore letto dalla stringa di identificazione del CD-Rom. Tale stringa si trova in una parte non raggiungibile dal masterizzatore, quindi con un normale processo di masterizzazione non sarà possibile copiarla (almeno credo). Tuttavia la debolezza di questo sistema è che la stringa di identificazione è uguale per tutti i CD di Carmageddon TDR2000, infatti è impressa dalla casa che ha fabbricato il CDRom ed in genere è un codice identificativo della casa stessa. Basterebbe quindi trovare un CDRom vuoto fabbricato dalla stessa casa che ha fabbricato i CD per TDR2000, fare la copia... et voilà. Un gioco perfettamente funzionante anche con un Safedisc su cd masterizzato :-) Se non ci credete chiedetelo a Que, magari vi spedirà il suo NFS tarocco :-P. Cmq noi siamo qui per decrittarci manualmente il gioco. Come appena detto, la stringa di identificazione del CD originale costituisce un parametro indispensabile per il decrypt del file ICD, quindi senza il CD originale la cosa sarebbe mooolto dura. Ma voi direte: e allora come fa R!sc con i suoi UnSafedisc? Dipende dalle versioni, cmq si tratta essenzialmente di un BruteForcer. Noi però abbiamo il CD originale e non dobbiamo preoccuparci di tutto questo. E' ora di riscaldare il softice! Prima di tutto caricate IceDump, ci servirà per poterci dumpare tutto quello che vogliamo. Ora proviamo ad eseguire il gioco... Unload The Debugger Then Try Again. Suona come una minaccia. Vabbè, manco serve dirlo, caricate il FrogSice e avrete una preoccupazione in meno. Ora carichiamo il gioco e questo partirà. La prima cosa che dobbiamo fare è arrivare all'entry point del file ICD: quando ci saremo avremo una copia intatta delle sezioni codice e data già decrittate, quindi le dumperemo. Diamo un'occhiata al PE del file ICD, ecco le informazioni che dobbiamo tenere a mente:

Entry Point:  0018CE7B  (VA = 0058CE7B)

NOME VIRTUAL SIZE VIRTUAL OFFSET RAW SIZE RAW OFFSET
.text 001AD54D 00001000 001AE000 00001000
.rdata 00019E3D 001AF000 0001A000 001AF000
.data 000D0160 001C9000 00033000 001C9000
.rsrc 0008879E 0029A000 00089000 001FC000

visto che il VirtualOffset e il RawOffset della prima sezione coincidono possiamo considerare tutti gli RVA come VA - ImageBase (file alignment = 1000 -> allineamento perfetto) ed è quello che farò quando farò calcoli con valori vari. Questo ci renderà più semplici i calcoli, ma badate che non è sempre così. Non ho riportato le characteristics delle sezioni, cmq è facile intuire che la text è di codice, la rdata è quella che contiene la IAT, la data è la sezione dei dati e rsrc delle risorse. Ora se con un qualsiasi resource editor (io ho usato exescope) apriamo il file icd vediamo che tutte le risorse sono in chiaro, quindi tale sezione non è criptata. Le sezioni criptate saranno quindi la text e la data. Anche la rdata la dovremo modificare, ma questo sarà il passo finale del tutorial. Dobbiamo notare subito una cosa: la sezione data ha una discrepanza nel valore del VirtualSize e RawSize. Con un VirtualSize di D0160 e un FileAlignment a 1000 avremmo dovuto avere un RawSize di D1000, invece lo abbiamo a 33000. Cosa significa questo? Che la sezione data in memoria occupa molto più spazio di quanto non occupi nel file fisico. Questo ce lo dobbiamo ricordare quando andremo a dumpare la sezione data: dovremo stare attenti a dumpare solo i 33000 bytes che effettivamente sono presenti nel file fisico, infatti i rimanenti bytes verranno allocati a runtime, ma a quel punto la sezione data sarà stata modificata dal programma stesso e non ci sarà più utile. Bene, la nostra analisi iniziale è terminata, eravamo rimasti al punto di cercare l'EP originale del gioco. Sappiamo che l'EP sta a 0058CE7B, quindi o ci sobbarchiamo tuuutto il loader finchè dopo interminabili routines di decrypt ci porta all'EP, oppure ci arrangiamo qualche metodo. Ho provato con BPR ma a me non ha funzionato (forse perchè i processi del loader e dell'icd sono distinti), quindi ricorriamo a una leim maniera: facciamo partire il gioco e premiamo ctrl+d in continuazione. Ogni volta il softice ci apparirà in un processo, che può essere KERNEL, DPLAYER etc... dopo qualche tentativo dovremmo riuscire a beccare il processo del TDR2000. Nota: il processo dovrà essere quello del gioco e NON quello del loader. Per essere sicuri basta che prima di iniziare a breakare a caso aspettiate che il gioco mandi la scritta "caricamento in corso". Una volta che siete nel processo giusto, digitate

u 0058CE7B

e nella finestra del codice vi apparirà la linea cercata. Se ci trovate tutti "INVALID" allora le pagine non sono ancora state caricate. Potremmo caricarle noi col PAGEIN, ma è meglio di no, quindi ritornate in esecuzione e continuate a breakare finchè oltre che arrivare al processo giusto troverete alla linea 0058CE7B non più "INVALID" ma tante belle istruzioni :-). In particolare dovrebbe iniziare con la classica struttura

 

0058CE7B   PUSH   EBP

                     MOV    EBP,  ESP

 

questo codice ha senso, quindi è già stato decrittato. Ora possiamo settarci un breakpoint semplicemente clickandoci sopra. Questo sarà il breakpoint sull'Original Entry Point del gioco, non cancellatelo mai, così ogni volta che vogliamo cominciare a esaminare il gioco non dobbiamo rifarci tutto il trafilo del break casuale. Adesso potete chiudere e riavviare il gioco, aspettate un pò e il softice breakerà sull'EP appena scoperto. Siamo all'inizio del programma, abbiamo le sezioni di dati e codice pronte pronte per essere dumpate. Ora tocca a IceDump (l'avete caricato vero?). Dumpiamoci le sezioni di codice, dati e rdata col comando dump:

 

/DUMP 00401000  1AD54D   c:\carma3.txt     (-> sezione .text)

/DUMP 005AF000  1A000     c:\carma3.rda     (-> sezione .rdata)

/DUMP 005C9000  33000      c:\carma3.dat      (-> sezione .data)

 

abbiamo su C:\  i tre file corrispondenti alle sezioni dumpate. Adesso li dobbiamo sostituire al programma originale, cioè al file .ICD. ATTENZIONE! Se avete dumpato la .text mentre il breakpoint sull'entry point era attivo otterrete un INT 3 sull'entry point al posto del PUSH EBP. Ricordatevi di rimetterlo a posto. Dunque prendiamo il file icd, copiamolo e rinominiamolo in cracked.exe (o quello che volete). Con un HexEditor e un semplice Copia/Incolla non vi sarà difficile sostituire le sezioni dumpate alle sezioni presenti. In particolare basta seguire la tabella precedente: la sezione text inizierà all'offset 1000, quindi selezionate 1AD54D bytes (tutti quelli della sezione esclusi gli zeri inseriti come padding), cancellateli e poi incollateci tutti bytes del file carma3.txt. Se alla fine il file non è correttamente allineato come prima allora avete sbagliato qualcosa. Cmq notate che gli zeri del padding non sono stati decriptati, il che ci fa pensare che non siano degli allocchi totali. Infatti alcuni crypter fanno lo sbaglio di cryptare anche gli zeri del padding, lasciandoci così visibile ad occhio nudo la mask utilizzata, che in caso di un algoritmo non troppo complesso ci può far capire come decriptare il file senza neanche ricorrere al softIce! Ma questa è un'altra storia. Cmq, ora abbiamo un file con le sezioni data e text corrette. Prima di sostituire anche la RDATA andiamo a disassemblare il file. Se avete fatto tutto correttamente vedrete che avrete le string & dialog references, e la sezione di codice presenta un codice sensato e non istruzioni sconnesse da ogni logica. Notate che anche le Import ci sono, e sono anche referenziate correttamente (cioè il disassemblatore le riconosce e hanno i giusti valori nella IT). Però non ci sono le import del Kernel32 e di User32. Male. Se sostituiamo la sezione .rdata e ridisassembliamo il file (ammazza quanto ci mette), adesso avremo sì le reference delle funzioni importate da Kernel e User, ma non sappiamo quali. Avremo solo una sfilza di

KERNEL32.KERNEL32  e   USER32.USER32

mmmm. Però almeno ora le funzioni ci sono, anche se non sappiamo quali. Rimane l'ultimo passo, riuscire a ricostruire la IT valida per queste import. In effetti non sarà molto facile. Prima di tutto andiamo a dare una occhiata ai descrittori della IT per i moduli KERNEL e USER. Ecco cosa troviamo:

 

Original First Thunk Time Date Stamp Forwarder Chain Name RVA First Thunk (IAT)
00 00 00 00 B1 AB 46 35 00 00 F7 BF 42 79 1C 00 8C F0 1A 00
00 00 00 00 A3 38 6E 35 00 00 F5 BF 16 7D 1C 00 50 F2 1A 00

attenti alla notazione intel:  B1 AB 46 35 = 0x3546ABB1. La prima riga corrisponde a KERNEL e la seconda a USER, sono rispettivamente il 3° e 4° modulo importato, indi saranno il terzo e quarto descrittore nella struttura dei descrittori della IT  (offset: 1C7248). Come vediamo il valore di OriginalFirstThunnk è zero per entrambi. Questo valore doveva contenere il puntatore ai nomi delle funzioni importate. Questo vuol dire che non ci sono i nomi?? NO!! Vuol dire che il puntatore è zero, ma l'array di strutture dei descrittori delle funzioni (HINT | NAME) deve esserci. Per vedere se c'è basta che andiamo a vedere dove sono storati tutti i nomi di funzioni importate. Basta infatti cercare gli OriginalFirstThunk degli altri moduli: quello che vedremo è che per i moduli noti i nomi di funzioni sono presenti, e in mezzo a questi ci sono delle zone di bytes sensa senso. Questi bytes sono i nomi delle funzioni che stiamo cercando, però sono criptati. Cmq se avete inserito la nuova sezione rdata troverete in fondo alla lista di tutti i nomi un'altra zona di bytes criptati, e anche questa è importante. Ora siamo proprio nel vivo dell'operazione: per capire in che modo vanno decrittati i nomi delle funzioni e come viene ottenuto l'entry point originale basta che spiamo il safedisc in esecuzione :-). Lanciamo il gioco, e se avete lasciato attivo il break sull' EP il softice breakerà. Saremo qui:

 

0058CE7B   Entry Point

                     un pò di codice

0058CEA1   CALL  [005AF148]

 

come vediamo abbiamo subito subito una call sospetta. Non è una call ad una delle funzioni note, altrimenti il disassembler l'avrebbe riconosciuta, quindi è una call del modulo KERNEL o USER. Bene arriviamoci sopra e steppiamoci dentro con F8. Ci porterà alla linea

 

00EBA688    xxx

                     un pò di codice spazzatura

00EBA69A   CALL  [00EBA6C3]

 

di nuovo una call sospetta. Stavolta ci manda nel processo del DPLAYERX.DLL alla linea  00973270. All'inizio avevo erroneamente pensato che questo codice del Dplayer fosse una funzione importata da TDR, solo che non vedendo il modulo dplayer nelle sue import ero andato in panico :-) La routine a cui siamo arrivati si occupa di:

1-  Decriptare il nome della funzione chiamata

2-  Fornire l'address dell'entry point di tale funzione

3-  Tramite un RET saltare al codice della funzione (dopo aver ricriptato il nome della funzione)

4-  Infine dalla funzione si torna al programma chiamante

bello vero? Da vero bastardo prima decripta la funzione, poi con GetProcAddress ne ottiene l'address, quindi ricripta il nome della funzione, così dal processo del TDR2000 noi non vediamo niente e non ci rimane la IT bella decrittata e pronta per essere dumpata. A questo punto dobbiamo ingegnarci. Come vedremo tra poco basterà seguire il codice del processo del DPLAYER per vedere come decrittare i nomi delle funzioni (il che si riduce a uno xor di ogni byte con il byte precedente :-). Cmq nonostante vedremo come decrittarci a mano i dati, non possiamo mica starci a decrittare tutti i nomi di funzioni a mano! Potremmo proseguire l'analisi, arrivare al punto in cui i nomi vengono ri-criptati, e saltarlo, in modo da lasciare intatti tutti i nomi decriptati e dumparceli. Ovviamente non tutte le funzioni vengono chiamate (e quindi decrittate) a runtime, ma la maggior parte si, quindi quelle che rimarranno poi dovremmo comunque decriptarle a mano. Dico POTREMMO fare come appena detto, ma in realtà sarà più comodo scriverci un decrypter a mano. Lo vedremo tra poco. Voi direte: ma perchè tutto questo accanimento sui nomi delle funzioni? Non possiamo semplicemente sniffarci gli address degli entry point e schiaffarli nella IAT? Beh, questa non sarebbe una vera soluzione. Funzionerebbe sul vostro stesso computer, in quanto al 99% delle volte il windogs mappa sempre allo stesso address le API, però su un altro computer l'IAT non risulterebbe valida (a meno che pure quel computer allochi le API sempre allo stesso posto, ma è una probabilità abbastanza remota). Se invece otteniamo i nomi delle funzioni, possiamo ripristinare tutta la IT e possiamo anche fare a meno di aggiustare la IAT: il PE-Loader infatti userà gli OriginalFirstThuk per ottenere gli EP delle funzioni e sovrascriverli ai presenti FirstThunk (e quindi non ci frega se sono errati). Così avremmo un eseguibile perfettamente funzionante su qualsiasi computer. Bene, spostiamo l'analisi al DPLAYER e vediamo come funziona il tutto. Come noterete l'80% del codice è un inutile morphismo, quindi riporterò solo alcune linee, quelle più importanti. Bene, eravamo arrivati alla linea 00973270 del DPLAYER, quindi iniziamo l'analisi:

 

00972370   start

...

00973529   CALL    00972610      dopo questa call vi verrà restituito in eax l'address della funzione

...

00973582   CALL    xxxxxxxxx       questo vi fa uscire dal softice e continua l'esecuzione del programma


fin qui ci siamo. La prima delle due call ci restituisce in eax (o edx) l'address dell'entry point della funzione che avrebbe dovuto essere stata chiamata da TDR2000, volendo potete fare D (o U) di tale valore per vedere che funzione è. Nel nostro caso (stiamo esaminando la prima funzione) dovrebbe essere GetVersion. Poi continuando ed eseguendo la seconda call riportata il programma continua l'esecuzione. Se provate a entrare nella seconda call, vedrete che dopo un pò di codice arrivate a un RET, solo che tale ret non vi riporta da dove venivate ma vi spedisce al codice della API. Quindi non ci rimane che esaminare la prima call per vedere come viene ottenuto l'address, e quindi come fa a ricavare il nome della funzione:

 

00976210    start

...

00972735   MOV  EAX, [33E4558B]    eax = puntatore al nome criptato (= edx)

           XOR  EAX, EAX           azzera eax

           MOV   AL, [EDX]         prende il char della funzione

           XOR  ECX, ECX           azzera ecx

           MOV   CL, [00993E09]    metti in CL la xormask

           XOR  EAX, ECX           xora il char con la xormask

           MOV  EDX, [EBP - 04]    prende una dword-xormask

           AND  EDX, 000000FF      tieni la parte bassa della xormask

           XOR  EAX, EDX           ri-xor che ci fa ottenere il carattere vero e proprio della funzione

           MOV  ECX, [EBP - 1C]    ecx = puntatore al nome funzione criptato

           MOV  [ECX], AL          ci inserisce il char decriptato

...

00972788    JNZ  0097272A          loop per tutti i char del nome funzione

 

ecco fatto, questo è il codice del decrypter per i nomi delle funzioni. A prima vista può sembrare un pò pesante la cosa, vedendo i due xor, ma non fatevi ingannare! In realtà l'algoritmo non fa altro che xorare ogni char criptato col precedente char criptato, e il primo char viene xorato sempre col valore 0x10. Intanto all'indirizzo che conteneva il nome criptato della funzione ora ci sarà il nome corretto (GetVersion per noi), poi se continuate l'esecuzione vedrete che passerete per un bel GetProcAddress! Questo serve per ottenere l'address della funzione a partire dal suo nome. Vedete infatti che restituisce l'address di GetVersion. Ora che l'address è stato ottenuto proseguiamo ancora e incapperemo nelle righe

00972914

...

009729BD

che rieffettuano il criptaggio del nome della funzione, così in memoria non avremo più "GetVersion", ma una sfilza di bytes insensati. Ora possiamo provare a modificare il DPLAYER per saltare la routine di re-crypt. Senza stare ad ammattire a decompilare il DPLAYER, possiamo fare le midifche in memoria. Infatti rilanciate il gioco, quando sarete all'entry point il processo del DPLAYER è già presente, quindi possiamo mettere un pò di JMP per far saltare il recrypt! Innanzitutto possiamo mettere un jump alla linea

00972914   EB 7F   jmp   00972995

oops. Siccome EB considera il byte come signed, possiamo mettere un massimo di 7F per un jump in avanti, ma non ci basta per saltare tutta la routine (che finisce a 009729BD e la riga successiva che si serve è 009729C2). Siccome sono pigro e non avevo voglia di andare a cercare il jmp unsigned basta che facciamo la seguente modifica:

00972995  EB 2F   jmp   009729C2

così ora tutta la routine viene saltata. Caspio, non è abbastanza! Se proseguiamo nel codice vediamo che il primo char del nome della funzione viene modificato, quindi dobbiamo fare la modifica:

009729EE   90   NOP

                    90    NOP

ora lasciamo runnare il programma, e settiamo un BPX ExitProcess. Fatevi qualche giro con l'auto, schiacciate qualche pedone, quindi uscite dal gioco. Il softice breakerà di nuovo per tre volte. Arrivati alla terza volta saremo sull'exitprocess che chiuderà definitivamente tutto. Prima di eseguirlo dunque andiamo a cercare in memoria dove sono i nomi di funzione decriptati (intorno a 00FB7A04). Vediamo che molti nomi di funzioni sono stati decrittati, e alcuni no.Questo perchè sono API che potrebbero essere richiamate in condizioni particolari. Quindi abbiamo appurato che il decrypt per i nomi di funzioni è sempre uguale. Beh, se la IT non viene a AndreaGeddon allora AndreaGeddon va dalla IT :-). Ma si, che ci frega, abbiamo visto come decriptarci le funzioni, quindi in un modo o nell'altro le otterremo! Resta sempre il fatto che le funzioni sono in tutto 163. Azz, ci vorrà parecchio... perchè sprecare il nostro logoro cervellino quando abbiamo affianco un quadratino di silicio che può fare il lavoro al posto nostro e in una frazione di secondo ?! Mettiamo mano al compilatore (io ho usato il C per fare alla svelta), e pianifichiamo il lavoro. Innanzitutto vediamo come funziano le cose in dettaglio con un esempio:

 

physical bytes:  57 32 46 05 6A 07 6A 0B 65 01 4D 24 4A 2F 6E 6E

       xor mask:  10 57 32 46 05 6A 07 6A 0B 65 01 4D 24 4A 2F 6E

         risultato:  G  e  t  C  o  m  m  a  n  d  L  i   n  e  A 00

 

etc. etc. Nota che alla fine della funzione per ottenere 00 (il terminatore di stringa) dovete avere la ripetizione del char (6E 6E), quindi nella IT criptata ogni volta che vedete una doppia sapete che quella è la fine della funzione. RiNota che i descrittori per le funzioni sono formati nel modo seguente:

 

HINT Function Name

quindi i primi due byte (del campo HINT) sono da escludere dal decrypting. Il primo byte va decriptato sempre col valore 10. Per esserci utile il nostro decripter ci dovrà prendere la IT criptata così com'è e restituirci un output decriptato da sostituire in blocco alla IT criptata. Quindi il file di input lo prevediamo contenente SOLO i nomi criptati delle funzioni. Ora abbiamo tutte le carte in regola per scrivere il nostro decrypter:


void CSafeDiscDlg::OnButton1()
{
    HANDLE FileHandle, ResultHandle;
    char buffer[50000];
    long toread = 50000;
    unsigned long letti, scritti;

    FileHandle = CreateFile("c:\\iat.iat", GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (FileHandle != INVALID_HANDLE_VALUE)
    {
        if (ReadFile(FileHandle, &buffer, toread, &letti, NULL) != 0)
        {
            __asm {
                dec letti       decrementa il numero di iterazioni (in asm il conteggio parte da 0!)
                xor ecx, ecx        il nostro contatore
                xor eax, eax
Ripeti:
                xor edx, edx
                mov esi, 0x10     mettiamo il valore di xor per il primo char in esi
                cmp ecx, letti      abbiamo terminato?
                jge Terminato     se si, salta
                lea ebx, [buffer]                    metti il buffer dei dati letti in ebx
                mov al, byte ptr [buffer+ecx]   prendi il char attuale
                test al, al                                    e controlla che non sia zero
                jnz TrovatoHint                         altrimenti incrementa il contatore
                inc ecx
                jmp Ripeti                                 e controlla il prossimo char
TrovatoHint:

                mov byte ptr [buffer+ecx], 0
                inc ecx                                      se il char non è zero, aumentiamo di 2 il buffer...

                mov byte ptr [buffer+ecx], 0
                inc ecx                                      e azzeriamo i due byte corrispondenti all'hint
NomeDecr:
                mov al, byte ptr [buffer+ecx]    prendi il char
                mov edi, eax                             salvalo per il prossimo ciclo di xor
                xor eax, esi                               decrypt!
                test eax, eax                              se è zero abbiamo raggiunto il terminatore
                jnz NoFlag                                quindi...
                mov dl, 01                                settiamo dl a 1
NoFlag:
                mov esi, edi                               prepara il prossimo valore di xor
                mov byte ptr [buffer+ecx], al    sostituisci il byte decrittato a quello crittato
                inc ecx                                      vai al prossimo char
                cmp ecx, letti                             abbiamo trattato tutti i char?
                jge Terminato                            se si, esci
                cmp dl, 01                                 siamo arrivati alla fine della funzione?
                jz Ripeti                                     se si, ricomincia la ricerca di 00
                jmp NomeDecr                         altrimenti continua e decritta il byte successivo
Terminato:
                nop                                           oops questo lo avevo lasciato per debug :-)
            }
            ResultHandle = CreateFile("c:\\iat2.iat", GENERIC_WRITE, NULL, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
            if (ResultHandle != INVALID_HANDLE_VALUE)
            {
                letti++;
                WriteFile(ResultHandle, &buffer, letti, &scritti, NULL);
                MessageBox("File Decriptato", "Enjoy!", MB_ICONASTERISK);
            } else MessageBox("Impossibile scrivere su iat2.iat: file già esistente?", "Errore", MB_ICONHAND);
        } else MessageBox("Impossibile leggere dal file", "Errore", MB_ICONHAND);
    } else MessageBox("c:\\iat.iat non trovato", "Errore", MB_ICONHAND);
    CloseHandle(FileHandle);
}

 


come al solito ho usato MFC, ma il codice lo potete riadattare facilmente a qualsiasi compilatore. La parte in C non necessita spiegazioni, la parte asm invece è debitamente commentata. Il programma prende in inputil file "c:\iat.iat", lo decripta, e restituisce l'output in "c:\iat2.iat". Nota che se iat2.iat esiste già il file non verrà sovrascritto. Bene. Ora scriviamoci il nostro file. Andiamo nella IT fisica, e scegliamoci i byte che ci servono. Basta che diamo una raw-occhiata e troviamo che i nomi delle funzioni partono da 001C77F8. Proseguendo a 001C783E abbiamo il primo blocco criptato (fino a 001C7941). E questa sarà una porzione da inserire in iat.iat. Poi a 001C7950 inizia il secondo blocco di funzioni criptate, fino a 001C7D15. In fondo troveremo il terzo blocco, da 001C8722 a 001C8D92. Ecco i pacchi che dobbiamo inserire nell'iat.iat. Con un semplice copia incolla abbiamo il file pronto per il decrypter. Per comodità possiamo separare i tre blocchi da qualche riga di 00, tanto il parser del decrypter non considera gli zeri. Eseguiamo il crypter... Tadààààà. Fatto. In iat2.iat abbiamo tutti i nostri bei nomi di funzione! Ora li possiamo prendere in blocco e sostituire alla IT criptata. Bada che il lavoro non è affatto finito! Vi ricordate gli 00000000 del campo OriginalFirstThunk? Questo campo doveva puntare a un array di dwords, ognuna delle quali è un puntatore all' HINT - NOME FUNZIONE. Ora se cerchiamo gli OriginalFirstThunnk del primo descrittore (001C73A8), li troviamo, quindi è lì che sono tutti ammatassati. Scorriamo un pò i dati... voilà. Da 1C73EC inizia un graande spazio di 00000000 fino a 1C7584 che è la NULL-DWORD terminatrice dell'elenco. Qualche riga dopo abbiamo l'altro spazio vuoto (a 001C75B0). Questi vuoti andranno riempiti con tante dword contenenti l'indirizzo di ogni funzione! E gli HINT? Ache quelli sono sbagliati! Teoricamente non dovrebbero esserci problemi però, perchè gli hint vengono usati per cercare le funzioni tramite il loro ordinale, e quindi più velocemente; l'assenza degli hint costringe il PEloader a cercarsi gli entry point delle funzioni by name only, che è una scelta meno efficiente, ma del resto chissenefrega! Se volete potete aggiustarveli a mano tutti gli hint, io non ci tengo :-P. Quindi abbiamo fatto in modo che il nostro decrypter azzerasse i due bytes degli hint. Rimane da risolvere l'array di descrittori per OriginalFirstThunk. Bene! Anzi, male! Vorrei dirvi che ci sono i Rebuilder che lo fanno in automatico, ma non è così :-(. A dire la verità ho provato solo col Rebuilder del PEditor, e non ha funzionato, ma, come si dice, se vuoi una cosa fatta bene.... fattela da solo! Quindi prendiamo carta e penna, iniziamo a guardare i nomi di funzioni che abbiamo decrittati, e troviamoci il loro rispettivo rva. Attenzione che qui il lavoro è abbastanza delicato! Infatti gli indirizzi della IAT sono disposti con un diverso ordine rispetto a quelli della IT. Mi spiego meglio. Nella ImportTable cerchiamo il primo nome di funzione decrittato da noi (in pratica iat2.iat), e troviamo il nome GetCurrentDirectoryA. Andiamo nella IAT, cerchiamo la IAT (1AF08C per KERNEL32), e troviamo l'rva 00EB03AF. Ora andiamo ad eseguire il programma originale, settiamo un break sull'entry point, arriviamo alla prima call (che punterà alla dword 00EBA688), la sostituiamo con 00EB03AF e la tracciamo con F8. Seguiamo il trafiletto del dplayer ed arriviamo all'entry point di GetWindowsDirectoryA! Quindi GetCurrentDirectoryA != GetWindowsDirectoryA e quindi non cè corrispondenza diretta tra gli OriginalFirstThunk e i FirstThunk. Quindi che ci tocca fa? Dobbiamo copiarci a mano tutti i puntatori del tipo "00EBxxxx" e vedere a quale funzione ci porta ognuno! Se avete un amico o un fratellino da schiavizzare non ci vorrà molto: settate un break sull'EP del gioco originale, arrivati alla prima call di volta in volta sostituite il valore puntato da 005AF148 (che inizialmente punta a 00EBA688) con il puntatore che volete determinare (ad esempio il secondo 00EB0726) e poi steppate in dplayer come abbiamo visto prima fino ad arrivare al RET che ci manda sull'EP della funzione. Su tale ret mettete un break, così poi vi basterà fare F10 per finire nella funzione (per il secondo puntatore è la func GetOemCp),e così via per tutti i puntatori. Una volta che per ogni indirizzo 00EBxxxx abbiamo ottenuto la corrispondente funzione, ADESSO possiamo tornare al lavoro di prima: nell'array degli OriginalFirstThunk sostituiamo tutti gli EBxxxx con i puntatori all'offset della funzione corrispondente.

UPDATE

Come vedremo più tardi usare la prima call dopo l'entry point non va bene perchè le chiamate alle api sono determinate in base al caller address, quindi ho dovuto riscrivere la iat cercando le call di ogni api nel codice e chiamandole dalla loro posizione originaria quando possibile. La tabella degli OrigialFirstThunk qui sotto cmq è corretta.

Es:

   3° puntatore:  00EB0A9D   corrisponde a GetCurrentProcess  quindi l'offset del suo name sarà: 1C88D6

e così via per TUTTE le funzioni. Badate che il puntatore al nome della funzione inizia DUE bytes prima del nome ascii (hint + name). Siccome so che siete pigri vi dò i descrittori che ho trovato io:

 

PRIMO BLOCCO:

                                       2A 79 1C 00
CC 8C 1C 00  D6 88 1C 00  C2 8C 1C 00  B4 88 1C 00
A0 88 1C 00  C8 88 1C 00  7C 88 1C 00  68 88 1C 00
8C 88 1C 00  46 88 1C 00  30 88 1C 00  52 88 1C 00
14 88 1C 00  FA 87 1C 00  DE 87 1C 00  D2 87 1C 00
EC 87 1C 00  B2 87 1C 00  A2 87 1C 00  C4 87 1C 00
86 87 1C 00  6E 87 1C 00  96 87 1C 00  3A 87 1C 00
22 87 1C 00  52 87 1C 00  D8 8C 1C 00  EA 88 1C 00
FC 88 1C 00  0C 89 1C 00  18 89 1C 00  2A 89 1C 00
42 89 1C 00  5A 89 1C 00  6C 89 1C 00  78 89 1C 00
92 89 1C 00  A2 89 1C 00  BC 89 1C 00  D4 89 1C 00
EA 89 1C 00  02 8A 1C 00  1C 8A 1C 00  28 8A 1C 00
3A 8A 1C 00  4C 8A 1C 00  5E 8A 1C 00  6C 8A 1C 00
7A 8A 1C 00  A2 8A 1C 00  8E 8A 1C 00  0A 8D 1C 00
F8 8C 1C 00  E4 8A 1C 00  50 8D 1C 00  1E 79 1C 00
0A 79 1C 00  FC 78 1C 00  EC 78 1C 00  DE 78 1C 00
12 8B 1C 00  22 8B 1C 00  D8 8A 1C 00  CE 78 1C 00
BE 78 1C 00  A8 78 1C 00  92 78 1C 00  7E 78 1C 00
76 78 1C 00  66 78 1C 00  56 78 1C 00  3E 78 1C 00
98 8C 1C 00  40 8D 1C 00  2E 8D 1C 00  1C 8D 1C 00
06 8B 1C 00  F0 8A 1C 00  E8 8C 1C 00  CA 8A 1C 00
BC 8A 1C 00  B6 8C 1C 00  A8 8C 1C 00  B8 8B 1C 00
86 8C 1C 00  6C 8C 1C 00  54 8C 1C 00  3A 8C 1C 00
20 8C 1C 00  04 8C 1C 00  E6 8B 1C 00  D6 8B 1C 00
C6 8B 1C 00  6C 8B 1C 00  AA 8B 1C 00  9C 8B 1C 00
88 8B 1C 00  7C 8B 1C 00  5C 8B 1C 00  46 8B 1C 00
30 8B 1C 00

SECONDO BLOCCO:

B2 79 1C 00  C6 79 1C 00  D8 79 1C 00  20 7C 1C 00
FE 79 1C 00  EC 79 1C 00  A4 79 1C 00  0E 7A 1C 00
50 79 1C 00  1A 7A 1C 00  2E 7A 1C 00  3E 7A 1C 00
4C 7A 1C 00  5C 7A 1C 00  0C 7D 1C 00  FA 7C 1C 00
EA 7C 1C 00  DE 7C 1C 00  D2 7C 1C 00  BA 7C 1C 00
A8 7C 1C 00  9A 7C 1C 00  8A 7C 1C 00  7A 7C 1C 00
6C 7C 1C 00  60 7C 1C 00  4A 7C 1C 00  34 7C 1C 00
06 7C 1C 00  14 7C 1C 00  96 79 1C 00  82 79 1C 00
D4 7B 1C 00  F6 7B 1C 00  E8 7B 1C 00  A4 7B 1C 00
BE 7B 1C 00  B0 7B 1C 00  6A 7B 1C 00  92 7B 1C 00
80 7B 1C 00  38 7B 1C 00  56 7B 1C 00  44 7B 1C 00
14 7B 1C 00  2C 7B 1C 00  20 7B 1C 00  E4 7A 1C 00
02 7B 1C 00  F4 7A 1C 00  B6 7A 1C 00  D2 7A 1C 00
C2 7A 1C 00  7C 7A 1C 00  A0 7A 1C 00  8E 7A 1C 00
6A 7A 1C 00  6E 79 1C 00  60 8D 1C 00  72 8D 1C 00
7E 8D 1C 00  8A 8D 1C 00  5E 79 1C 00

questi sono i puntatori corretti per tutte le funzioni (moduli Kernel e User). Bene, gli OrginalFirstThunk ora sono a posto. Passiamo ad occuparci dei FirstThunk. Stavolta sarà tutto più semplice! Infatti come abbiamo già visto non possiamo riempire la IAT di indirizzi ricavati "a mano" dalle relative funzioni, ma dobbiamo farle ricalcolare di volta in volta a runtime giacchè per ogni pc sono diverse. Quindi possiamo semplicemente copiare gli address qui sopra anche nelle zone della iat, infatti normalmente l'array dei FirstThunk contiene la copia dell'array di OriginalFirstThunk, e poi a runtime il PEloader si occupa di risolvere gli entry point delle funzioni e di sostituirli all'array dei FirstThunk. Fate un copia/incolla e siamo a posto. Non ci resta che modificare i descrittori globali della IT: cioè la struttura a 5 dwords per ogni import. Se infatti proviamo ad eseguire l'eseguibile così com'è, otterremo un crash. Vediamone i dettagli e ci dirà che il crash è avvenuto all'EIP 001C8A5E, che guarda caso è l'offsett nella iat della funzione GetVersion (che dovrebbe essere la PRIMA call). Ciò significa che le API non vengono risolte. Andiamo all'indirizzo 1C7220 (corrispondente alla IT) e abbiamo i descrittori:

 

OrFstThunk     Time/Date    Forwarder    DLL Name     FirstThunk

A8 73 1C 00   1B F5 D6 37  00 00 AA BA  28 78 1C 00  48 F0 1A 00     DDRAW

BC 73 1C 00   4D F5 D6 37  00 00 AF BE  32 78 1C 00  5C F0 1A 00     DSOUND

00 00 00 00   B1 AB 46 35  00 00 F7 BF  42 79 1C 00  8C F0 1A 00     KERNEL32

00 00 00 00   A3 38 6E 35  00 00 F5 BF  16 7D 1C 00  50 F2 1A 00     USER32
C4 73 1C 00   72 C2 3E 35  00 00 F2 BF  82 7D 1C 00  64 F0 1A 00     GDI32

A0 75 1C 00   A6 38 6E 35  00 00 CE 7F  C8 7D 1C 00  40 F2 1A 00     SHELL32
etc...

Innanzitutto per i moduli Kernel e User il puntatore dell'OriginalFirstThunk non è definito, quindi sostituiamo i due NULL rispettivamente con 001C73EC e 001C75B0 che sono gli offset di dove abbiamo inserito i nomi delle funzioni. Poi azzeriamo i ForwarderChain: questo ci assicura che la iat verrà ricalcolata a runtime, quindi avremo una esatta IAT sempre! I forwarder chain è meglio azzerarli per tutti i moduli ovviamente.

Bene! Il lavoro sembra essere finito! Andiamo ad eseguire il nostro file crackato e... CRASH! DOH! Innanzitutto vediamo i dettagli del crash: tale crash avviene all'EIP 00EA0000. Quindi vuol dire che non è stata una api a provocare il crash, e quindi la IT è stata correttamente ricostruita. Abbiamo visto prima che l'area 00EAxxxx e successiva era quella che faceva da "ponte" per saltare poi a Dplayerx. Quindi mettiamo un break sull'EP del nostro eseguibile crackato, lanciamo il programma, e aspettiamo di vedere che succede. Innanzitutto vediamo che le call sono indicate dalle funzioni, a maggior conferma che la IT è a posto! Bene, dall'entry point steppiamo un pò. La prima api che troviamo è GetVersion, poi c'è una call. Se la eseguiamo con F10 otterremo il crash. Quindi arriviamoci e steppiamoci dentro con F8. Arriveremo all'indirizzo 005920B7. Continuiamo lo stepping, passiamo prima su HeapCreate, poi passiamo su una call che di nuovo ci manda in crash. Bene, entriamo in quella call e arriviamo a 005920F3. Il codice è più o meno il seguente:

 

005920F3     push

             push

             push

             call    GetVersion

             test

             mov

             ret

quindi in queste poche righe c'è qualcosa che non va. Che ci fa un GetVersion dopo un HeapCreate???? Se steppiamo queste linee va tutto bene, finchè arriviamo al RET. Arrivati al RET non lo eseguite, ma guardate lo stack pointer (d esp). Questo punta a 00EA0000, che è l'indirizzo dove verrà riportata l'esecuzione. Guarda caso è anche l'indirizzo che ci veniva segnalato nel crash :-). Cosa succede? Che l'api chiamata è sbagliata: ciò porta a uno scompenso dello stack, quindi al momento del ret non avremo il corretto indirizzo di ritorno ma avremo un valore che non c'entra nulla. Lo so che questo vi fa incazzare, ma non è colpa nostra! La IT è stata ricostruita perfettamente. Prendiamo il programma originale, iniziamo l'esecuzione dall'entry point e arriviamo fino all'indirizzo della GetVersion qui sopra. Nel programma originale non avrete la call risolta, quindi come prima steppiamo nella call, ci passiamo il dplayer e vediamo a che api ci porta il tutto: a HeapAlloc! Questo è un risultato più attendibile: insomma, dopo un HeapCreate ci sta bene un HeapAlloc (e non un GetVersion). Bene. Torniamo al NOSTRO eseguibile crackato,   ripartiamo dall'entry point fino ad arrivare a questo benedetto GetVersion, e ci steppiamo dentro con F8. Finiremo ovviamente all'EP della funzione GetVersion, quindi digitiamo

r eip HeapAlloc

così finiremo sull'EP di HeapAlloc. Ora con F12 torniamo al processo dell'eseguibile crackato, steppiamo fino al ret, lo eseguiamo e non crashamo! Voilà! Continuiamo l'esecuzione e sbattiamo di nuovo su un'altra call che ci crasha. Cmq abbiamo capito cosa succede. La IT è valida, sono le chiamate ad essa dal codice che non sono valide! Infatti le due call a GetVersion hanno un opcode IDENTICO, cioè all'inizio del prog viene giustamente chiamato GetVersion, e questo è plausibile, ma poi viene chiamato di nuovo al posto di HeapAlloc, e questo non è affatto sensato. Se l'opcode è identico vuol dire che entrambe le chiamate puntano allo stesso valore della IAT, quindi possiamo escludere ulteriormente un errore della IAT. Beh, vediamo un pò: l'opcode della calla GetVersion è il seguente:

FF 15 48 F1 5A 00              call    [005AF148]

dove 005AF148 è il puntatore all'entry point della funzione nella IAT. Arrivati alla seconda call a GetVersion dobbiamo modificare l'opcode affinchè punti nella IAT non al valore di GetVersion ma a quello di HeapAlloc, che si trova all'rva 1AF188, quindi al va 005AF188 e l'opcode diventa:

FF 15 88 F1 5A 00              call    [005AF188]

ora se eseguiamo il programma la seconda call a GetVersion che abbiamo modificata diventerà d'incanto una call a HeapAlloc. E questo ci fa evitare il primo crash. Ma poi purtroppo c'è un altro crash. Ovviamente dobbiamo aspettarci questo fix per mooolte call (ma non tutte). Il problema è dato dal fatto che le chiamate venivano smistate dal ponte a 00EBxxxx e poi da dplayer. Invece di definire un handler per ogni singola call, hanno raggruppatto le call che poi vengono risolte dal ponte e dal dplayer. Cioè, a 005AF148 corrispondono più funzioni (GetVersion, HeapAlloc), quindi il programma chiama [005AF148], poi safedisc smista la chiamata alla giusta funzione. E' quindi chiaro che tutte le call a 005AF148 devono essere fixate per chiamare DIRETTAMENTE le giuste funzioni. Chiaramente non solo 5AF148, ma tutte le call del programma devono essere esaminate e nel caso fixate! Tutte le call a USER e KERNEL per fortuna! Farlo a mano non è molto consigliato, quindi dobbiamo studiarci il sistema per automatizzare il tutto. Naturalmente adesso ci toccherà esaminare per bene il DPLAYERX per vedere come fa ad assegnare ad una call la giusta API. Ho parlato con Yado in chat e lui mi ha detto che le call vengono risolte in base all'EIP. In effetti anche ad intuito questa era l'ipotesi più probabile. Quindi chiamando lo stesso valore della IAT il dplayer si occupa di vedere l'offset dell'opcode stesso, quindi gli assegna la giusta API. La nostra idea è quella di sniffare il punto in cui avviene l'assegnamento della giusta API, capire come tutto ciò avviene e scrivere un prog che ci risolverà in automatico tutte le call che ci servono in base al loro offset! Facile no? Quindi riprendiamo il TDR originale e cominciamo l'analisi. La prima api-call dopo l'entry point si trova a:

 

0058CEA1     call   [005AF148]

0058CEA7

 

quindi partiamo da questa. Ci steppiamo dentro e arriviamo a:

 

00EBA688      push   BFEA1333

             pushfd

             pushad

             push    esp       indirizzo di ritorno

             push    2F        identificativo della funzione

             push    00        identificativo del modulo (0 kernel/1 user)

             call    [00EBA6C3]   annamo ar dplayer :-)

 

e da questa call arriviamo al DPLAYER. Non riporterò tutto il codice ma solo le linee più importanti:

 

00973270      start

...

009732BC      sub    eax, 6    ottiene il VA che ha chiamato l'API

...

009733C1      call   009712A0  controlla se il VA caller si trova nella sezione di codice (>1000 e <1AE54D)

...

009733F0      sub    ecx, eax  ottiene l'RVA del VA caller

...

00973412      call   00977090  importante! qui si decide quale API chiamare

    00977090     start

    ...

    00977163     call   ebx   questa call viene eseguita 2 volte (ma chiama procedure diverse) e trasforma l'RVA caller in un valore xxx

                                                                           tenetelo a mente, la chiameremo ROUTINE DECISIVA

    ...

    00977199     div    ecx   divide il valore xxx per 4 ed in base al resto setta un flag

...

0097341F      cmp    eax, 01   controlla il flag settato poco fa

             jnz     009734CD  qui decidiamo quali delle due strade prendere...

             jmp     00973431  ... e quindi quale API chiamare

...

009734F3      mov    ecx, [eax*4+edx]     in entrambi i casi si arriva qui. In eax troveremo il numero identificativo della funzione (bada

...                                       che può non essere uguale a quello visto all'inizio), ed in edx c'è 00FB700C che il base

...                                       address di una tabella di bytes, dalla quale otterremo un altro valore utile. Nel caso della

...                                       nostra prima call, da 2F otterremo 51

00973508      imul   edx, edx, 377         edx = 51, 51*377 = 118A7

...

00973511      mov    ecx, [edx+eax+322]   edx = 118A7 e eax = 00EB0078, che è il base address dell'inizio dei nomi criptati delle

...                                       funzioni.

 

e qui ci fermiamo. Dopo questa ultima istruzione infatti in ECX avremo il puntatore al nome criptato della funzione, che poi verrà decrittato e usato con GetProcAddress. Quello che ora dobbiamo fare è determinare cosa succede se scegliamo una o l'altra strada a 0097341F. Se eax è 0, allora il JNZ ci sparerà alla locazione 009734CD, altrimenti andremo a 00973431. Basta debuggare il relativo codice e vediamo quello che succede. Se prendiamo il caso della

CALL  [005AF148]

vediamo che la prima volta viene chiamata da 0058CEA1. In questo caso nel dplayer arriviamo a 0097341F con eax = 0, quindi salteremo a 009734CD. L'identificativo per la funzione era 2F, tramite una tabella gli viene associato il valore 51. Questa tabella ci serve, quindi occhio alla linea:

 

009734F3      MOV     ECX,  [EAX * 4 + EDX]

 

dove eax è l'identificativo della funzione (2F), e edx è il base address della tabella. Che si fa ora? Si dumpa la tabella! Avete ancora Icedump vero??? Dumpare questa tabella è facile. Se la osservate vedrete tutte dword del tipo xx 00 00 00, e dopo queste dword ci sono dei puntatori, che guarda caso puntano ai nomi criptati delle stringhe. Poi ci sono i nomi criptati, e poi di nuovo si ripete il tutto (tabella, puntatori, nomi funzioni). Infatti questo pezzo di codice vale per i 2 moduli, il primo terzetto tab-ptr-name è usato per KERNEL, il secondo per USER. E fin qui ci siamo. Nella formula di prima il base addresso della tabella è FB7008. Cmq basta guardare a occhio e vediamo che la tabella è definita in un'area di 1000h bytes che vanno da FB7000 a FB8000. Voilà, ci dumpiamo tutto :-). Ecco cosa dovreste aver dumpato:

 

CC 3E ED 00   06 00 00 A0  9C 01 00 A0  45 00 00 00   tabella identificatori di funzioni
50 00 00 00  19 00 00 00  47 00 00 00  2E 00 00 00  
per il KERNEL
1A 00 00 00  42 00 00 00  01 00 00 00  34 00 00 00  
(EID table)
0C 00 00 00  48 00 00 00  3A 00 00 00  58 00 00 00
08 00 00 00  43 00 00 00  32 00 00 00  2D 00 00 00
4A 00 00 00  4C 00 00 00  1F 00 00 00  4F 00 00 00
60 00 00 00  56 00 00 00  03 00 00 00  0F 00 00 00
10 00 00 00  61 00 00 00  36 00 00 00  30 00 00 00
0E 00 00 00  4E 00 00 00  1D 00 00 00  28 00 00 00
59 00 00 00  63 00 00 00  14 00 00 00  02 00 00 00
11 00 00 00  20 00 00 00  5A 00 00 00  04 00 00 00
44 00 00 00  00 00 00 00  40 00 00 00  2B 00 00 00
55 00 00 00  3D 00 00 00  51 00 00 00  2C 00 00 00
0D 00 00 00  52 00 00 00  39 00 00 00  07 00 00 00
57 00 00 00  15 00 00 00  12 00 00 00  62 00 00 00
05 00 00 00  25 00 00 00  38 00 00 00  22 00 00 00
2A 00 00 00  06 00 00 00  37 00 00 00  09 00 00 00
5E 00 00 00  21 00 00 00  5B 00 00 00  3F 00 00 00
29 00 00 00  54 00 00 00  31 00 00 00  46 00 00 00
4D 00 00 00  49 00 00 00  27 00 00 00  16 00 00 00
4B 00 00 00  1B 00 00 00  33 00 00 00  0A 00 00 00
0B 00 00 00  5D 00 00 00  65 00 00 00  3E 00 00 00
5F 00 00 00  18 00 00 00  53 00 00 00  2F 00 00 00
13 00 00 00  35 00 00 00  17 00 00 00  23 00 00 00
26 00 00 00  24 00 00 00  64 00 00 00  3B 00 00 00
1C 00 00 00  5C 00 00 00  1E 00 00 00  41 00 00 00
3C 00 00 00  9C 01 00 A0  44 73 FB 00  60 73 FB 00 
puntatori ai nomi delle funzioni
74 73 FB 00  90 73 FB 00  A0 73 FB 00  B8 73 FB 00 
per il KERNEL
D0 73 FB 00  E0 73 FB 00  F4 73 FB 00  14 74 FB 00
28 74 FB 00  38 74 FB 00  48 74 FB 00  60 74 FB 00
78 74 FB 00  8C 74 FB 00  A8 74 FB 00  C4 74 FB 00
D8 74 FB 00  EC 74 FB 00  08 75 FB 00  18 75 FB 00
28 75 FB 00  3C 75 FB 00  5C 75 FB 00  78 75 FB 00
90 75 FB 00  A8 75 FB 00  C0 75 FB 00  D8 75 FB 00
EC 75 FB 00  00 76 FB 00  14 76 FB 00  30 76 FB 00
48 76 FB 00  58 76 FB 00  6C 76 FB 00  80 76 FB 00
90 76 FB 00  A4 76 FB 00  B8 76 FB 00  D4 76 FB 00
E4 76 FB 00  F8 76 FB 00  0C 77 FB 00  1C 77 FB 00
2C 77 FB 00  44 77 FB 00  60 77 FB 00  74 77 FB 00
88 77 FB 00  98 77 FB 00  AC 77 FB 00  C4 77 FB 00
E4 77 FB 00  F8 77 FB 00  08 78 FB 00  1C 78 FB 00
34 78 FB 00  4C 78 FB 00  5C 78 FB 00  74 78 FB 00
88 78 FB 00  98 78 FB 00  B0 78 FB 00  C0 78 FB 00
D8 78 FB 00  E8 78 FB 00  04 79 FB 00  20 79 FB 00
3C 79 FB 00  58 79 FB 00  68 79 FB 00  78 79 FB 00
8C 79 FB 00  9C 79 FB 00  AC 79 FB 00  C0 79 FB 00
D4 79 FB 00  E4 79 FB 00  F4 79 FB 00  04 7A FB 00
14 7A FB 00  30 7A FB 00  4C 7A FB 00  60 7A FB 00
74 7A FB 00  90 7A FB 00  A4 7A FB 00  BC 7A FB 00
D8 7A FB 00  F4 7A FB 00  0C 7B FB 00  1C 7B FB 00
2C 7B FB 00  40 7B FB 00  54 7B FB 00  68 7B FB 00
88 7B FB 00  98 7B FB 00  AC 7B FB 00  BC 7B FB 00
1C 00 00 A0  56 3F 53 36  62 0B 66 03  57 38 74 1B 
\ tagliato, contiene
  ... 8<  snip                                       
\ soltanto i nomi
39 00 00 00  14 00 00 A0  43 26 52 14  7D 11 74 24 
\ criptati delle
4B 22 4C 38  5D 2F 2F 00  10 00 00 A0  58 3D 5C 2C 
\ funzioni di KERNEL
6F 1D 78 19  6D 08 08 00  10 00 00 A0  57 32 46 00
69 05 60 34  4D 3D 58 58  00 01 00 A0  09 00 00 00 
tabella identificatori di funzione
30 00 00 00  32 00 00 00  02 00 00 00  27 00 00 00 
per USER
2C 00 00 00  19 00 00 00  36 00 00 00  0E 00 00 00 
(EID table)
2E 00 00 00  31 00 00 00  03 00 00 00  0B 00 00 00
0D 00 00 00  01 00 00 00  2D 00 00 00  0F 00 00 00
08 00 00 00  25 00 00 00  1F 00 00 00  07 00 00 00
0C 00 00 00  20 00 00 00  33 00 00 00  3E 00 00 00
37 00 00 00  00 00 00 00  23 00 00 00  15 00 00 00
13 00 00 00  3C 00 00 00  35 00 00 00  14 00 00 00
1E 00 00 00  12 00 00 00  05 00 00 00  10 00 00 00
0A 00 00 00  28 00 00 00  34 00 00 00  21 00 00 00
1D 00 00 00  17 00 00 00  29 00 00 00  3D 00 00 00
38 00 00 00  22 00 00 00  3A 00 00 00  1B 00 00 00
26 00 00 00  2A 00 00 00  39 00 00 00  06 00 00 00
18 00 00 00  2F 00 00 00  2B 00 00 00  24 00 00 00
04 00 00 00  16 00 00 00  3B 00 00 00  1C 00 00 00
1A 00 00 00  11 00 00 00  00 01 00 A0  CC 7D FB 00
E4 7D FB 00  F4 7D FB 00  0C 7E FB 00  1C 7E FB 00 
puntatori ai nomi delle funzioni
34 7E FB 00  44 7E FB 00  58 7E FB 00  6C 7E FB 00 
per USER
7C 7E FB 00  94 7E FB 00  A4 7E FB 00  B8 7E FB 00
C8 7E FB 00  D8 7E FB 00  E8 7E FB 00  FC 7E FB 00
14 7F FB 00  28 7F FB 00  38 7F FB 00  48 7F FB 00
60 7F FB 00  70 7F FB 00  84 7F FB 00  9C 7F FB 00
B0 7F FB 00  C0 7F FB 00  D0 7F FB 00  E4 7F FB 00
38 3C ED 00  48 3C ED 00  5C 3C ED 00  78 3C ED 00
8C 3C ED 00  A0 3C ED 00  B0 3C ED 00  C8 3C ED 00
DC 3C ED 00  EC 3C ED 00  FC 3C ED 00  10 3D ED 00
28 3D ED 00  3C 3D ED 00  4C 3D ED 00  60 3D ED 00
74 3D ED 00  88 3D ED 00  A0 3D ED 00  B8 3D ED 00
CC 3D ED 00  E0 3D ED 00  F8 3D ED 00  0C 3E ED 00
20 3E ED 00  38 3E ED 00  48 3E ED 00  58 3E ED 00
68 3E ED 00  7C 3E ED 00  90 3E ED 00  A0 3E ED 00
B0 3E ED 00  C0 3E ED 00  18 00 00 A0  43 26 48 2C
68 04 63 2A  5E 3B 56 1B  7E 0D 7E 1F  78 1D 5C 5C 
\
... 8< snip                                        
\ nomi criptati delle
1C 00 00 A0  42 27 4B 2E  4F 3C 59 1D  5E 5E 00 00 
\ funzioni di USER
00 00 00 00  00 00 00 00  00 00 00 00  00 00 00 A0 
\

bella vero? Ora abbiamo la tabella quindi possiamo risolvere la precedente formula per associare all'identificativo della funzione un valore xx della tabella. Questo ci servirà per scrivere il fixer ovviamente. Ora che sappiamo come fa a passare da 2F a 51 vediamo cosa succede. Viene usato il base address 00FB71A8 che è l'inizio dei nomi di funzione criptati, indi con la formula

51 * 4 + FB71A8

ottiene il puntatore alla funzione criptata (GetVersion), della quale il dplayer otterrà l'address (dopo averla decrittata nel modo già descritto).

Vediamo l'altro caso. Poco dopo la precedente funzione arriviamo di nuovo a

00592100    CALL    [005AF148]

ci steppiamo dentro, e nel dplayer quando arriviamo a 0097341F avremo eax = 1, quindi invece del JNZ finiremo nel JMP 00973431. Quindi arriviamo nel codice che sta PRIMA del precedente caso (009734CD). Qui non succede molto, la cosa importante è che viene cambiato l'identificativo della funzione, che da 2F diventa 3F. Come facciamo a determinare il cambiamento? Di nuovo basta steppare il codice e vediamo che succede la seguente cosa.

2F + 66 = 95

BBE942D4  / 66 = 01D79EC5  (resto   56)

95 - 56 = 3F

3F % 66 = 3F

bada che per kernel32 usiamo il parametro 0x66, per user va usato 0x3F. Ora da dove salta fuori BBE942D4?? Dalla seguente formula:

EIP - 00401000      -->    00592100 - 00401000  =   00191100

BBD031D4 + 00191100 = BBE942D4

dove i valori BBD031D4 e il precedente 66 sono sempre fissi. Ora che ha ottenuto il valore 3F verrà riusata la formula precedente: gli verrà associato un valore XX tramite la solita tabella, quindi si avrà:

XX * 4 + FB71A8

e otterremo il puntatore alla funzione criptata ALTERNATIVA (in questo caso HeapAlloc). Ecco quindi risolto il mistero delle chiamate multiplte :-)

Ora però c'è ancora un caso da tenere in considerazione: non ci sono solo strutture del tipo

CALL   [iat-value]

per le quali abbiamo già visto cosa succede, ma ci sono anche strutture del genere:

MOV      ESI,   dword ptr [iat-value]

PUSH     dword ptr [valore]

CALL     ESI

quindi abbiamo visto cosa succede nel caso di call [iat-value], ora dobbiamo esaminare l'altro caso. Si lo cosa state pensando: "che palle! Non si finisce maiii", beh avete ragione :-) E pensate che dobbiamo ancora arrivare a scrivere il fixer :-P. Cmq pensate a me che a scrivere tutte ste menate mi si consuma la tastiera (e il cervellino)... Vabbè, proseguiamo che sennò famo notte.

Allora dicevamo, che dicevamo? Ah già, che dobbiamo esaminare il caso delle CALL ESI. Quando scriveremo il fixer infatti dovremmo effettuare una scansione degli opcode per trovare le call, indi dobbiamo tenere conto di entrambe le forme di call. Ma andiamo a vedere a runtime che succede alla CALL ESI. Si finisce sempre nel dplayer alla solita routine, viene controllato l'algoritmo, e si arriva all'indirizzo della decisione fatale: 0097341F. Nella call precedente a questa riga abbiamo visto che veniva confrontato il resto di tutto quel cazz di calcolo. Se il resto è >= 2 then don't fix, se invece è <2 fix! Nella divisione per 4 essendo le classi resto i valori [0, 1, 2, 3] abbiamo la metà delle possibilità di fixaggio :-). Cmq, se il resto è >= 2 il fix non avviene, e viene chiamata l'api puntata effettivamente dalla nostra IAT. Se invece il resto è <2, saltiamo alla routine del fixaggio. In tale routine però, prima di procedere al fixing, viene controllato l'opcode del caller:

 

00973472    mov       al, [edx]             edx = puntatore all'opcode della call

                    cmp       eax,   FF            controlla se è FF

                    jnz         009734CD         se non lo è, salta il fixing

 

e poco dopo viene confrontato il byte seguente dell'opcode con il valore 15h. In pratica il DPLAYER controlla che la chiamata abbia l'opcode FF 15 xx xx xx xx, corrispondente alla CALL [api]. Se l'opcode è diverso, allora vuol dire che siamo in una  CALL  ESI, quindi salta automaticamente il fixing. Bene!!!!! Lavoro risparmiato per noi :-))). A che pro tutto questo? Beh, devo ancora postare il codice relativo alla ROUTINE DECISIVA (vedete prima), ma cmq tale routine consiste in un calcolo veramente orribile pieno di XOR, ROR, ROL, ADD, SUB, volto a trasformare l'RVA del caller nel valore il cui resto della divisione per 4 ci dirà se dobbiamo fixare o no. Ma chi ci assicura che tale calcolo funziona sempre???? Insomma l'RVA delle call non è un fattore che possiamo conoscere a priori, nè tantomeno possiamo definire una funzione per studiarne la disposizione, che è casuale. Quindi tutto questo fottuto casino del DPLAYER potrebbe finire per portarci a fixare una call che in realtà non deve essere fixata. Bene!!!! Hanno ben pensato di usare l'opcode stesso come flag, in modo di riparare agli errori dell'algoritmo.  Insomma, la cosa è di questo genere:

 

l'opcode del caller è FF 15 xx xx xx xx (call [api])?

     Se si, allora procediamo normalmente e vediamo se la rilocazione deve avvenire o no.

     Se no (caso FF D6   call esi), allora qualsiasi sia l'esito dell'algoritmo NON rilocare.

 

e così siamo sicuri che non avremo errori del cacio. Bah che robaccia. Beh, questo riduce notevolmente il lavoro del nostro fixer. Cmq non è un lavoro piacevole da fare a mano. Ora ci manca solo la ROUTINE DECISIVA. Non la posto ora, la posto direttamente nel codice del fixer, cmq le due routine chiamate sono

 

00976E03   - la prima parte

00976CEE  - la seconda parte

 

ogni volta dopo questa call (la call ebx a 00977163) c'è uno XOR del valore passato alla CALL EBX con il suo valore di ritorno.

Ora occupiamoci di sintetizzare l'algoritmo del nostro fixer. Adesso abbiamo tutti gli elementi per procedere. Essenzialmente abbiamo tutte tabelle, e il nostro algoritmo non deve fare altro che partendo dal CALLER tramite le associazioni da tabella a tabella arrivare alla funzione da chiamare. Insomma una cosa così:

 

  IAT            ID          EID         POINTER

dword 0          id 0        eid 0       ptr 0

dword 1          id 1        eid 1       ptr 1

...              ...         ...          ...

[005AF148] ->   id 2F ->   eid 51 ->   ptr alla corretta api

...              ...         ...          ...

dword n          id n        eid n       ptr n

 

ovviamente i valori delle tabelle sono tutti mescolati, non è che alla prima dword corrisponde l'id 0 e l'eid 0 etc... E' per questo che serve il fixer :-).

Allora partiamo dall'inizio. Ecco tutti i passi che dovremo affrontare.

SCANSIONE OPCODES

Faremo una scansione della sezione .text del gioco, in cerca di tutte le CALL [api], quindi cercheremo un opcode FF 15 xx xx xx xx. Una volta trovato, andremo ad analizzare l'argomento xx xx xx xx della call. Se tale argomento sarà un puntatore ad un valore nella IAT relativa al KERNEL o USER, allora procediamo.

IAT

Siamo alla fine del ventesimo secolo. mmm no, quello è Ken. Sorry. Siamo nella IAT di uno dei due moduli che ci servono. Come facciamo a sapere che ci siamo? A che serve essere qui? Abbiamo ad esempio la call [005AF148]. Se vediamo la IAT del modulo kernel (prendere il PEditor ed Hexeditor forza!), ebbene vediamo nei descrittori della IT che abbiamo aggiustato, che la IAT del Kernel inizia a 001AF08C e finisce a 001AF24C (dove comincia l'iat del modulo seguente). L'argomento della call è in formato VA, quindi in RVA diventa 1AF148. Abbiamo quindi che 001AF08C <= 001AF148 <= 001AF224, quindi sappiamo che la call è diretta ad una funzione del Kernel. Questa discriminazione la dobbiamo fare sia perchè degli altri moduli importati non ci frega niente, sia perchè dobbiamo sapere se la funzione è del Kernel o di User, in quanto poi dovremo applicare la relativa tabella. Cmq, ora che siamo sicuri sulla provenienza di 001AF148  dobbiamo semplicemente associargli il relativo identificatore.

ID

Per la call [005AF148] arrivavamo nella locazione ponte 00EBA688 dove c'era il bel PUSH 2F. Ebbene, 2F è l'identificativo col quale otterremo ciò che vogliamo. C'è un piccolo problema: come facciamo a sapere che a 005AF148 è associato 2F? Qui la cosa sarebbe complicata. Ci dovremmo dumpare tutta la locazione di ponte e vedere man mano dal disassemblato (rigorosamente binario) cosa viene associato a chi. Invece, grazie alla nostra perfetta ricostruzione dei FirstThunk tutto ciò non è necessario :-). Ancora non capite? Bene, mano al softice. Breakate sull'entry point del programma (as usual), ed andiamo a vedere il VA 005AF08C, che è il corrispondente dell'RVA 1AF08C, cioè vediamo in memoria la IAT interessata. Vediamo le prime quatro dwords:

 

00EB03AF     00EB0726      00EB0A9D     00EB0E14

 

bene. Queste sono le locazioni che puntano al codice di "ponte" in cui viene pushato l'identificativo della funzione. Vediamo il disassemblato di tali locazioni. Cominciamo dalla prima. Basta battere

u 00EB03AF

e visualizziamo il relativo codice. Vediamo che il push "identificativo" pusha il valore 0. Bene.

u 00EB0726

e qui troviamo un push 01. Proviamo col prossimo:

u 00EB0A9D

e troviamo push 02.

A questo punto le vostre rotelle saranno già arrivate dove volevo andare a parare :-P. 0.. 1... 2... 3... l'associazione ID è ordinale :-))). Questo ci facilita enromemente il lavoro! Infatti la IAT è solo un array di puntatori, dove l'identificativo corrispondente a un puntatore è il suo indice stesso nell'array. Facile no? Cioè, per trovare l'identificativo a questo punto non dobbiamo far altro che sottrarre il base address della IAT e dividere il risultato per 4 (il prog considera BYTE pointers non DWORDS). Cioè, per l'esempio di call [005AF148] possiamo fare:

 

005AF148 - 005AF08C = BC

BC / 4 = 2F

 

preso! Ora abbiamo l' ID, passiamo all'EID (extra-id :-)).

EID

Ora che abbiamo l'ID basta applicare la formuletta che abbiamo visto prima sulla tabella dumpata. La formuletta era:

xx * 4 + base_address_eid_table

solo che il base address lo possiamo eliminare. Infatti noi metteremo la tabella EID in un array, quindi di nuovo l'EID che vogliamo ottenere ci sarà dato dall'ID usato come indice dell' EID_ARRAY. Di nuovo esempio pratico. L'ID pari a 2F (47 dec) siginifica che il 47° elemento della eid_table è quello che vogliamo (contare per credere :-)). La formula sopra citata non ci serve perchè come base address noi abbiamo la variabile che dichiaraimo e il fattore 4 lo escludiamo perchè piazziamo gli eid nell'array in modo sequenziale, senza mettere pure gli zeri. Quindi definiamo

char eid[200]

ci basterà un semplice assegnamento

valore = eid[47]

ed otterremo l'EID che vogliamo. Però dobbiamo tenere conto del fixing, quindi come visto prima il 2F potrebbe cambiare in 3F. Per ottenere l'EID il procedimento non cambia, dobbiamo solo per ogni dword calcolarci la ROUTINE DECISIVA e poi ottenere 3F tramite il procedimento visto. Di nuovo vi rimando al sorgente che posterò qui sotto.

Ora passiamo al prossimo passo.

POINTER

Il pointer è l'ultimo valore che a noi interessa. Infatti nella tabella con gli EID-ptr-nomi il pointer punta al nome criptato della funzione. Quindi basta sapere a che nome punta e sapremo che funzione chiama la nostra call. I nomi di funzioni però sono criptati :-( Ecco che ci torna utile ancora l'ImportNameDecrypter il cui source vi ho postato prima!! Eh già, il decrypting è identico. Prendiamo la tabella, leviamo gli EID e i PTR, lasciamo solo i nomi criptati delle funzioni. Usiamo il decrypter, e otterremo la lista delle Api belle in chiaro. Beh quasi! Il primo char manca, ma non fa niente, tanto le api si riconoscono lo stesso. Adesso notiamo che le api hanno un ordine diverso da quello che avevano nella IT originale, ma non fa niente. Ora ci rimane l'ultimo passo.

FASE FINALE: METTERE IL PUNTATORE VALIDO DELLA IAT NELL'OPCODE

Okei. Abbiamo ottenuto da 2F il puntatore, sappiamo che punta a GetVersion, ora dobbiamo aggiustare l'opcode della call per puntare effettivamente a GetVersion. Cioè, nel solito caso di call [005AF148] non dobbiamo fare nulla, perchè la call non viene relocata. Nel secondo caso, quello alla linea 00592100 c'è sempre la solita call [005AF148], solo che stavolta la rilocazione avviene. Quindi otteremo l'ID 3F e da lì arriveremo al puntatore a HeapAlloc. Okei. Nella IT che abbiamo ricostruito noi il puntatore a HeapAlloc si trova alla posizione 005AF188, quindi la call dovrà essere modificata in call [005AF188]. Per farlo basterà costruire una nostra tabella in cui associamo il puntatore ottenuto al valore valido della IAT.

 

E qui abbiamo finito. Naturalmente noi non dovremo sobbarcarci tutta questa procedura. Una volta usata la funzione per calcolare se la rilocazione avviene o no possiamo direttamente prendere il valore associato tramite l'ID della funzione (semplicemente moltiplicando l'ID per 4 e sommandolo alla base 1AF08C). Inutile fare giri e rigiri con EID. Cioè, partiamo dalla solita call a GetVersion, determinato che il suo ID è 2F dobbiamo solo calcolare tramite l'RVA Caller se la rilocazione avviene o no. Se non avviene è bene, altrimenti correggiamo l'opcode. Immagino che vi ho fatto due palle così, ma ora finalmente arriva il source del fixer!

 

--------------------------------------BEGIN-------------------------------------------------------------------

void CSafedisc2Dlg::OnButton1()
{
    // FIXING ROUTINE
   
    HANDLE FileHandle, FixedHandle;
    long toread = 2641965; //filesize
    unsigned long letti, scritti, i, j, caller, caller2, caller3, argomento, func_id, id_swap;
    unsigned char *buffer,a,b,c,d, resto;
    j=0;

// allocazione memoria per il file da aprire
    if ((buffer = (unsigned char *) malloc(3000000)) == NULL)
    {
        MessageBox("non allocato");
    }

//apriamo il file
    FileHandle = CreateFile("c:\\cracked4.exe", GENERIC_READ, NULL, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if (FileHandle == INVALID_HANDLE_VALUE)
    {
        MessageBox("Impossibile aprire il file", "Errore", MB_ICONHAND);
        return;
    }

//leggiamo il file e mettiamolo nel buffer precedentemente allocato
    if (ReadFile(FileHandle, buffer, toread, &letti, NULL) == 0)
    {
        MessageBox("Impossibile leggere il file", "Errore", MB_ICONHAND);
        return;
    }

//ora abbiamo un array di char che corrisponde a tutti i byte del file
    for (i=0x1000; i<0x1AF000; i++)
    {
        //cerchiamo le call [value] opcode FF15
        if (buffer[i] == 0xFF && buffer[i+1] == 0x15)
        {
            a = buffer [i+2]; //ricomponiamo l'address chiamato
            b = buffer [i+3]; //codice schifosamente leim
            c = buffer [i+4]; // scusate ma vado di fretta :-)
            d = buffer [i+5];
            __asm
            {
                xor eax, eax //orribile!!!!
                add al, d // avrei dovuto usare il filemapping
                shl eax, 8
                add al, c
                shl eax, 8
                add al, b
                shl eax, 8
                add al, a
                mov argomento, eax // ecco l'iat-addr chiamato
            }
            //ora controlliamo se l'address chiamato
            //fa parte della iat che richiede il fixaggio
            argomento = argomento - 0x00400000; //togliamo l'imagebase
            if ((argomento > 0x001AF088 && argomento < 0x001AF220) || (argomento > 0x001AF24D && argomento < 0x001AF34C))
            {
                //caller = 0x0018CEA1;
                caller = i; //indice array = caller offset
                caller2 = caller;
                caller = caller - 0x1000; //toglimo l'offset della .text
                caller3 = caller;
                //caller2 = caller; //duplichiamo il caller
                // questo è il codice copiato al dplayer
                // prima ROUTINE DECISIVA
                __asm
                {
                    mov eax, caller
                    rol eax, 0x99
                    neg eax
                    add eax, 0x1850277C
                    xor eax, 0x235734FC
                    xor eax, 0x6EEF42E5
                    sub eax, 0x442C6000
                    add eax, 0x74003579
                    add eax, 0x2B690739
                    inc eax
                    ror eax, 0xF8
                    sub eax, 0x7D046F17
                    dec eax
                    xor eax, 0x53F92615
                    dec eax
                    inc eax
                    add eax, 0x001651DD
                    add eax, 0x0ED90A22
                    ror eax, 0xAD
                    ror eax, 0xCD
                    dec eax
                    xor eax, 0x21F21A92
                    dec eax
                    dec eax
                    dec eax
                    dec eax
                    neg eax
                    rol eax, 0x8A
                    neg eax
                    rol eax, 0x6E
                    dec eax
                    xor eax, 0x0D5D1B1D
                    sub eax, 0x5EB754C4
                    rol eax, 0x0D
                    inc eax
                    inc eax
                    neg eax
                    sub eax, 0x27BE10ED
                    sub eax, 0x6D324B38
                    rol eax, 0xE7
                    add eax, 0x6BA210B5
                    add eax, 0x5EC56DA8
                    add eax, 0x5562779D
                    sub eax, 0x211A079E
                    dec eax
                    ror eax, 0x5B
                    inc eax
                    xor eax, caller2
                    mov caller2, eax
                   //seconda ROUTINE DECISIVA
                    add eax, 0x59070A0B
                    rol eax, 0x99
                    xor eax, 0x709F6F9C
                    ror eax, 0x7C
                    sub eax, 0x02D257F8
                    add eax, 0x6D171FBB
                    sub eax, 0x6EEF42E5
                    ror eax, 00
                    add eax, 0x2B690739
                    ror eax, 0x79
                    rol eax, 0x53
                    rol eax, 0xF8
                    rol eax, 0x17
                    sub eax, 0x36B44B00
                    xor eax, 0x53F92615
                    ror eax, 0xC8
                    xor eax, 0x5F7864A1
                    add eax, 0x6B13406B
                    ror eax, 0xDD
                    sub eax, 0x0ED90A22
                    rol eax, 0xAD
                    add eax, 0x6B7A5FCD
                    sub eax, 0x389B33B8
                    add eax, 0x21F21A92
                    add eax, 0x1F986631
                    sub eax, 0x6D9C55CC
                    ror eax, 0x2D
                    xor eax, 0x5EB754C4
                    add eax, 0x19C9710D
                    add eax, 0x4C8D64B8
                    add eax, 0x7F757EA4
                    rol eax, 0xF6
                    ror eax, 0xA7
                    sub eax, 0x371048A6
                    xor eax, 0x27BE10ED
                    ror eax, 0x38
                    xor eax, caller2
                    // dobbiamo fixare??
                    xor edx, edx
                    mov ecx, 4
                    div ecx
                    mov resto, dl
                }
                if (resto < 2)
                {
                    //kernel fix
                    if (argomento > 0x001AF088 && argomento < 0x001AF224)
                    {
                        id_swap = ((argomento - 0x001AF08C) / 4) + 0x66; //prendi eid
                        func_id = (0xBBD031D4 + caller3) % 0x66;
                        func_id = id_swap - func_id;
                        func_id = func_id % 0x66;
                        argomento = (func_id*4) + 0x001AF08C + 0x00400000; //argomento VA corretto   
                        __asm
                        {
                            mov eax, argomento
                            mov a, al
                            shr eax, 8
                            mov b, al
                            shr eax, 8
                            mov c, al
                            shr eax, 8
                            mov d, al
                        }
                        buffer[i+2] = a;
                        buffer[i+3] = b;
                        buffer[i+4] = c;
                        buffer[i+5] = d;
                    }

                    //user fix
                    if (argomento > 0x001AF24C && argomento < 0x001AF34C)
                    {
                        id_swap = ((argomento - 0x001AF250) / 4) + 0x3F; //prendi eid
                        func_id = (0xBBD031D4 + caller3) % 0x3F;
                        func_id = id_swap - func_id;
                        func_id = func_id % 0x3F;
                        argomento = (func_id*4) + 0x001AF250 + 0x00400000; //argomento VA corretto   
                        __asm
                        {
                            mov eax, argomento
                            mov a, al
                            shr eax, 8
                            mov b, al
                            shr eax, 8
                            mov c, al
                            shr eax, 8
                            mov d, al
                        }
                        buffer[i+2] = a;
                        buffer[i+3] = b;
                        buffer[i+4] = c;
                        buffer[i+5] = d;
                    }
                }
            }
        }
    }
    //salva il file fixato
    FixedHandle = CreateFile("c:\\cracked5.exe", GENERIC_WRITE, NULL, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
    if (FixedHandle == INVALID_HANDLE_VALUE)
    {
        MessageBox("Impossibile scrivere c:\\cracked5.exe: file già esistente?", "Errore", MB_ICONHAND);
        return;
    }
    letti++;
    WriteFile(FixedHandle, buffer, toread, &scritti, NULL);
    MessageBox("File Fixato!", "Completato", MB_ICONASTERISK);
    CloseHandle(FileHandle); //rilasciamo gli handle dei file
    CloseHandle(FixedHandle);
    free(buffer); //e liberiamo la memoria allocata prima
}
-----------------------------END---------------------------------------------------------------------------------------

 

ecco fatto. Questà è la routine del fixer (MFC as usual :-)). Prende in input il file cracked4.exe e restituisce il file fixato in cracked5.exe. Beh ormai abbiamo finito! Fixiamo il programma... Fatto. Mi copio cracked5 nella cartella di carmageddon, lo lancio... crash! Arggghhhhh IO LO AMMAZZO!!!! Come crash???? Okei, facciamo appello alla nostra santa pazienza e andiamo a vedere cosa minchia è che provoca il crash. Usate il loader del softice, lanciate il programma, tracciate, tracciate, troverete che la linea del crash è un ret. Analizzando qualche call in precedenza troviamo la linea

004E78B2    CALL    [005AF220]

che chiama WideCharToMultyByte. Questa api è sbagliata, infatti come al solito provoca lo scompenso nello stack e quando arriviamo al RET sono dolori. Eseguiamo il carmageddon originale e al posto di tale call troviamo una call a GetFileAttributesA. Bene, sosituite la call da

CALL  [005AF220]

a

CALL  [005AF198]

lanciate ed ora il programma funzionerà correttamente :-) Il gioco gira i pedoni muoiono. Allora perchè questo cazz di errore? Boh... non sono stato ad indagare più di tanto, semplicemente la call originale chiama 005AF220 che è l'ultimo elemento dell'array dei FirstThunk, forse c'è un trick che non ho visto che oblliga sempre il fix di questa call, ma del resto, chi se ne frega!!! Cambiata a mano con l'HexEditor funziona benissimo. Se doveste incontrare qualche altro errore di questo genere (ma non credo, ho fatto girare il prog in lungo e in largo e funziona benissimo) basterà procedere analogamente.

 

FINALMENTE ARRIVIAMO AL CD CHECK!!

Ora abbiamo il programma decriptato e funzionante, ma richiede sempre il cd originale per runnare!! Vabbè, ora non vi resta che tracciare la messagebox di errore, risalire di qualche riga fino a

00401963   JNZ

e trasformarlo in JUMP incondizionato, così eviterete sempre il cd check. Tutto questo casino solo per uno stramaledetto jump!!!!!!!!

 

CONSIDERAZIONI FINALI

Questo tute l'ho scritto nelle 3 settimane in cui ho lavorato, l'ho portato avanti a ritmi di 10 minuti al giorno, quindi perdonatemi se non è proprio il massimo della comprensibilità. Ho letto molti tute su safedisc (cod, BlackCheck, risc e qualche altro), ma di sicuro questo è il più dettagliato! Avrei voluto aggiungere tutto il codice del DPLAYER commentato, ma causa tempo (e poca disassemblabilità) ho riportato solo le linee principali. Mi è sembrato abbstanza strano l'errore del fixer, cmq mi sono scocciato di esaminare il dplayer (ormai lo so a memoria), quindi fixata a mano quella dword il mio lavoro è finito, ma voi potete tranquillamente ricercare il perchè di quello stupido errore :-). Il codice del fixer è orribile, ci ho lasciato parecchie linee che usavo per debug e che non servono al prog, ma l'importante è che funziona. Ricordate che in questi casi è meglio usare la tecnica del filemapping piuttosto che il CreateFile con il buffer. Ora che ci penso, il gioco fa tutto quel giro col dplayer e la locazione di ponte ogni volta che chiama un'api e questo va a scapito dell'efficienza del programma... Non solo abbiamo un file decriptato e perfettamente ricostruito, ma anche più efficiente!! Questa è una delle ultime versioni di safedisc, è la r4, Yado mi ha detto che ora c'è la r5 e che funziona allo stesso modo.

FINE DEL TUTORIAL (pant pant!)

 

Note finali

Saluto tutti gli amici della UIC, in particolare saluto Yado che mi ha dato delle dritte per questo SafeDisc, ZeroByte che si è fatto un mazzo per dare un bell'aspetto alla UIC, e Luca, al quale devo molto: grazie di cuore. Un Super Grazie anche a Ritz, che mi ha mandato i Cd (qualcuno di troppo!), e last but not least Kill3x, che è sempre gentilissimo e grandissimo (con tutto il rispetto per l'eccelso Xoa, è chiaro!) :-) E anche Nu, che finalmente è sceso tra noi in #crack-it!! E poiiiii ancora Nerds, che dopo mesi di assenza si fa sentire!!!! Ciauz gente! ps Que, ancora non lo hai rivinto il titolo di FOUNDER!

Disclaimer

Il software va comprato e non rubato, ma se avete una savage4 allora non compratelo perchè i pedoni non li vedrete e perderete il 99% di divertimento del gioco. Una bella tirata di orecchi alla Sci >:-)

Noi reversiamo al solo scopo informativo e di miglioramento del linguaggio Assembly.