Crackare Soul Reaver 2
(Manual Unpacking)

Data

by "Quake2"

 

27/02/2002

UIC's Home Page

Published by Quequero


In a time when everyone follows
Ignorance can kill

 

Coooooooomplimenti hai fatto un tute dettagliatissimo con tando di logger bravo bravo

In a world of spoon-fed emotion
Intelligence can save

....

E-mail: [email protected]
Quake2, 51184823, IRCNet: #programmazione, Azzurra: #gameprog-ita/#crack-it/#asm

....

Difficoltà

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

 

In questo tutorial vedremo come crackare l'ultima versione del safedisc (ovvero la 2), come target useremo Soul Reaver 2


Crackare Soul Reaver 2
Manual Unpacking
Written by Quake2

Introduzione

Soul Reaver 2... e chi non conosce le gesta del divino Raziel? La sua continua ricerca della verità su chi sia, cosa sia e a cosa sia destinato? Se non conoscete la saga di The Legacy Of Kain è bene che iniziate a procurarvi il primo Blood Omen (e capirete anche il significato della frase "Kain refused to sacrifice" :)), e metterete le vostre mani su una delle storie più belle mai scritte, ve lo assicuro :)

Tools usati

SoftIce
IceDump
ProcDump 1.6
Hiew
FileMon
UltraEdit32 7.0
W32Dasm
MASM 6.11 o superiore
PEditor 1.7

URL o FTP del programma

Qualsiasi negozio di giochi per pc :)

Notizie sul programma

Soul Reaver 2 usa l'ultima versione disponibile per safedisc, ovvero safedisc 2, che rispetto alle altre è stata abbastanza "imbastardita". Per crackare soul reaver 2, useremo un metodo un po' anomalo ma che funziona alla perfezione (graaaaaaaaaazie Yado :)), prima di tutto per decrittare il codice e i dati useremo il solito metodo del dump, mentre per la IT, scriveremo un programmino che beh... lo vedrete in seguito :)

Essay

Bene, iniziamo, allora per prima cosa dobbiamo individuare la protezione, cioè noi gia lo sappiamo, ma facciamo finta di non saperlo, quindi apriamo la directory del gioco e... sorpresa! niente file .icd, niente clcd32.dll, niente dplayerx.dll, niente di niente! ma che protezione è? Ok, non lasciamoci scoraggiare, il precedente Soul Reaver era protetto sempre con safedisc, quindi diamo un'occhiata al file sr2.exe, apriamolo con hiew, e noteremo subito una cosa strana, ovvero la presenza di due sezioni molto sospette, una chiamata stxt774 e una stxt371, diamo un'occhiata all'entry point... punta dentro stxt371, quindi sappiamo che qui risiede il loader, ma ancora non abbiamo individuato la protezione, non ha niente di classico di safedisc, ma non lasciamoci scoraggiare, quindi chiudiamo hiew, e carichiamo filemon, e attiviamo il logging, facciamo partire soul reaver 2, giocate un po' se volete, uscite e poi date un'occhiata al log prodotto, tombola! tra i vari nomi c'è pure clcd32.dll e clcd16.dll, è safedisc!!! ma voi direte, ste dll do cacchio stanno? Semplice, allora il safedisc quello che fa quando parte è estrarre quelle due dll in C:\windows\temp\cartella\, dove cartella prende un nome che può variare, i dati di queste due dll li prende dal file exe, dato che sono appesi alla fine (attenzione, non fanno parte del file, ma sono solo appesi, il file quando viene mappato in memoria non comprende i dati delle dll), e sono crittati, poi estrae altre due dll, di cui una è l'ex-dplayerx, l'altra non lo so e sinceramente non me ne frega niente :) (ovviamente anche queste ultime due dll sono crittate). Ok, sappiamo che è safedisc, sappiamo che è la versione 2, insomma, sappiamo abbastanza per partire :).
Allora, prima di tutto dobbiamo trovare l'oep del programma, per far ciò, faremo un po' di backtrace, quindi mettete un bel bpx su GetVersion, fate partire SR2, e premete F5 finche il softice smette di apparire e compare la schermata col logo della eidos, dopo questa schermata riapparirà il softice, premete F12, e vi trovere alla prima call del programma, qualche riga più sopra, ci sarà l'entry point (push ebp) e sarà al VA 004D810E, quindi mettiamo un bel break, e togliamo pure il break su GetVersion, fate partire il gioco, uscite e fatelo ripartire, quando il softice brekka, fate a eip e poi jmp eip, così il processo entra in loop, ora lanciate ProcDump, selezionate il task sr2.exe, e dumpatelo (Full dump), e chiamatelo (con molta fantasia) Crack.exe, killate il processo (oppure riassemblate il push ebp, insomma basta che uscite da soul reaver 2 :)), e la prima parte è fatta :). Ora abbiamo il codice bello in chiaro, iniziamo a dare un'occhiata al file dumpato, per prima cosa controllate la dimensione, è quasi 1mb più piccolo, questo perché nel dump ovviamente non sono presenti le dll del safedisc, ora diamo un'occhiata all'IAT (inizio della sezione .rdata), come vedrete conterrà qualcosa del genere:

000E2000 D3 A9 EC 01  44 16 E8 BF  69 B0 EC 01  B4 B3 EC 01 ....D...i.......
000E2010 FF B6 EC 01  4A BA EC 01  00 00 00 00  72 A4 B7 BF ....J.......r...
000E2020 00 00 00 00  60 B0 00 70  00 00 00 00  37 A3 EC 01 ....`..p....7...
000E2030 82 A6 EC 01  00 00 00 00  77 03 EB 01  C2 06 EB 01 ........w.......
000E2040 0D 0A EB 01  58 0D EB 01  A3 10 EB 01  EE 13 EB 01 ....X...........
000E2050 39 17 EB 01  84 1A EB 01  CF 1D EB 01  1A 21 EB 01 9............!..
000E2060 65 24 EB 01  B0 27 EB 01  FB 2A EB 01  46 2E EB 01 e$...'...*..F...
000E2070 91 31 EB 01  DC 34 EB 01  27 38 EB 01  72 3B EB 01 .1...4..'8..r;..


Dunque, gli indirizzi tipo 01ECxxxx o 01EBxxxx puntano a codice del safedisc che spiegherò più tardi, mentre come potete vedere per le prime entry, c'è un'entry che punta a BFE81644, che guarda caso è una funzione di advapi32, quindi questo ci suggerisce che non tutte le api sono risolte, ma solo alcune, la scelta di quali api risolvere è fatta al momento del wrapping, quindi è del tutto arbitraria, ma da quello che sono riuscito a capire, serve ALMENO un'api per dll importata (perché il safedisc per prendere gli entry point dell'api non usa GetProcAddress come nelle versioni precedenti, ma si scanna la export table, e per sapere il base address della dll, tramite una sua IT carica le dll necessarie al gioco e poi prende il loro base address, che metterà in una tabella che verrà usata in seguito per trovare l'entry point dell'api), comunque questi sono dettagli per il momento, ora sappiamo dov'è la IAT, dobbiamo trovare la IT originale (quella che c'è adesso è quella usata dal safedisc, e a noi non ci serve :)), e in questo caso il metodo è del tutto "casuale" ovvero a partire dalla IAT scorrete il file alla ricerca di una zona che possa sembrare una IT, e questa zona si trova all'RVA 000E5840, ed eccola qui:

000E5840 00 00 00 00  FF FF FF FF  FF FF FF FF  FC 5E 0E 00 .............^..
000E5850 38 20 0E 00  00 00 00 00  FF FF FF FF  FF FF FF FF 8 ..............
000E5860 72 61 0E 00  8C 21 0E 00  00 00 00 00  FF FF FF FF ra...!..........
000E5870 FF FF FF FF  A0 61 0E 00  2C 20 0E 00  00 00 00 00 .....a.., ......
000E5880 FF FF FF FF  FF FF FF FF  0E 62 0E 00  00 20 0E 00 .........b... ..
000E5890 00 00 00 00  FF FF FF FF  FF FF FF FF  52 62 0E 00 ............Rb..
000E58A0 84 22 0E 00  38 59 0E 00  FF FF FF FF  FF FF FF FF ."..8Y..........
000E58B0 5C 62 0E 00  1C 20 0E 00  98 5B 0E 00  FF FF FF FF \b... ...[......
000E58C0 FF FF FF FF  7C 62 0E 00  7C 22 0E 00  40 59 0E 00 ....|b..|"..@Y..
000E58D0 FF FF FF FF  FF FF FF FF  9C 62 0E 00  24 20 0E 00 .........b..$ ..
000E58E0 44 5B 0E 00  FF FF FF FF  FF FF FF FF  66 63 0E 00 D[..........fc..
000E58F0 28 22 0E 00  70 5B 0E 00  FF FF FF FF  FF FF FF FF ("..p[..........
000E5900 1C 64 0E 00  54 22 0E 00  00 00 00 00  00 00 00 00 .d..T"..........
000E5910 00 00 00 00  00 00 00 00  00 00 00 00              ............


Allora, per prima cosa vi ricordo la struttura di una entry dell'IT:

+00h OriginalFirstThunk
+04h TimeDateStamp
+08h ForwarderChain
+0Ch NameRVA
+10h FirstThunk


dove OriginalFirstThunk è un puntatore ad una tabella (terminata con una dword vuota) che contiene dei puntatori ai nomi delle funzioni importate, o nel caso che la funzione venga importata con l'ordinal, contengono un'array di ordinal, TimeDateStamp e ForwarderChain non ci interessano, NameRVA punta al nome della dll, e FirstThunk punta ad un'array di dword che a runtime verrano sostuituite con gli entry point delle funzioni importate da quella dll. Quindi, come possiamo vedere, abbiamo 5 entry senza OriginalFirstThunk, e queste sono le entry che dovremo sistemare, ma per prima cosa vediamo a cosa puntano, per vederlo, basta vedere a che nome punta il campo a +0Ch dell'entry, ovvero NameRVA, quindi abbiamo in ordine: Kernel32.dll, User32.dll, Gdi32.dll, Advapi32.dll e ole32.dll, ricordatevi questo ordine che in seguito ci servirà :). Ok adesso che sappiamo dov'è l'import table, apriamo ProcDump e usiamo il suo pe editor per sistemare l'import table e l'entry point e salviamo il file. Ok, siamo quasi al 10% del lavoro :). Adesso a questo punto, non ci rimane che fixare la IT e abbiamo finito, semplice no? Se magari :) Adesso prima di continuare, vorrei aprire una piccola parentesi su come safedisc risolve le api, principalmente ci sono due casi, nel primo caso, è quando abbiamo o un call dword ptr [xxx], o un jmp dword ptr [xxx], o un call edi, o call esi o call ebx o call ebp, in questi casi il return value viene preso dallo stack (perché come tutti sapete quando viene chiamata una funzione il return valure viene pushato nello stack), mentre nel secondo caso, abbiamo una cosa del genere:

jmp 000762099

000762099:

:00762099 53 push ebx
:0076209A E800000000 call 0076209F

CALL at Address 0076209A:

:0076209F xchg dword ptr [esp], eax
:007620A2 pushfd
:007620A3 add eax, FFFFFF61
:007620A8 mov ebx, dword ptr [eax]
:007620AA imul ebx, 00000005
:007620AD add ebx, dword ptr [eax+04]
:007620B0 popfd
:007620B1 pop eax
:007620B2 xchg dword ptr [esp], ebx
:007620B5 ret


Questo pezzo di codice, non fa altro che generare un'indirizzo che corrisponde ad una locazione di memoria allocata dal safedisc, in cui è presente del codice che pusha nello stack il return address vero, e poi chiama la routine di risoluzione, ora analizzeremo nel dettaglio i vari casi, quindi assicuratevi che il bpx sull'entry point sia ancora attivo, e fate partire SR2, steppate finche non arrivate alla prima call, e entrateci con F8, ed arriverete a del codice che fa da ponte, è da notare che c'è una routine ponte per ogni api chiamata, nel caso di GetVersion, la routine di ponte sarà questa:

push BFEA1236
pushfd
pushad
push esp
push 01EB3EFD
call 10043C90
add esp, 08
push 00
pop eax
popad
popfd
ret


Allora, il push 01EB3EFD pusha nello stack l'indirizzo di una tabella fatta da due DWORD, di cui la prima identifica la dll, mentre la seconda identifica la funzione da chiamare, ad esempio, se la dll è Kernel32, la prima dword sarà 0, 1 per user32, 2 per gdi32 e così via, mentre la numerazione per le funzioni è un po' diversa, ad esempio, mettiamo che l'api 3 (contando a partire da 1) non viene risolta dal fixer (ma c'è direttamente l'indirizzo nella IAT), quindi la numerazione sarà 0, 1, 3, 4, 5, n, come vedete, non viene contata l'api gia risolta. Mentre la call 10043C90 chiama la funzione di risoluzione vera e propria, ora siccome il codice di risoluzione è un po' lunghetto, riporterò solo il pezzo di codice iniziale, mentre per le funzioni chiamate, di quelle meno importanti darò solo una descrizione, di quelle più importanti (in questo caso solo la funzione che prende l'entry point dell'api) riporterò i pezzi di codice più importanti, allora dopo la call ci troveremo qua:

10043CD8: push eax
10043CD9: mov eax, ebp
10043CDB: add eax, 38
10043CE6: mov eax, [eax] ; in eax abbiamo il return address
10043CE8: sub eax, 06 ; ora invece in eax abbiamo il punto in cui è stata chiamata la funzione (si sottrae 6, perché la dimensione di una call dword ptr [xxx] è di 6 byte, stesso discorso per jmp dword ptr [xxx], mentre per le call edi, esi, ecc..., non ci ritroveremo al punto di chiamata)
10043CEB: mov [ebp-2C], eax
10043CEE: pop eax
10043D01: mov ecx, [ebp+08] ; in ebp+08 abbiamo la tabella dll:funzione
10043D04: mov edx, [ecx] ; in edx adesso abbiamo il numero della dll (0 in questo caso)
10043D06: mov [ebp-18], edx
10043D09: mov eax, [ebp+08]
10043D0C: mov ecx, [eax+04]
10043D0F: mov [ebp-14], ecx
10043D12: cmp [ebp-18], -01 ; controlla che il numero della funzione non sia -1, se lo è, allora vuol dire che la funzione è stata chiamata con quel jmp 00762xxx, quindi prende il numero di dll e di funzione in un'altro modo che descriverò in seguito
10043D16: jnz 10043DCF


ora seguendo quel jmp vi ritroverete in un punto di codice in cui inizia la risoluzione, se non viene eseguito, appunto come dicevo prima, verrà calcolato il numero della dll e della funzione in un'altro modo, non riporto il codice perché tanto per risolvere i jmp 00762xxx basta mettere l'eip all'RVA dove sono presenti e farglielo eseguire, tanto il return address lo pusha lui, comunque questo lo vedremo in seguito, ora concentriamoci sulla risoluzione della funzione, quindi stavamo a 10043DCF:

10043DCF: mov eax, [ebp-18]
10043DD2: imul eax, eax, 8D ; moltiplica il numero della dll per 8d, 8d è una specie di numeretto magico che serve per calcolare
l'indirizzo di alcuni valori fondamentali e alcune tabelle
10043DD8: mov ecx, [10065218] ; se dal softice fate d 10065218, vedrete l'indirizzo 01FDF00C, questo è un'indirizzo molto
importante, perché è usato come base address per calcolare l'indirizzo di praticamente tutte le
tabelle e i valori del safedisc
10043DDE: mov edx, [eax+ecx+C3] ; in eax abbiamo l'indirizzo di una tabella (una per ogni dll), di cui tra un po' di codice ne spiegherò il
significato
10043DE5: mov [ebp-1C], edx
10043E01: mov eax, [ebp-2C] ; in ebp-2c come potete vedere qualche riga più sopra abbiamo il caller address (solo nel caso di jmp
dword ptr e call dword ptr )

10043E04: mov [ebp-04], eax
10043E2C: mov ecx, [ebp-2C]
10043E2F: push ecx
10043E30: mov edx, [ebp-14]
10043E33: push edx
10043E34: mov eax, [ebp-1C] ; riecco la tabella di prima
10043E3A: push eax
10043E38: call 10041F30 ; questa call basandosi sulla tabella di prima, controlla se quella funzione è stata gia risolta, in questo caso salta tutto il resto, così non perde tempo a risolvere 2 volte la stessa api
10043E3D: add esp, 0C
10043E40: mov [ebp-0C], eax
10043E43: cmp dword ptr [ebp-0C], 00
10043E47: jz 10043FC8 ; se non è stato gia risolto, allora procedi alla risoluzione

10043FC8: un po' di morfismo inutile che ci porta a 10043FD4
10043FD4: mov eax, [ebp-14]
10043FD7: mov [ebp-20], eax
10043FF3: lea ecx, [ebp-28]
10043FF6: push ecx
10043FF7: lea edx, [ebp-24]
10043FFA: push edx
10043FFB: lea eax, [ebp-08]
10043FFE: push eax
10043FFF: mov ecx, [ebp-2C]
10044002: push ecx
10044003: call 10044880 ; questa call non fa altro che controllare che il caller address sia dentro la sezione .text o .stxt
10044008: add esp, 10
1004400B: and eax, 0000FFFF
10044010: cmp eax, 01
10044013: jnz 10044149
10044029: mov edx, [ebp-08]
1004402C: add edx, [ebp-28] ; ora in eax abbiamo il VA della sezione .text, ovvero 00401000
1004402F; mov eax, [ebp-2C] ; in eax abbiamo il caller address
10044032: sub eax, edx ; ora in eax abbiamo caller address - 00401000
10044034: mov [ebp-10], eax
10044050: mov ecx, [ebp-10]
10044053: push ecx
10044054: call 10044CE0 ; questa funzione fa i soliti calcoli fisico-nucleari sul caller address - 00401000 e poi divide il risultato per 4, se il resto è maggiore di 2 torna 1, altrimenti 0, se viene ritornato 1, allora dal numero della funzione ricava un'altro numero, altrimenti usa quello, poi spiegherò per cosa viene usato
10044059: add esp, 04
1004405C: and eax, 0000FFFF
10044061: cmp eax, 01
10044064: jnz 10044149
1004408E: mov edx, [ebp-14]
10044091: imul edx, edx, 34B
10044097: mov eax, [ebp-04] ; caller address
1004409A: mov ecx, [ebp-1C] ; ecco di nuovo la tabella
1004409D: mov eax, [eax+02] ; prende l'indirizzo dell'entry nella IAT (siccome il call dword ptr occupa 2 byte, a eax+2 abbiamo l'indirizzo)
100440A0: cmp eax, [edx+ecx+332] ; controlla l'indirizzo nella IAT con quello memorizzato nella tabella per questa funzione
100440A7: jnz 10044149
100440AD: mov ecx, [ebp-04]
100440B0: xor edx, edx
100440B2: mov dl, [ecx]
100440B4: cmp edx, FF
100440BA: jnz 10044149
100440C0: mov eax, [ebp-04]
100440C3: xor ecx, ecx
100440C5: mov cl, [eax+1]
100440C8: cmp ecx, 15 ; a questo punto controlla che l'opcode sia FF (controllato prima) 15, ovvero call dword ptr
100440CB: jnz 10044149
100440DD: mov edx, [ebp-14]
100440E0: mov [ebp-20], edx
100440E3: mov eax, [10065218] ; riecco il base address :)
100440E8: mov ecx, [eax+28] ; ora in ecx abbiamo un'altro valore magico, ovvero 9EE3E155
100440EB: add ecx, [ebp-10] ; aggiunge al valore magico il caller address - 00401000
100440EE: push ecx
100440EF: mov edx, [ebp+20]
100440F2: push edx
100440F3: mov eax, [ebp-18]
100440F6: imul eax, eax, 8D
100440FC: mov ecx, [10065218]
10044102: mov edx, [eax+ecx+58] ; ora in edx abbiamo un'altro valore che rimane costante, per kernel32 è 54, che, casualmente :), rappresenta il numero delle funzioni importate, se volete sapere quante funzioni ci sono per ogni api, basta che moltiplicate il numero della dll per 8D, e poi in softice fate d risultato+ecx+58, così avrete il numero di funzioni
10044106: push edx
10044107: call 100445E0 ; questa funzione a partire dai parametri passati, attraverso 2 divisioni calcola un valore che viene ritornato in eax
1004410C: add esp, 0C
1004410F: mov [ebp-20], eax
1004411F; mov eax, [ebp-20]
10044122: shr eax, 03
10044125: mov ecx, [ebp-18]
10044128: mov edx, [10065214] ; ecco un'altro base address, ma questo è meno importante del precedente, perché è usato solo qui
1004412E: mov ecx, [ecx*4+edx]
10044131: xor edx, edx
10044133: mov dl, [eax+ecx]
10044136: mov ecx, [ebp-20]
10044130: and ecx, 07
1004413C: mov eax, 01
10044141: mov eax, cl
10044143: and edx, eax
10044145: test edx, edx
10044147: jz 100440E3 ; ripete questo ciclo finche il risultato è diverso da 0, in questo caso significa che abbiamo trovato il vero numero dell'api da chiamare, per GetVersion, da 12 diventa 30
1004415D: mov ecx, [ebp-18]
10044160: imul ecx, ecx, 8D
10044166: mov edx, [10065218]
1004416C: mov eax, [ecx+edx+4C] ; ora in eax abbiamo il base address di una tabella di numeri, dove il valore ottenuto dal codice precedente verrà utilizzato come indice in questa tabella, in questo modo abbiamo un valore che poi verrà usato come indice nella tabella di nomi delle funzioni e della lunghezza, ma questo lo spiegherò più avanti
10044170: mov ecx, [ebp-20]
10044173: mov edx, [ecx*4+eax]
10044176: mov [ebp-20], edx ; ora in ebp-20 abbiamo l'indice di cui parlavo prima
10044185: mov eax, [ebp-20]
10044188: imul eax, eax, 8D
1004418E: mov ecx, [ebp-12]
10044191: mov edx, [eax+ecx+2FA]
10044198: mov [ebp-0C], eax
1004419B: cmp dword ptr [ebp-0C], 00
1004419E: jnz 100441CA
100441A1: mov eax, [ebp-20] ; l'indice trovato in precedenza
100441A4: push eax
100441A5: mov ecx, [ebp-18]
100441A8: push ecx
100441A9: call 10043630 ; chiama la funzione che decritta il nome e ritorna l'entry point della funzione, di questa funzione ripoterò gran parte del codice
10044265: mov esp, [ebp+0C] ; ho saltato tutto il codice che viene dopo la call, perché sostanzialmente quello che fa è questo: inserisce nella tabella che dicevamo prima, la funzione chiamata con relativo caller address (quindi avremo una cosa del genere: caller address:api_entry_point) e recritta il nome
10044268: popad
10044269: popfd
1004426A: ret
; quando verrà eseguito il ret, vi ritroverete all'entry point dell'api chiamata

Ok, il codice tolta la robaccia di morfismo è abbastanza chiaro, comunque quello che fa sostanzialmente è questo:
1) Controlla che il call provenga dalla sezione .text o .stxt
2) Controlla che la funzione non sia stata gia risolta, se è stata gia risolta allora non risolve
3) Se non è stata risolta, procede alla risoluzione
4) Prende il numero dell'api, se non è una call dword ptr, allora si basa su quello, altrimenti procede a ricavare un'altro numero
5) Il numero dell'api o quello ricavato dal numero dell'api, è usato come indice in una tabella, il valore preso da questa tabella è poi usato per risolvere l'api
6) Una volta risolto tutto, recritta il nome, aggiunge l'api risolta alla tabella usata per controllare che l'api non sia stata gia risolta
7) Attraverso un ret salta all'entry point dell'api

Ok, ora sappiamo come funziona safedisc, ma con il metodo che ho deciso di usare, tutta sta roba non è necessaria, ho voluto descrivere il funzionamento del safedisc solo per chiarire meglio le cose, e poi è sempre meglio saperlo :). La cosa che ci interessa a noi è la funzione di decrypt del nome che andremo ad analizzare adesso:

10043639: mov dword ptr [ebp-0C], 00
100436A4: mov eax, [10065218]
100436A9: mov ecx, [ebp+08]
100436AC: cmp ecx, [eax+0F] ; controlla che il numero della dll non sia superiore del numero massimo di dll, ovvero 5
100436AF: jbe 10043B2D
100436B5: mov edx, [ebp+08]
100436B8: imul edx, edx, 8D
100436BE: mov eax, [10065218]
100436C3: mov ecx, [ebp+0C] ; ecx contiene il numero della funzione, 50h per GetVersion
100436C6: cmp ecx, [edx+eax+58] ; controlla che non sia superiore al numero massimo di funzioni importate dalla dll, nel caso di kernel32 sono 54h (84)
100436CA: jae 10043B2D
100436E8: mov edx, [ebp+08]
100436EB: imul edx, edx, 8D
100436F1: mov eax, [10065218]
100436F6: mov ecx, [edx+eax+B5] ; mette in ecx il base address di una tabella di puntatori ai nomi delle funzioni
100436FD: mov edx, [ebp+0C]
10043700: mov eax, [edx*4+ecx] ; il numero dell'api viene appunto usato come indice all'interno della tabella, e in eax abbiamo il puntatore al nome (crittato della funzione)
10043703: mov [ebp-0C], eax
10043706: mov ecx, [ebp+08]
10043709: imul ecx, ecx, 8D
1004370F: mov edx, [10065218]
10043715: mov eax, [ecx+edx+BF] ; in eax abbiamo il base address di un'altra tabella, stavolta contiene un'array di puntatori ad una dword, che contiene la lunghezza della funzione
1004371C: mov ecx, [ebp+0C]
1004371F: mov edx, [ecx*4+eax] ; adesso in edx abbiamo il puntatore alla lunghezza
10043722: mov eax, [edx] ; in eax abbiamo la lunghezza (per GetVersion sarà 0B)
10043724: mov [ebp-18], eax
10043741: mov ecx, [10065218]
10043747: add ecx, 26 ; ecx punta ad un'array di 4 dword (9EE3E155, 76AD7FF7, 2A2A7F59, DACE2F29) che sono usate per il decrypt del nome (solo per la prima funzione di decrypt, poi lo spiegherò meglio)
1004374A: push ecx
1004374B: mov edx, [ebp-18] ; in edx abbiamo il puntatore alla lunghezza del nome
1004374E: push edx ; e lo pushiamo :)
1004374F: mov eax, [ebp-0C] ; in eax abbiamo il puntatore al nome
10043752: push eax ; e pushiamo pure quello :)
10043753: call 10029CD0 ; chiama la funzione di decrypt, che sostanzialmente è divisa in 3 parti fondamentali, che poi spiegherò in dettaglio, adesso vediamo il codice della funzione di decrypt:

10029DCA: mov eax, [ebp+0C]
10029DDD: shr eax, 03 ; divide eax per 8
10029DD0: mov [ebp-10], eax
10029DD3: cmp dword ptr [ebp-10], 00
10029DD7: jnz 10029EA4 ; se il risultato è 0, allora la lunghezza del nome è < di 8 caratteri, e chiama un'altra funzione di decrypt
10029ECC: mov eax, [ebp+0C]
10029ECF: xor edx, edx
10029ED1: mov ecx, 8
10029ED6: div ecx
10029ED8: mov [ebp-0C], edx ; salva il resto della divisione, che verrà usato in seguito
10029EE8: cmp dword ptr [ebp-0C], 00 ; controlla se il resto è 0 (ovvero il nome è lungo esattamente 8 caratteri
10029EEC: jz 10029FA9 ; se si salta la prima parte di decrypt
10029F27: mov edx, [ebp+0C]
10029F29: mov eax, [ebp+08]
10029F2D: lea ecx, [edx+eax-08] ; ecx punta alla posizione len-8 del nome (dove len è la lunghezza)
10029F31: mov [ebp-14], ecx
10029F58: mov edx, [ebp+10] ; mette in edx un puntatore alla tabella di 4 dword
10029F5B: push edx
10029F5C: mov eax, [ebp-14] ; in eax abbiamo il puntatore a len-8
10029F5F: push eax
10029F60: call Decrypt ; chiama la prima funzione di decrypt di cui non riporto il codice perché è troppo lungo, comunque è una serie di xor con i valori della tabella di 4 dword di prima
10029F65: add esp, 08
10029FA9: mov ecx, [ebp+08]
10029FAC: mov [ebp-14], ecx
10029FF0: mov dword ptr [ebp-04], 1437A1E9 ; primo valore di xor
1002A01F: mov dword ptr [ebp-08], 00
1002A026: jmp 1002A031
1002A028: mov edx, [ebp-08]
1002A02B: add edx, 01
1002A02E: mov [ebp-08], edx
1002A031: mov eax, [ebp-08]
1002A034: cmp eax, [ebp-10]
1002A037: jae 10024196
1002A03B: jbe 1002A04A
1002A04A: mov ecx, [ebp-14]
1002A04D: mov edx, [ecx]
1002A04F: xor edx, [ebp-04]
1002A052: mov eax, [ebp-14]
1002A055: mov [eax], edx
1002A07C: mov ecx, [ebp-04]
1002A07F: imul ecx, ecx, E32A79DC ; al primo valore di xor moltiplica un secondo valore
1002A085: add ecx, 274B0EFA ; e poi ne aggiunge un terzo
1002A08B: mov [ebp-04], ecx
1002A0A2: mov edx, [ebp-14]
1002A0A5: mov eax, [edx+04]
1002A0A8: xor eax, [ebp-04]
1002A0AB: mov ecx, [ebp-14]
1002A0AE: mov [ecx+04], eax
1002A0BE: mov edx, [ebp-04]
1002A0C1: imul edx, edx, E32A79DC
1002A0C7: add edx, 292881ED
1002A0CD: mov [ebp-04], edx
1002A120: mov eax, [ebp+10]
1002A123: push eax
1002A124: mov ecx, [ebp-14]
1002A127: push ecx
1002A128: call Decrypt
1002A12D: add esp, 08
1002A154: mov edx, [ebp-14]
1002A157: add edx, 08
1002A15A: mov [ebp-14], edx
1002A15D: jmp 10027A28


Allora quello che fa questa funzione, è praticamente decrittare a 8 a 8 il nome, se il nome è minore di 8 caratteri allora viene usata un'altra funzione molto semplice, altrimenti viene usata questa appunto, che chiama a sua volta un'altra funzione che non fa altro che decrittare il nome, come potete vedere il decrypt è cambiato molto dalle precedenti versioni, ma questo non è un problema, noi sappiamo come agisce, quindi adesso ci basta scriverci un decrypter che farà esattamente questo, quindi, riassumiamo brevemente cosa dovrà fare il nostro decrypter:

1) Prendere il nome crittato
2) Controllare se la lunghezza è minore di 8
3) se si, chiamare una routine particolare che eseguirà il decrypt tutto in un colpo
4) se no continuare
5) restituire il nome decrittato
6) passare al prossimo nome

Ok, adesso ci resta da analizzare la routine di decrittazione nel caso che il nome sia inferiore agli 8 caratteri, questa routine è semplicissima, riporto direttamente il codice che ho convertito in C:

void SimpleDecrypt(char *Name, int len)
{
    int mv = 0x56;
    for(int i = 0; i < len; i++)
    {
        Name[i] ^= mv;
        mv *= 0x32;
        mv += 0x34;
        mv &= 0x000000FF;
    }
}


Come vedete è talmente semplice che è quasi redicola :), quello che fa è xorare il primo byte con un numero fisso, ovvero 56h, e successivamente xorare gli altri con il risultato ottenuto dalla moltiplicazione la somma e l'and.

Ora rimane un'ultima cosa, dove stanno i nomi da decrittare? Allora diamo un'occhiata al campo NameRVA dell'entry per kernel32 nella import table, punta ad una zona di memoria, poco più sopra ci sono i byte da decrittare, attenzione, le funzioni di kernel32 stanno in due zone, una all'inizio e una alla fine, comunque ecco la lista delle zone di memoria, con relativo indirizzo e lunghezza:

Kernel32_1: E5BB0 - E5EFC, lunghezza: 34C
Kernel32_2: E6428 - E66D2, lunghezza: 2AA
User32: E5F0A - E6172, lunghezza: 268
Gdi32: E617E - E61A0, lunghezza: 22
Advapi32: E61AA - E620E, lunghezza: 64
Ole32: E621C - E6252, lunghezza: 36


Ok, adesso sappiamo dove stanno le funzioni, però attenzione, il modo in cui sono scritte è un po' particolare, vi faccio un'esempio, aprite ultraedit, andate su Search, Find, e mettete i byte crittati di GetVersion (li ho presi io per voi :)), che sono: C1 58 86 71 BA 42 9F F9 69 D2 E3, e fate Find Next, ovviamente ve li trova, e stanno all'indirizzo E64EE, però date un'occhiata 2 byte prima, troverete 0B 7A, ora 0B è la lunghezza e lo sappiamo, ma quel 7A non centra assolutamente niente col nome, quindi nel codice del decrypter, dovremmo anche tener conto di questo, ovvero si legge la lunghezza, si salta un byte, si legge il nome e si decritta, e così via, sperò di essere stato chiaro :). Ora sorge un'altro problema, io me ne sono accorto durante l'esecuzione del decrypter, ovvero dopo aver fatto l'ultima api, la lunghezza che viene letta non è esatta (per kernel32 viene letto 4B) e quindi si sballa il processo di decrypt, questo è dovuto al fatto, che per ogni serie di nomi di funzioni, c'è un byte terminatore dopo l'ultima funzione, per far capire al safedisc che deve smettere di prendere le funzioni, per kernel32 questo byte è 4B, mentre Gdi32 non è presente (per GDI32 ci baseremo sull'inizio del nome della DLL), ecco i vari terminatori:

Kernel32_1: 4B
Kernel32_2: 47
User32: 55
Gdi32: 47
Advapi32: 41
Ole32: 6F


Ok, adesso vediamo il codice del decrypter:

__declspec(naked) void Decr(char *Ptr, DWORD *XorValue)
{
    __asm {
        push ebp
        mov ebp, esp
        sub esp, 00000010h
        mov eax, dword ptr [ebp+08h]
        mov ecx, dword ptr [eax]
        mov dword ptr [ebp-08h], ecx
        mov edx, dword ptr [ebp+08h]
        mov eax, dword ptr [edx+04h]
        mov dword ptr [ebp-0Ch], eax
        mov ecx, 20h
        mov dword ptr [ebp-10h], ecx
        mov edx, 9E3779B9h
        shl edx, 05h
        mov dword ptr [ebp-04h], edx
jmp1:
        mov eax, dword ptr [ebp-10h]
        mov ecx, dword ptr [ebp-10h]
        sub ecx, 00000001
        mov dword ptr [ebp-10h], ecx
        test eax, eax
        jbe jmp2
        mov edx, dword ptr [ebp-08h]
        shl edx, 04h
        mov eax, dword ptr [ebp+0Ch]
        add edx, dword ptr [eax+08h]
        mov ecx, dword ptr [ebp-08h]
        add ecx, dword ptr [ebp-04h]
        xor edx, ecx
        mov eax, dword ptr [ebp-08h]
        shr eax, 05h
        mov ecx, dword ptr [ebp+0Ch]
        add eax, dword ptr [ecx+0Ch]
        xor edx, eax
        mov eax, dword ptr [ebp-0Ch]
        sub eax, edx
        mov dword ptr [ebp-0Ch], eax
        mov ecx, dword ptr [ebp-0Ch]
        shl ecx, 04h
        mov edx, dword ptr [ebp+0Ch]
        add ecx, dword ptr [edx]
        mov eax, dword ptr [ebp-0Ch]
        add eax, dword ptr [ebp-04h]
        xor ecx, eax
        mov edx, dword ptr [ebp-0Ch]
        shr edx, 05h
        mov eax, dword ptr [ebp+0Ch]
        add edx, dword ptr [eax+04h]
        xor ecx, edx
        mov edx, dword ptr [ebp-08h]
        sub edx, ecx
        mov dword ptr [ebp-08h], edx
        mov eax, dword ptr [ebp-04h]
        sub eax, 9E3779B9h
        mov dword ptr [ebp-04h], eax
        jmp jmp1
jmp2:
        mov ecx, dword ptr [ebp+08h]
        mov edx, dword ptr [ebp-08h]
        mov dword ptr [ecx], edx
        mov eax, dword ptr [ebp+08h]
        mov ecx, dword ptr [ebp-0Ch]
        mov dword ptr [eax+04h], ecx
        mov esp, ebp
        pop ebp
        ret
    }
}

__declspec(naked) void Decrypt(char *Name, int NameLen, DWORD *XorValue)
{
    __asm {
        push ebp
        mov ebp, esp
        sub esp, 14h
        push ebx
        push esi
        push edi
        mov eax, [ebp+0Ch]
        shr eax, 03h
        mov [ebp-10h], eax
        mov eax, [ebp+0Ch]
        xor edx, edx
        mov ecx, 08h
        div ecx
        mov [ebp-0Ch], edx
        cmp dword ptr [ebp-0Ch], 0h
        jz jmp1
        mov edx, [ebp+0Ch]
        mov eax, [ebp+08h]
        lea ecx, [edx+eax-08h]
        mov [ebp-14h], ecx
        mov edx, [ebp+10h]
        push edx
        mov eax, [ebp-14h]
        push eax
        call Decr
        add esp, 08h
jmp5:
        mov ecx, [ebp+08h]
        mov [ebp-14h], ecx
        mov dword ptr [ebp-04h], 1437A1E9h
        mov dword ptr [ebp-08h], 0h
        jmp jmp2 //1002A031
jmp4:
        mov edx, [ebp-08h]
        add edx, 01h
        mov [ebp-08h], edx
jmp2:
        mov eax, [ebp-08h]
        cmp eax, [ebp-10h]
        jae jmp3 //1002A196

        mov ecx, [ebp-14h]
        mov edx, [ecx]
        xor edx, [ebp-04h]
        mov eax, [ebp-14h]
        mov [eax], edx
        mov ecx, [ebp-04h]
        imul ecx, ecx, 0E32A79DCh
        add ecx, 274B0EFAh
        mov [ebp-04], ecx
        mov edx, [ebp-14h]
        mov eax, [edx+04h]
        xor eax, [ebp-04h]
        mov ecx, [ebp-14h]
        mov [ecx+04h], eax
        mov edx, [ebp-04h]
        imul edx, edx, 0E32A79DCh
        add edx, 292881EDh
        mov [ebp-04h], edx
        mov eax, [ebp+10h]
        push eax
        mov ecx, [ebp-14h]
        push ecx
        call Decr
        add esp, 08h
        mov edx, [ebp-14h]
        add edx, 08h
        mov [ebp-14h], edx
        jmp jmp4
jmp1:
        jmp jmp5
jmp3:
        mov ax, 01h
        pop edi
        pop esi
        pop ebx
        add esp, 14h
        mov esp, ebp
        pop ebp
        ret
    }
}

void SimpleDecrypt(char *Name, int len)
{
    int mv = 0x56;
    for(int i = 0; i < len; i++)
    {
        Name[i] ^= mv;

        mv *= 0x32;
        mv += 0x34;
        mv &= 0x000000FF;
    }
}

int main()
{
    DWORD XorValue[] = {0x9EE3E155, 0x76AD7FF7, 0x2A2A7F59, 0xDACE2F29};

    FILE *f = fopen("crack.exe", "r+b");
    FILE *f2 = fopen("crack2.exe", "r+b");
    fseek(f, 0xE5BB0, SEEK_SET);
    fseek(f2, 0xE5BB0, SEEK_SET);

    char Buffer[150];
    memset(Buffer, 0, 150);

    while(1)
    {
        int nlen = 0;
        while(1)
        {
            nlen = fgetc(f);
            if(nlen == 0)
            continue;
            if(nlen == 0x4B)
            {
                fclose(f);
                fclose(f2);
                return 0;
            }
            break;
        }

        fgetc(f);

        fread(Buffer, nlen, 1, f);

        if(nlen < 8)
            SimpleDecrypt(Buffer, nlen);
        else
            Decrypt(Buffer, nlen, XorValue);

        fputc('\0', f2);
        fputc('\0', f2);
        fwrite(Buffer, nlen, 1, f2);
        memset(Buffer, 0, 150);
    }

    fclose(f);
    fclose(f2);
    printf("All functions decrypted... enjoy :)\n");
    return 0;
}


Ok, questo è il programma, lo so che il codice fa schifo, ma l'ho copiato direttamente dal safedisc, non avevo voglia di stare a riscriverlo in C, comunque dovrebbe essere chiaro quello che fa, l'unica cosa che dovete fare è far partire il programma, quando ha finito cambiate gli indirizzi delle due fseek, con quelli di User32 e mettete il carattere terminatore nell'if (if nlen == 0x4B), e fate ripartire, fate così per tutte le api, alla fine otterrete il file crack2.exe con i nomi decrittati (attenzione, il file crack2.exe deve gia esistere all'interno della cartella), ok adesso dobbiamo azzerare un po' di byte del file, che erano quelli dopo il carattere terminatore, quindi aprite il file crack2.exe con hiew, e azzerate tutti i byte dopo l'ultimo nome di ciascuna funzione della dll importata (azzerate fino al nome della dll), per Kernel32_1 iniziate ad azzerare da E5E66, per Kernel32_2 da E66BE, per User32 da E615F, per Gdi32 non dovete azzerare niente, per Advapi32 da E620C e infine per Ole32 da E624F.
Ok, adesso finalmente abbiamo un file con tutti i nomi decrittati, manca solo una cosa, ovvero ricostruire l'array di OriginalFirstThunk, però noi non ricostruiremo quello, ma invece ricostruiremo l'array di FirstThunk (il pe loader risolve sia dall'OriginalFirstThunk che dal FirstThunk) per far ciò faremo una cosa un po' particolare, ovvero, attraverso un programma che inietteremo direttamente nel processo, ci loggeremo il caller address, la funzione chiamata, e in che indirizzo della iat c'è quella funzione, poi con un programma in C, ci creeremo due tabelle, una per User32 e una per Kernel32, che conterranno informazioni sulle api chiamate e da dove sono chiamate, poi ricostruiremo la iat in modo che ogni api chiamata punti ad un solo indirizzo nella iat, e non a due (può capitare perché il safedisc può risolvere più di un'api con lo stesso indirizzo), fatto ciò, useremo il PEditor per ricostruire l'array di FirstThunk, che conterrà dei puntatori al nome della funzione, in modo che il pe loader di windows ci risolva correttamente le funzioni, infine aggiungeremo a mano le funzioni che non sono state loggate (saranno pochissime non preoccupatevi). Vediamo cosà dovrà fare il nostro logger:

1) Scannare la sezione .text alla ricerca degli opcode di call dword ptr, jmp dword ptr, call edi, call esi, call ebx e call ebp
2) Se trova l'opcode di mov esi, dword ptr o mov edi, dword ptr o mov ebx, dword ptr o ebp, dword ptr, scannare il codice in avanti alla ricerca dei relativi call (call esi, call edi, call ebx, call ebp)

3) Prendere il return address (che si ottiene sommando 6 nel caso di call dword e jmp dword, e sommando 2 nel caso di call edi, call esi, ecc..), e pusharlo nello stack (in modo che il safedisc pensi che la funzione è veramente chiamata da li)

4) Controllare che l'api non sia stata gia risolta (quindi dentro c'è ad esempio BFF8xxxx per qualche funzione di Kernel32)

5) Se è stata risolta allora aggiungerla alla tabella e proseguire

6) Se non è stata risolta, allora saltare direttamente al codice di risoluzione della funzione (preso dal call dword o jmp dword o call esi, ecc...), tramite in jmp dword ptr [ebx], in modo che lo stack rimanga inalterato

7) Sostituire il ret finale della funzione di risoluzione con un jmp alla funzione di logging che abbiamo fatto noi

8) La funzione di logging riempie la tabella (mettendo caller address, api chiamata, e indirizzo della iat), incrementa il puntatore alla tabella di 16 byte, e procede alla prossima istruzione

9) ripetere il ciclo finche non si è scannata tutta la sezione .text :)

(il logger scannerà solo per le chiamate a Kernel32 e User32, dato che per le altre ci ricostruiremo l'iat a mano (è una cavolata non preoccupatevi))
Ok, vediamo il codice:

; logger.asm

.486P
.Model Flat, stdcall

.code
start:
    mov esi, 401000h ; mettiamo in esi il VA dell'inizio della sezione .text
    mov edi, 4E2000h-401000h ; in edi la grandezza della sezione .text (che sarà inizio .rdata - inizio .text)
    mov ecx, 20001000h ; indirizzo del punto dove creeremo la nostra bella tabella che poi dumperemo

search_loop: ; questo loop scanna tutti gli opcode alla ricerca di quell che ci interessano
    cmp word ptr [esi], 015ffh ; è un call dword ptr ?
    jne try_jmp
    cmp dword ptr [esi+2],04E2038h ; l'indirizzo della iat è compreso tra gli indirizzi di kernel32 e user32?
    jl try_jmp
    cmp dword ptr [esi+2],04E2224h
    jg try_jmp ; se no salta al prossimo controllo
    lea eax, [esi+6] ; mettiamo in eax il return address (sommiamo 6 ad esi perché il call dword ptr occupa 6 byte)
    pushad
    push eax ; pushiamo il return address, così la funzione di risoluzione penserà che stiamo chiamando al funzione dal punto vero
    mov ebx,[esi+2] ; mette in ebx l'indirizzo della iat
    mov edx,[ebx] ; mettiamo in edx la funzione chiamata
    and edx,0f0000000h ; controlliamo che non sia una di quelle gia risolve (perché nel caso che non è risolta, sarà 01ECxxxx, se è gia risolta sarà BFF8xxxx per kernel32 ad esempio)
    test edx,edx
    jnz imm ; se è stata risolta, allora aggiungila direttamente alla tabella
    jmp dword ptr [ebx] ; altrimenti salta alla funzione di risoluzione

OkApi: ; questa è la funzione che loggerà la chiamata (a cui salteremo col jmp nella funzione di risoluzione
    mov [ecx],esi ; mette nel primo elemento della tabella l'indirizzo da dove è stata chiamata la funzione
    mov eax,[esp] ; mette in eax l'entry point dell'api da chiamare
    mov [ecx+4],eax ; e mette l'indirizzo nel secondo elemento
    mov eax,ebx ; in eax abbiamo l'indirizzo nella iat
    mov [ecx+8],eax ; e lo mettiamo nel terzo elemento
    mov dword ptr [ecx+12],0h ; azzeriamo la quarta dword
    inc esi ; incrementa il puntatore alle istruzioni
    dec edi ; decrementa la lunghezza del codice
    add ecx,16 ; ci spostiamo di 16 byte nella tabella, così siamo alla prossima entry, perché ogni entry occupa 16 byte (4 dword)
    test edi,edi ; sono finite le istruzioni?
    jnz search_loop ; se ci sono altre istruzioni continua, altrimenti lancia un int 1 che verrà catturato dal softice
    int 1

imm: ; questa funzione invece non fa altro che aggiungere alla tabella un'api gia risolta
; esi = caller address
; ebx = iat address
; edx = api entry point


    mov [ecx], esi ; al solito mettiamo nel primo elemento il caller address
    mov eax, [ebx]
    mov [ecx+4], eax ; mettiamo nel secondo l'api chiamata
    mov [ecx+8], ebx ; nel terzo l'indirizzo nella iat
    mov dword ptr [ecx+12], 0h ; e azzeriamo l'ultimo valore
    inc esi
    dec edi
    add ecx, 16
    test edi, edi
    jnz search_loop
    int 1

try_jmp: ; cerca i jmp
    cmp word ptr [esi], 025FFh ; controlla che l'opcode corrisponda a quell del jmp, se si procede come nel caso del call, quindi non commento, se no passa al prossimo controllo
    jne try_esi
    cmp dword ptr [esi+2],04E2038h
    jl try_esi
    cmp dword ptr [esi+2],04E2224h
    jg try_esi
    mov eax, dword ptr [esi+2]
    cmp dword ptr [eax], 00789C50h
    je try_again
    lea eax, [esi+6]
    pushad
    push eax
    mov ebx,[esi+2]
    mov edx,[ebx]
    and edx,0f0000000h
    test edx,edx
    jnz imm
    jmp dword ptr [ebx]

try_esi: ; cerca le call esi
    cmp word ptr [esi], 358Bh ; prima di cercare una call esi però, dobbiamo trovare il mov esi, dword ptr [xxx], 358B è il suo opcode
    jne try_edi
    cmp dword ptr [esi+2], 04E2038h
    jl try_edi
    cmp dword ptr [esi+2], 04E2224h
    jg try_edi
    mov ebx, [esi+2] ; ora che abbiamo trovato la mov esi, mettiamo in ebx l'indirizzo nella iat
search_for_call_esi: ; questa funzione cerca la call esi a partire dal punto del mov esi, dword ptr
    inc esi
    cmp word ptr [esi], 0D6FFh ; cerca per l'opcode di call esi, se non lo trova continua a cercare
    jnz search_for_call_esi
    lea eax, [esi+2] ; abbiamo l'opcode, quindi come al solito mettiamo in eax il return address
    pushad
    push eax
    mov edx, [ebx]
    and edx, 0F0000000h ; facciamo il solito test per vedere se non è gia risolta
    test edx, edx
    jnz imm
    jmp dword ptr [ebx] ; se non è gia risolta allora risolviamola :)

try_edi: ; cerca per il mov edi, dword ptr, e successivamente il call edi, non commento perché tanto accade sempre la stessa cosa
    cmp word ptr [esi], 3D8Bh
    jne try_ebx
    cmp dword ptr [esi+2], 04E2038h
    jl try_ebx
    cmp dword ptr [esi+2], 04E2224h
    jg try_ebx
    mov ebx, [esi+2]
search_for_call_edi:
    inc esi
    cmp word ptr [esi], 0D7FFh
    jnz search_for_call_edi
    lea eax, [esi+2]
    pushad
    push eax
    mov edx, [ebx]
    and edx, 0F0000000h
    test edx, edx
    jnz imm
    jmp dword ptr [ebx]

try_ebx: ; cerca per il mov ebx, dword ptr/call ebx
    cmp word ptr [esi], 1D8Bh
    jne try_ebp
    cmp dword ptr [esi+2], 04E2038h
    jl try_ebp
    cmp dword ptr [esi+2], 04E2224h
    jg try_ebp
    mov ebx, [esi+2]
search_for_call_ebx:
    inc esi
    cmp word ptr [esi], 0D3FFh
    jnz search_for_call_ebx
    lea eax, [esi+2]
    pushad
    push eax
    mov edx, [ebx]
    and edx, 0F0000000h
    test edx, edx
    jnz imm
    jmp dword ptr [ebx]

try_ebp: ; cerca per il mov ebp, dword ptr/call ebp
    cmp word ptr [esi], 2D8Bh
    jne try_again
    cmp dword ptr [esi+2], 04E2038h
    jl try_again
    cmp dword ptr [esi+2], 04E2224h
    jg try_again
    mov ebx, [esi+2]
search_for_call_ebp:
    inc esi
    cmp word ptr [esi], 0D5FFh
    jne search_for_call_ebp
    lea eax, [esi+2]
    pushad
    push eax
    mov edx, [ebx]
    and edx, 0F0000000h
    test edx, edx
    jnz imm
    jmp dword ptr [ebx]

try_again: ; arriveremo qui se l'opcode non è uno dei casi precedenti, quindi passiamo al prossimo byte di codice
    inc esi
    dec edi
    jne search_loop
    int 1 ; qui arriveremo al termine del fixing e ci brekkerà il softice

end start
 
Ok, adesso invece vediamo come funziona (il codice mi sembra commentato abbastanza bene :)). Allora prima di tutto dobbiamo compilarlo, quindi col masm, fate ml /c /coff /Cp logger.asm, e poi link /SUBSYSTEM:WINDOWS logger.obj, a questo punto abbiamo un file exe con cui non possiamo farci assolutamente niente :), quindi apriamolo con ultraedit, e tagliamo l'header (praticamente tagliate finche non inizia il primo byte di codice, e successivamente tagliate dall'ultimo in poi), quindi, tagliate da 0 a 1ff, e da 3B0 a 400, adesso abbiamo qualcosa gia più utile :), segnatevi la dimensione di questo file, che se avete fatto le cose per bene dovrebbe essere di 1B0 byte. Ok, ora accertatevi che icedump sia caricato (dovrebbe esserlo considerando il fatto che avete fatto partire SR2), fate partire SR2, e brekkate sull'entry point, a questo punto iniettermo il codice direttamente dentro il processo, ora, invece di stare a cercare dello spazio vuoto, io ho scelto una soluzione meno pulita ma più rapida, ovvero mi sono allocato 4 pagine e ho lavorato con quelle, ed è quello che farete pure voi :), quindi sempre nel softice, allocate 4 pagine con /alloc 20000000 4000 (ho usato 20000000 come base address perché sto tranquillo che qui non c'è niente, almeno nel mio caso :)), adesso tocca a inserire le pagine in memoria, quindi fate pagein 20000000, pagein 20001000, pagein 20002000 e pagein 20003000. Ok adesso abbiamo le nostre belle 4 pagine, ora nella prima pagina caricheremo il logger, la seconda invece la useremo per salvare la nostra bella tabella (e fa pure rima! :)), dunque, per caricare il logger, fate /load 20000000 1B0 f:\games\sr2\logger.exe (ovviamente sostuituite la directory del gioco con quella che avete voi), a questo punto il nostro bel logger è in memoria, adesso dobbiamo modificare il ret della routine di risoluzione in modo che una volta risolta l'api salti al codice del logger invece che all'api da chiamare, ora il pezzo di codice a cui bisogna saltare è quello che nel file asm era nella label OkApi, l'indirizzo dovrebbe essere 2000003E, comunque voi per sicurezza controllate, fatto questo, fate A 1004426A (l'indirizzo è quello del ret, se non vi corrisponde, allora cercatelo (basta seguire qualsiasi call dword ptr [xxx] e poi vedere qual'è l'indirizzo del ret)) e mettete l'istruzione jmp 2000003E, in questo modo invece di chiamare l'api salterà al codice del logger. Bene, adesso dobbiamo far eseguire il logger :), quindi facciamo un brutale r eip 20000000, fate u eip, e vi ritroverete nel codice del logger, ora basta fare i1here on e poi F5, in questo modo il softice brekkerà ad ogni int 1, e il primo int 1 viene eseguito quando il logger ha finito il suo lavoro, appena brekkerà il softice, significa che abbiamo terminato lo scan e la tabella è stata creata, per sapere quanto è grande, basta vedere il valore di ecx, che sarà 20002B50, quindi per trovare la grandezza basta fare 20002B50 - 20001000, ovvero 1B50 byte, ora quello che ci resta da fare è dumparla, quindi facciamo un bel /dump 20001000 1B50 f:\games\sr2\iat.dmp, a questo punto avremo la nostra tabella su disco, che dovrebbe assomigliare a qualcosa del genere:

00000000 3A F5 44 00  B4 48 F7 BF  EC 20 4E 00  00 00 00 00 :.D..H... N.....
00000010 55 F5 44 00  3D 6E F7 BF  E0 20 4E 00  00 00 00 00 U.D.=n... N.....
00000020 80 F5 44 00  DB 7A F7 BF  40 20 4E 00  00 00 00 00 ..D..z..@ N.....
00000030 A5 F5 44 00  DB 7A F7 BF  D4 20 4E 00  00 00 00 00 ..D..z... N.....
00000040 F8 F5 44 00  F0 FF F7 BF  CC 20 4E 00  00 00 00 00 ..D...... N.....
00000050 2B F6 44 00  39 70 F7 BF  D0 20 4E 00  00 00 00 00 +.D.9p... N.....
00000060 3E F6 44 00  83 0B FA BF  D4 20 4E 00  00 00 00 00 >.D...... N.....
00000070 75 F6 44 00  CB 41 F8 BF  70 21 4E 00  00 00 00 00 u.D..A..p!N.....


Come vedete, il primo elemento della tabella è il caller address, il secondo è l'api chiamata, mentre il terzo è il punto nella iat in cui si trova quell'api, ma a noi di questo terzo argomento non ce ne frega niente :), ok adesso abbiamo risolto la maggior parte delle api, ma per adesso mettiamo da parte questo file, ci servirà in seguito, quello che faremo adesso (come anticipato qualche paragrafo fa) sarà ricostruire le entry per Gdi32, Advapi32 e Ole32.
Allora, prima di tutto occupiamoci di Gdi32 (terza entry nella IT), quello che faremo sarà mettere nella sua IAT (che si trova all'indirizzo E202C), i puntatori all'HINT-NAME della funzione importata (perché come sapete, il nome della funzione è preceduto da 2 byte che sono l'hint della funzione, se presente, il pe loader risolve con quello altrimenti col nome, e il puntatore deve puntare all'hint), l'unico problema è che questi puntatori non possiamo scriverli nell'ordine in cui stanno nella zona che contiene i nomi, ma dobbiamo scriverli nell'ordine originale, per saperlo, ci basta vedere che funzioni chiama il file originale per le due entry della IAT di Gdi32, ovvero 01ECA337 per il primo e 01ECA682 per il secondo, questi sono gli indirizzi delle funzioni di ponte di safedisc, quindi quello che faremo sarà mettere un break su quegli indirizzi, e vedere a che api portano, e scopriremo che la prima api è GetStockObject, la seconda è GetDeviceCaps, quindi nella IAT dovremo scrivere il puntatore a GetStockObject e GetDeviceCaps, ma come facciamo a sapere a quale rva si trovano questi nomi? Semplice, con UltraEdit basta che facciamo Search->Find e attiviamo l'opzione find ascii, poi scriviamo il nome della funzione e vediamo in che punto sta, e troveremo che GetStockObject sta all'offset E618E (che corrisponde anche all'RVA, perché in questo caso abbiamo section offset = section rva), e GetDeviceCaps all'offset E617E, quindi nella IAT di Gdi32 scriviamo questi due valori, quindi alla fine avremo una cosa del genere:

000E2000 D3 A9 EC 01  44 16 E8 BF  69 B0 EC 01  B4 B3 EC 01 ....D...i.......
000E2010 FF B6 EC 01  4A BA EC 01  00 00 00 00  72 A4 B7 BF ....J.......r...
000E2020 00 00 00 00  60 B0 00 70  00 00 00 00  8E 61 0E 00 ....`..p....7...
000E2030 7E 61 0E 00  00 00 00 00  77 03 EB 01  C2 06 EB 01 ........w.......

Come vedete nelle altre entry abbiamo ancora gli indirizzi del safedisc, ok, ora dobbiamo fare la stessa cosa per Advapi32 e Ole32, state attenti che con Advapi32 avete gia una funzione risolta, ovvero RegCloseKey (BFE81644), quindi non dovrete neanche perdere tempo a cercare questa funzione, mentre per le altre dovete fare la stessa cosa che avete fatto per Gdi32, probabilmente non riuscirete a risolvere un'api per Advapi32, che è RegOpenKeyExA, non preoccupatevi, tanto è l'unica api che non è stata risolta quindi basta andare per esclusione :), comunque siccome non voglio farvi perdere tempo a cercarli, ecco gli altri indirizzi (le funzioni stanno nell'ordine in cui vanno messe nella IAT):

Advapi32: IAT: 000E2000

RegCreateKeyExA: 000E61EC
RegCloseKey: 000E61AA
RegQueryValueEx: 000E61B8
RegOpenKeyA: 000E61FE
RegSetValueExA: 000E610B
RegOpenKeyExA: 000E61CB

Ole32: IAT: 000E2284

CoCreateInstance: 000E623C
CoUnInitialize: 000E622B
CoInitialize: 000E621C


Ok, a questo punto abbiamo sistemato quasi tutto, comunque per evitare equivoci, ecco come apparirà la IAT dopo il fixing:

IAT per Advapi32 e Gdi32:

000E2000 EC 61 0E 00  AA 61 0E 00  B8 61 0E 00  FE 61 0E 00 .a...a...a...a..
000E2010 DB 61 0E 00  CB 61 0E 00  00 00 00 00  38 59 0E 00 .a...a......8Y..
000E2020 00 00 00 00  86 62 0E 00  00 00 00 00  8E 61 0E 00 .....b.......a..
000E2030 7E 61 0E 00  00 00 00 00                         ~a......


Se qualche valore (che non è della iat di advapi32 o gdi32) non vi corrisponde, è normale, perché questo era l'unico eseguibile, che avevo con la iat fixata, e avevo sistemato anche le altre, quindi ci saranno alcuni valori diversi, ma non è un problema.
Ok, adesso se disassemblate con W32Dasm (fatelo :)), vedrete che tra le funzioni importate ci saranno anche quelle di Advapi32, Gdi32 e Ole32 (mentre per kernel32 e user32 ci sarà qualcosa del genere. kernel32.kernel32, user32.user32).
Adesso, dobbiamo fixare Kernel32 e User32, per far ciò useremo un programmino che ho fatto in C, che scannerà la tabella che ci siamo dumpati, e salverà in un'array di strutture il caller address e l'api chiamata, a questo punto si posizionerà all'offset del caller address, e sostituirà all'argomento del call dword (o jmp o mov esi, dword ptr, ecc...) il primo elemento della iat, in cui metterà l'indirizzo della funzione chiamata, e così via, e in più, ogni volta che trova 2 entry nella iat che chiamano la stessa funzione, sostituirà l'argomento con quello del caller address che punta alla stessa funzione, così non avremo doppioni, uhm... mi sa che mi sono spiegato male, comunque riassumiamo brevemente i passi fondamentali che dovrà compiere il fixer:

1) Scannare il file iat.dmp, e per ogni entry (composta da 16byte), riempire una struttura fatta in questo modo:

struct fixs
{
    DWORD caller;
    DWORD api_called;
    DWORD iat_addr;
    DWORD pad;
};

dove caller è l'indirizzo da dove viene chiamata l'api, api_called contiene l'entry point dell'api chiamata, iat_addr contiene l'indirizzo della iat originale, e padding è usato per leggere gli ultimi 4 byte 00, fatto ciò, riempire una struttura di questo tipo:

struct iatinfo
{
    DWORD api_called;
    DWORD iat_addr;
    DWORD caller;
};

e aggiungerla ad un'array di strutture di quel tipo, i campi api_called e caller verranno copiati dalla struttura fixs, mentre iat_addr partirà dall'inizio della iat (per kernel32 E2038), e ad ogni nuova api chiamata, verrà incrementato di 4, se invece l'api è gia presente nell'array, allora non verrà incrementato, ma si userà quello gia presente, in questo modo avremo un iat address univoco.

2) Finito lo scanning, tramite un MMF (memory mapped file) aprire il file exe, leggere il primo elemento, posizionarsi all'indirizzo della iat indicato, e mettere l'indirizzo dell'entry point della funzione chiamata

3) Spostarsi all'indirizzo indicato come caller address, e vedere l'opcode, se è un call dword o un jmp dword, allora sostituire direttamente l'argomento con quello nuovo

4) se è un call edi, call esi, call ebx o call ebp, scannare all'indietro il file alla ricerca del mov esi, dword ptr [xxx], mov esi, dword ptr [xxx] ecc..., una volta trovato cambiare l'argomento del mov.

5) ripetere il ciclo sia per kernel32 che per user32

Ok come al solito per prima cosa vediamo il codice (avverto, il codice fa letteralmente schifo, ma che volete, è stato codato in 10 minuti e l'importante è che funziona :)) :

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <vector> // per l'array dinamico useremo la classe vector della stl

using namespace std;
 
//queste due strutture definiscono rispettivamente un'etry del file iat.dmp,
//e le informazioni sull'api chiamata con relativo caller address e iat address
struct fixs
{
    DWORD caller;
    DWORD api_called;
    DWORD iat_addr;
    DWORD pad;
};

struct iatinfo
{
    DWORD api_called;
    DWORD iat_addr;
    DWORD caller;
};

bool fix(char *FileName)
{
    HANDLE hFile = NULL;
    HANDLE hMappedFile = NULL;
    unsigned char *lpFileBase = NULL;
    unsigned char *pTmpPtr = NULL;
    unsigned char *pTmpPtr2 = NULL;
    WORD istr = 0;
    DWORD arg = 0;
    DWORD iat_addr = 0;

    int k32napi = 0;
    int u32napi = 0;

    FILE *f = NULL;
    fixs fix;

    vector<iatinfo> k32apis; // array che contiene le funzioni per kernel32
    vector<iatinfo> u32apis; // array che contiene le funzioni per user32

    hFile = CreateFile(FileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,
                       NULL);
    if(hFile == INVALID_HANDLE_VALUE)
        return false;

    hMappedFile = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL); // come vedete ho deciso di usare un mmf

    if(hMappedFile == INVALID_HANDLE_VALUE)
    {
        CloseHandle(hFile);
        return false;
    }

    lpFileBase = (unsigned char *)MapViewOfFile(hMappedFile, FILE_MAP_WRITE, 0, 0, 0);

    if(!lpFileBase)
    {
        CloseHandle(hMappedFile);
        CloseHandle(hFile);
        return false;
    }

    f = fopen("iat.dmp", "r+b"); // apriamo il file iat.dmp che ci scanneremo in seguito

    while(!feof(f))
    {
        bool isEqual = false;
        static k32iat = 0xE2038; //inizio della iat di kernel32
        static u32iat = 0xE218C; // inizio della iat di user32
        iatinfo info;

        fread(&fix, sizeof(fixs), 1, f);

        if((fix.api_called >= 0xBFF51000) && (fix.api_called <= 0xBFF5D000)) //controlla che la funzione sia di user32 (se avete user32 mappato in un indirizzo diverso, SOSTITUITE questi valori! altrimenti non funzionerà niente, per sapere dov'è mappato user32 da softice fate map32 user32)
        {
            for(int i = 0; i < u32napi; i++) // questo ciclo for controlla se l'api è gia stata aggiunta all'array
            {
                if(fix.api_called == u32apis[i].api_called)
                {
                    info.api_called = fix.api_called;
                    info.iat_addr = u32apis[i].iat_addr; // se si allora usiamo il suo indirizzo nella iat
                    info.caller = fix.caller;
                    u32apis.push_back(info); //aggiungiamo la struttura all'array
                    isEqual = true;
                    u32napi++;
                    break;
                }
            }

            if(!isEqual)
            {
                info.api_called = fix.api_called;
                info.iat_addr = u32iat; //se invece non è stata gia aggiunta, allora mettiamo come iat address quello corrente
                info.caller = fix.caller;
                u32iat += 4; //incrementa di 4 l'iat address corrente
                u32apis.push_back(info); //aggiunge alla struttura
                u32napi++;
            }
        }
        else //stesso discorso di prima solo che per kernel32
        {
            for(int i = 0; i < k32napi; i++)
            {
                if(fix.api_called == k32apis[i].api_called)
                {
                    info.api_called = fix.api_called;
                    info.iat_addr = k32apis[i].iat_addr;
                    info.caller = fix.caller;
                    k32apis.push_back(info);
                    isEqual = true;
                    k32napi++;
                    break;
                }
            }

            if(!isEqual)
            {
                info.api_called = fix.api_called;
                info.iat_addr = k32iat;
                info.caller = fix.caller;
                k32iat += 4;
                k32apis.push_back(info);
                k32napi++;
            }
        }
    }
    fclose(f);
 
//questo ciclo for scanna la tabella appena creata e fixa tutte le chiamate alle api che sono state loggate
    for(int i = 0; i < k32napi; i++)
    {
        WORD istr;
        DWORD operand;

        pTmpPtr = lpFileBase;
        pTmpPtr2 = lpFileBase;

        pTmpPtr += (k32apis[i].caller - 0x00400000); //pTmpPtr punterà all'offset del caller address

        pTmpPtr2 += k32apis[i].iat_addr; //pTmpPtr2 punta all'indirizzo nella iat per la struttura corrente
        DWORD addr = k32apis[i].api_called; //addr è l'entry point dell'api chiamata
 
//mette l'entry point dell'api chiamata nella iat
        __asm {
            mov esi, dword ptr [pTmpPtr2]
            mov eax, addr
            mov dword ptr [esi], eax
        }
 
//prende l'istruzione
        __asm {
            mov esi, dword ptr [pTmpPtr]
            mov ax, word ptr [esi]
            mov istr, ax
        }
//e controlla che istruzione è
        switch(istr)
        {
//nel caso di call dword ptr e jmp dword ptr cambiamo solo l'argomento del call o jmp
            case 0x15FF:
            case 0x25FF:
            {
                pTmpPtr += 2;

                operand = k32apis[i].iat_addr + 0x00400000;

                __asm {
                    mov esi, dword ptr [pTmpPtr]
                    mov eax, operand
                    mov dword ptr [esi], eax
                }
            }
            break;
//nel caso delle altre istruzioni (call esi, edi, ec...) scanniamo il file all'indietro alla ricerca del relativo mov, una volta trovato cambiamo l'argomento
            case 0xD6FF:
            {
                while(istr != 0x358B)
                {
                    pTmpPtr--;

                    __asm {
                        mov esi, dword ptr [pTmpPtr]
                        mov ax, word ptr [esi]
                        mov istr, ax
                    }
                }

                pTmpPtr += 2;

                operand = k32apis[i].iat_addr + 0x00400000;

                __asm {
                    mov esi, dword ptr [pTmpPtr]
                    mov eax, operand
                    mov dword ptr [esi], eax
                }
            }
            break;

            case 0xD7FF:
            {
                while(istr != 0x3D8B)
                {
                    pTmpPtr--;

                    __asm {
                        mov esi, dword ptr [pTmpPtr]
                        mov ax, word ptr [esi]
                        mov istr, ax
                    }
                }

                pTmpPtr += 2;

                operand = k32apis[i].iat_addr + 0x00400000;

                __asm {
                    mov esi, dword ptr [pTmpPtr]
                    mov eax, operand
                    mov dword ptr [esi], eax
                }
            }
            break;

            case 0xD3FF:
            {
                while(istr != 0x1D8B)
                {
                    pTmpPtr--;

                    __asm {
                        mov esi, dword ptr [pTmpPtr]
                        mov ax, word ptr [esi]
                        mov istr, ax
                    }
                }

                pTmpPtr += 2;

                operand = k32apis[i].iat_addr + 0x00400000;

                __asm {
                    mov esi, dword ptr [pTmpPtr]
                    mov eax, operand
                    mov dword ptr [esi], eax
                }
            }
            break;

            case 0xD5FF:
            {
                while(istr != 0x2D8B)
                {
                    pTmpPtr--;

                    __asm {
                        mov esi, dword ptr [pTmpPtr]
                        mov ax, word ptr [esi]
                        mov istr, ax
                    }
                }

                pTmpPtr += 2;

                operand = k32apis[i].iat_addr + 0x00400000;

                __asm {
                    mov esi, dword ptr [pTmpPtr]
                    mov eax, operand
                    mov dword ptr [esi], eax
                }
            }
            break;
        }
    }
//ripetiamo il tutto per user32
    for(i = 0; i < u32napi; i++)
    {
        WORD istr;
        DWORD operand;

        pTmpPtr = lpFileBase;
        pTmpPtr2 = lpFileBase;

        pTmpPtr += (u32apis[i].caller - 0x00400000);

        pTmpPtr2 += u32apis[i].iat_addr;
        DWORD addr = u32apis[i].api_called;

        __asm {
            mov esi, dword ptr [pTmpPtr2]
            mov eax, addr
            mov dword ptr [esi], eax
        }

        __asm {
            mov esi, dword ptr [pTmpPtr]
            mov ax, word ptr [esi]
            mov istr, ax
        }

        switch(istr)
        {
            case 0x15FF:
            case 0x25FF:
            {
                pTmpPtr += 2;

                operand = u32apis[i].iat_addr + 0x00400000;

                __asm {
                    mov esi, dword ptr [pTmpPtr]
                    mov eax, operand
                    mov dword ptr [esi], eax
                }
            }
            break;

            case 0xD6FF:
            {
                while(istr != 0x358B)
                {
                    pTmpPtr--;

                    __asm {
                        mov esi, dword ptr [pTmpPtr]
                        mov ax, word ptr [esi]
                        mov istr, ax
                    }
                }

                pTmpPtr += 2;

                operand = u32apis[i].iat_addr + 0x00400000;

                __asm {
                    mov esi, dword ptr [pTmpPtr]
                    mov eax, operand
                    mov dword ptr [esi], eax
                }
            }
            break;

            case 0xD7FF:
            {
                while(istr != 0x3D8B)
                {
                    pTmpPtr--;

                    __asm {
                        mov esi, dword ptr [pTmpPtr]
                        mov ax, word ptr [esi]
                        mov istr, ax
                    }
                }

                pTmpPtr += 2;

                operand = u32apis[i].iat_addr + 0x00400000;

                __asm {
                    mov esi, dword ptr [pTmpPtr]
                    mov eax, operand
                    mov dword ptr [esi], eax
                }
            }
            break;

            case 0xD3FF:
            {
                while(istr != 0x1D8B)
                {
                    pTmpPtr--;

                    __asm {
                        mov esi, dword ptr [pTmpPtr]
                        mov ax, word ptr [esi]
                        mov istr, ax
                    }
                }

                pTmpPtr += 2;

                operand = u32apis[i].iat_addr + 0x00400000;

                __asm {
                    mov esi, dword ptr [pTmpPtr]
                    mov eax, operand
                    mov dword ptr [esi], eax
                }
            }
            break;

            case 0xD5FF:
            {
                while(istr != 0x2D8B)
                {
                    pTmpPtr--;

                    __asm {
                        mov esi, dword ptr [pTmpPtr]
                        mov ax, word ptr [esi]
                        mov istr, ax
                    }
                }

                pTmpPtr += 2;

                operand = u32apis[i].iat_addr + 0x00400000;

                __asm {
                    mov esi, dword ptr [pTmpPtr]
                    mov eax, operand
                    mov dword ptr [esi], eax
                }
            }
            break;
        }
    }

    UnmapViewOfFile((LPVOID)lpFileBase);
    CloseHandle(hMappedFile);
    CloseHandle(hFile);

    return true;
}

int main()
{
    fix("crack.exe");

    printf("File fixed... enjoy! :)\n");
    return 0;
}
Quindi il fixer richiede la presenza del file crack.exe e iat.dmp nella sua directory, assicuratevi che ci siano quei file, e lanciatelo (ovviamente il file crack.exe sarà l'ultima versione dell'eseguibile su cui state lavorando), a questo punto abbiamo quasi finito (insomma :)). Date un'occhiata al file, e vedrete che nella iat di kernel32 e user32 ci saranno un sacco di indirizzi di entry point delle api, solo che noterete che le ultime 3 entry di kernel32 (a partire da E217C) e le ultime 4 di user32 (a partire da E2214) non sono state fixate, questo perché alcune funzioni non venivano chiamate in nessuno dei modi descritti ma tramite il jmp 00762xxx (che il logger non esamina), e quindi non siamo stati in grado di fixarle, ma lo faremo a mano senza problemi. Però adesso possiamo fixare il file col PEditor, perché un bel po' di api corrette le abbiamo, quindi azzerate le entry che non sono state fixate, aprite il PEditor, fate browse, e selezionate il file crack.exe, selezionate il rebuilder, e fate Rebuild Import Table (assicuratevi che l'opzione selezionata sia Rebuild Import Table e NON Rebuild New Import Table), fate ok, e aspettate, ci vorrà un po' di tempo, ma alla fine avrete un file quasi a posto. Quando il PEditor ha terminato il suo lavoro, chiudete il file, apritelo con Hiew ad esempio, e se avete la versione 6.70, fate F8 e poi F7 (nella modalità hex), e vedrete la lista delle imports, selezionate Kernel32 e vedrete che ci sono quasi tutte le api, stesso discorso per User32, a questo punto non ci resta che sistemare le altre api, far partire il file e risolvere gli eventuali crash (ce ne saranno pochissimi non preoccupatevi).
Allora, prima di tutto risolviamo i vari jmp 00762xxx, cosa abbastanza semplice da fare. E' giunto il momento di usare il W32Dasm, quindi apritelo e fategli disassemblare il file crack.exe (sempre l'ultima versione ovviamente), quando avrà finito, usate la sua funzione di search per cercare il testo "jmp 00762", e segnatevi l'indirizzo dei punti in cui trova quel jmp, alla fine troverete questi indirizzi:

004DB7FA, 004DC494, 004743B0, 0048790E, 004D8182, 0044FC5D, 0046C602, 00475632, 0048A6F3,
004DDC83, 00473900, 004736F0, 00474520, 00450207, 0047844A, 00450981, 004DA141, 004DF0CF,
0045A5BD, 0047633B, 00473B30, 0046FFB8, 00474860, 0046FE8F, 0046BB84, 0046F4A0, 0045099E,
00472A90, 004757F0, 00473743, 004722D2, 004746D0, 0046FE9C

Bene, ma che ci facciamo con questi indirizzi? Semplice, ci risolveremo queste api, poi cambieremo il jmp 00762 con call dword ptr o call xxx, ok, adesso vediamo come risolvere le api, è abbastanza semplice. Per prima cosa brekkate sull'entry point, a questo punto fate bpx 1004426A (break sul ret della funzione di risoluzione), e fate r eip 004DB7FA, fate u eip e vedrete il jmp, se volete eseguitelo per vedere cosa succede a runtime, ma l'importante è che arrivate al ret (o con F5 (perché abbiamo settato il break) o se siete masochisti vi fate tutto il codice di risoluzione), e che lo eseguite, a questo punto vi ritroverete nell'api chiamata, segnatevela e continuate così per tutti gli altri indirizzi. Siccome sono buono (insomma :)), ecco la lista delle funzioni chiamate:

004DB7FA --> GetStartupInfoA
004DC494 --> MultiByteToWideChar
004743B0 --> GetCurrentThreadId
0048790E --> InitializeCriticalSection
004D8182 --> GetCommandLineA
0044FC5D --> SetEvent
0046C602 --> DeleteCriticalSection
00475632 --> GetCurrentThreadId
0048A6F3 --> GetTickCount
004DDC83 --> GetModuleFileNameA
00473900 --> GetCurrentThreadId
004736F0 --> GetCurrentThreadId
00474520 --> GetCurrentThreadId
00450207 --> CreateFileA
0047844A --> EndPaint
00450981 --> GetActiveWindow
004DA141 --> VirtualFree
004DF0CF --> GetStringTypeA
0045A5BD --> GetTickCount
0047633B --> GlobalAlloc
00473B30 --> GetCurrentThreadId
0046FFB8 --> QueryPerformanceCounter
00474860 --> GetCurrentThreadId
0046FE8F --> SetThreadPriority
0046BB84 --> FreeLibrary
0046F4A0 --> GetCurrentThreadId
0045099E --> DialogBoxParamA
00472A90 --> GetCurrentThreadId
004757F0 --> GetCurrentThreadId
00473743 --> GetCurrentThreadId
004722D2 --> GetCurrentThreadId
004746D0 --> GetCurrentThreadId
0046FE9C --> SetThreadPriority

Ok, quello che faremo adesso è cercarci gli indirizzi nella iat di quelle funzioni, nel caso che la funzione non sia presente nella iat sistemata da PEditor, allora la aggiungeremo a mano, quindi iniziate a cercarvi gli indirizzi per le call dword ptr, per farlo, sempre usando il W32Dasm, cercate un punto in cui venga chiamata quella funzione, ad esempio per GetStartupInfoA, andate nel dialogo delle import, e fate doppio click su KERNEL32.GetStartupInfoA, e segnatevi che indirizzo viene chiamato (che sarà 004E2108). Come noterete, le chiamate a GetCurrentThreadId e GetTickCount, sono del tipo:

call xxxx

xxxx:
jmp dword ptr [yyy]


Quindi voi dovete segnarvi l'indirizzo del jmp dword ptr, perché noi nel caso di GetCurrentThreadId e GetTickCount non metteremo un call dword ptr, ma con hiew assembleremo un call 0046FE40 per GetCurrentThreadId e un call 0045AAC0 per GetTickCount (ricordatevi che quando assemblate con hiew gli indirizzi sono dei RVA, quindi ad esempio dovete assemblare un call 6FE40 per 004EFE40). Dopo esservi segnati tutti gli indirizzi, noterete che non avete trovato niente per GetCommandLineA, SetEvent, EndPatin e DialogBoxParamA, nessun problema, aggiungeremo queste funzioni a mano, iniziamo con Kernel32, per trovare a che indirizzo si trova il nome, con UltraEdit fate un search in ascii, e cercate la stringa del nome, segnatevi l'indirizzo e aggiungetelo nella iat dopo l'ultima entry, fatelo sia per Kernel32 che per User32, comunque gli indirizzi da aggiungere (e relative posizioni risultanti nella iat) sono questi:

GetCommandLineA --> Indirizzo: 000E64D8, IAT: 000E217C
SetEvent --> Indirizzo: 000E5C86, IAT: 000E2180
EndPaint --> Indirizzo: 000E60DC, IAT: 000E2214
DialogBoxParamA --> Indirizzo: 000E5F4F, IAT: 000E2218


Ecco tutti gli indirizzi delle varie api:

GetStartupInfoA: 004E2108
MultiByteToWideChar: 004E2134
GetCurrentThreadId: 0046FE40
InitializeCriticalSection: 004E206C
GetCommandLineA: 004E217C
SetEvent: 004E218D
DeleteCriticalSection: 004E2090
GetTickCount: 0045AAC0
GetModuleFileNameA: 004E2110
CreateFileA: 004E2040
EndPaint: 004E2214
GetActiveWindow: 004E219C
VirtualFree: 004E2050
GetStringType: 004E2158
GlobalAlloc: 004E2038
QueryPerformanceCounter: 004E20D4
SetThreadPriority: 004E2080
FreeLibrary: 004E20E0
DialogBoxParamA: 004E2218


Ok, adesso che avete tutti gli indirizzi, fixate il file (andate all'indirizzo del jmp e mettete un call dword ptr (vi conviene mettere direttamente l'opcode, ad esempio per GetStartupInfoA metterete FF1508214E00) o un call xxx (solo per GetCurrentThreadId e GetTickCount)).
Bene, adesso che avete finito di fixare avete un file quasi funzionante, prepariamoci alla prima esecuzione :). Quindi fate partire, e... crash! :) Beh ce lo aspettavamo, quindi andiamo a vedere perché crasha, uhm... l'ip del crash risulta essere 00478DDD, è un ret, quindi significa che c'è qualche api fixata male, infatti andando un po' sopra (molto sopra) troveremo dei mov edi e mov esi e relativi call, quindi facciamo partire il file originale, e vediamo cosa succede nelle varie call edi, call esi e call ebx, uhm... call edi punta a RegOpenKeyExA, call esi a RegQueryValueExA e call ebx a RegCloseKey (vedete i mov), apriamo il crack e controlliamo, corrispondono tutte, tranne che la call edi, che punta a RegOpenKeyA, quindi sostuituiamo l'argomento del mov edi, con mov edi, dword ptr [004E2014] (per farlo usate il solito hiew). Facciamo ripartire e noteremo che non crasha più la, ma ad un'altro address :), stavolta è 00478882, stesso discorso di prima, controllate il file originale, e scoprirete che all'indirizzo 00478842 viene chiamata RegOpenKeyExA, mentre nel crack abbiamo RegCreateKeyExA, ok come prima sostituite con il il giusto indirizzo della iat salvate e fate ripartire... altro crash :) ok, non perdete la pazienza, vedete a che eip crasha, e sarà 00450883, al solito fate partire il programma originale e vedete che api viene chiamata, GetDriveTypeA, uhm... se non sbaglio manca ancora un'entry nella iat di kernel32, e casualmente :), GetDriveTypeA non è presente tra le funzioni importate di Kernel32, esatto! aggiungete anche quella (l'indirizzo del nome è 000E5D8D), e modificate l'argomento del call dword ptr, con call dword ptr [004E2184]. Ok, fate ripartire per l'ennesima volta e otterrete un'altro bel crash :)) (forza ne mancano solo 3), stavolta l'eip indicato è 0044FE7D, prendete la funzione esatta col file originale, e vedrete che è Sleep (IAT: 004E2088), sostituite, salvate e fate ripartire, e ora sono guai. Il crash avviente dentro il codice di user.exe e non abbiamo alcuna informazione su cosa succede, dato che l'ip è sballato, che facciamo? E qui sono cazzi :), allora l'unica cosa da fare è tracciare dalla chiamata a GetCommandLineA in poi, e seguire le varie call, insomma andando per tentativi e con molta fortuna (non preoccupatevi non è semplice ma manco difficile), scoprirete che i problemi si trovano agli indirizzi 00478625 e 0047862B, nel file originale vengono chiamate TranslateMessage e DispatchMessage, mentre nel crack GetClientRect e FillRect, uhm... c'è qualcosa che non va :), e scoprirete che queste funzioni non sono importate da User32 e che mancano solo 2 entry in User32, quindi aggiungete anche queste due funzioni (gli indirizzi sono 000E6112 e 000E60FF), e fixate i mov ebp e mov edi che diventeranno rispettivamente mov ebp, dword ptr [004E221C] e mov edi, dword ptr [004E2220]. Forza abbiamo quasi finito, fate partire e.... PARTEEEEEEEEEEEEEEEEEEEEEEEE!!!!!!!!!!!!!! Provate però a cominciare una nuova partita :), crash! Ok, non spazientiamoci, osserviamo i dati del crash 00478E94, è un ret, quindi è un'api sballata, controllate il file originale e scoprirete che a 00478E0C viene chiamata RegCreateKeyEx, mentre nel crack viene chiamata RegOpenKeyA, sostituite e salvate. Fate partire, ok sappiamo che parte, fate nuovo gioco... WOWWWWWWW!!!!!! Ecco comparire il filmato iniziale ed ecco raziel che fa la sua comparsa nella stanza del tempo!!!!!!!!!!!!!! FUNZIONAAAAAAAAAAAAAAAAAAAAAAAAAA!!!!!!!!!!!!!!!!!!!! Premente ESC e inizierà a parlare moebius, mandatelo a quel paese e fate ALT+F4, d'oh! crash! Controllate il crash e vedrete che è a 0045017C, un call edi, controllate che viene messo in edi, e vedrete che è GetVersionEx, ok nessun problema, controllate il file originale e vedrete che è Sleep la funzione da mettere, fixate, salvate, avviate e... GIOCATE!!!!!! Adesso il gioco funziona alla perfezione, niente più safedisc e niente più crash!!!!!!!! Ok giocate finche volete ma poi uscite che c'è ancora un'ultima cosa da fare, ovvero rimuovere il cd check. Procediamo.

Chi di voi ricorda i bei tempi in cui quando eravate alle prime armi per cracckare cercavate la stringa del messaggio di errore e cambiavate il jne poco prima? Be è ora di tornare al passato :), quindi togliete il cd, e fate partire il gioco, vi appare il messaggio "Please insert ecc...", ok apriamo il file con w32dasm, e cerchiamo quella stringa, troviamo un'unica reference a 004509E0, andiamo un po' più sopra e a 004509D1 troviamo un jne, cambiamolo in un jmp incondizionato, avviamo soul reaver 2, e... parte! Senza cd! Ma che controllo del cacchio! Bene a questo punto se volete fate un rebuild con il PEditor (ovviamente fare un realign) così otterette un file più piccolo, comunque come volete, la differenza è di 32kb.

Ok, abbiamo FINITO!!!!!!!!!!!!!!! Ora divertitevi quanto vi pare ad ammazzare vampiri, a succhiare le anime, e... be non vi svelo la trama :).
 
(un ultima nota, alcuni crash sono dovuti al fatto che il logger per qualche arcano motivo, non ha loggato bene alcune chiamate, ma infondo non sono poi tante, quindi chi se ne frega! :), se volete potete pure aggiungere il logging per advapi32 dato che da un po' di problemi, io ho preferito di no perché volevo vedere come si comportava sistemando l'entry per advapi32 a mano)
Ciao!!

Quake2

Note finali

Be che dire, il tutorial è venuto più lungo del previsto, ma credo che sia abbastanza chiaro e soprattutto ho cercato di descrivere al meglio ciò che c'è da fare, ma purtroppo non sono molto bravo a spiegarmi :), e adesso è ora dei ringraziamenti e dei saluti:

cominciamo con azzurra :) :

un saluto, un ringraziamento, un milione di ringraziamenti, un milardo di ringraziamenti, vabbe basta :), a Yado per il suo aiuto e per i tanti consigli che mi ha dato su safedisc e per il codice del logger :) (anche se poi alla fine l'ho cambiato quasi tutto :))
ringrazio anche AndreaGeddon perché anche lui mi ha dato un sacco di consigli
vorrei salutare anche tuttti quelli di #crack-it e #asm (tra cui albe, deimos, pbdz, true-love, blackdeath, ecc... :))
un saluto particolare a ^Spider^ perché mi sta simpatico (e perché tra un po' tocca pure alla sua tarantula :))
un saluto a tutto #gameprog-ita (casa dolce casa :))

e finiamo con ircnet :) :


vorrei salutare tutti quelli di #kill'em-all (hail!)
vorrei salutare anche tutti quelli di #programmazione, e vorrei ringraziare recidjvo per avermi fatto da guida a milano altrimenti mi sarei perso :)

come ultima cosa, forse non importante ma per me lo è, vorrei ringraziare gli Iced Earth per la splendida serata che mi hanno fatto passare il 10 febbraio, grazie del concerto!!!!!!!!!!!...Si si...concerto...NdQue :P

Disclaimer

Vorrei ricordare che il software va comprato e  non rubato, dovete registrare il vostro prodotto dopo il periodo di valutazione. Non mi ritengo responsabile per eventuali danni causati al vostro computer determinati dall'uso improprio di questo tutorial. Questo documento è stato scritto per invogliare il consumatore a registrare legalmente i propri programmi, e non a fargli fare uso dei tantissimi file crack presenti in rete, infatti tale documento aiuta a comprendere lo sforzo immane che ogni singolo programmatore ha dovuto portare avanti per fornire ai rispettivi consumatori i migliori prodotti possibili.

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

Capitoooooooo????? Bhè credo di si ;))))