Medal Of Honor:
Manual Unpacking SafeDisc v2.51

Data

by "ViGàN"

 

12/05/2003

UIC's Home Page

Published by Quequero


Non so cosa scrivere......

Complimenti vivissimi vigan, per essere il tuo primo tute hai fatto un lavoro davvero ottimo, mille complimenti davvero, continua cosi!

Se mi viene in mente qualcosa lo scriverò la prossima volta :-)

....

UIN ICQ: 169642101

....

Difficoltà

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

 

In questo tutorial vedremo come eliminare la SafeDisc v2.51 e di conseguenza faremo anche la patch no-cd in modo da poter giocare a MOHAA senza aver il cd originale nel lettore cd-rom :-))


Medal Of Honor:
Manual Unpacking SafeDisc v2.51
Written by ViGàN

Introduzione

Bon ragazzi....Armatevi di tanta pazienza (perchè ce ne vorrà) e non prendete impegni per un certo periodo che la storia si fa un pò lunga :-))). SafeDisc v2.51 è molto simile alla versione 2 di cui Quake2 ne ha scritto un tutorial solo che è diventata ancora più carogna :-), infatti ora ci sono alcuni problemi in più da risolvere oltre alla IAT da ricostruire.

Tools usati

SoftIce
IceDump
W32Dasm
UltraEdit32
LordPe
ProcDump
ImportRec142+

Notizie sul programma

Beh, credo che tutti conoscono Medal Of Honor.... 

Essay

Ok, si comincia !!!
Innanzitutto devo specificare che in questo tutorial tutti gli indirizzi riportati sono stati presi dopo aver applicato la patch "MOHAA_IT_ONLY_patch111v9safedisk.exe" scaricata dal sito ufficiale.

Bon, cominciamo con l'osservare com'è fatto l'eseguibile... Perciò prendiamo LordPE, apriamo MOHAA.EXE e andiamo a vedere le sezioni e notiamo subito che ce ne sono due con nomi strani.... stxt774 e stxt371, sia l'EP che la IT puntano appunto alla stxt371 perciò siamo sicuri che questo è il loader :-) Notiamo anche che la sezione .text ha il flag di sola lettura perciò andremo ad aggiungerci pure il writeable :-) Bon, ora per prima cosa dobbiamo trovare l'OEP del programma e questa è la parte più facile :-) Per trovare l'OEP dobbiamo settare un bpx GetVersion, poi inseriamo il cd originale nel lettore, facciamo partire il gioco e vediamo che ci appare una massagebox che ci avverte di aver rilevato un debugger quindi di scaricarlo e di riprovare... Le diamo l'OK, carichiamo IceDump e riproviamo... Infatti ora il gioco parte tranquillamente e noi non avremo più problemi di questo tipo :-). SoftIce popperà parecchie volte e noi usciremo di volta in volta con F5, poi uscirà la videata del gioco e Sice per 10-12 secondi se ne resterà buono e appena scomparirà lui popperà di nuovo... ora premiamo F12 e ci ritroviamo alla linea 0051DE9D...

:0051DE6A   or eax, FFFFFFFF
:0051DE6D   ret
:0051DE6E   xor eax, eax
:0051DE70   ret
:0051DE71   push ebp                  ; Entry Point
:0051DE72   mov ebp, esp
:0051DE74   push FFFFFFFF
:0051DE76   push 00537938
:0051DE7B   push 00520830
:0051DE80   mov eax, dword ptr fs:[00000000]
:0051DE86   push eax
:0051DE87   mov dword ptr fs:[00000000], esp
:0051DE8E   sub esp, 00000058
:0051DE91   push ebx
:0051DE92   push esi
:0051DE93   push edi
:0051DE94   mov dword ptr [ebp-18], esp
:0051DE97   call dword ptr [005351A4] ; call GetVersion
:0051DE9D   xor edx, edx              ; ci troviamo qui
:0051DE9F   mov dl, ah

Ora risaliamo un attimo il codice e all'indirizzo 0051DE71 troviamo un PUSH EBP preceduto da un RET.... Abbiamo trovato l'Original Entry Point !!! Ora cancelliamo il bpx GetVersion, ne mettiamo uno sull'OEP e usciamo dal Sice con F5. Quando avrà finito di caricare il processo mettiamo un bpx su ExitProcess e usciamo dal gioco. Sice popperà subito dopo, quindi andiamo a vedere in ESP l'indirizzo da cui siamo arrivati e troviamo 0051DE17. Facciamo UN-ASSEMBLE col tasto destro e, dopo aver cancellato il bpx ExitProcess, mettiamo un'altro bpx sulla CALL all'indirizzo 0051DE11. Ora abbiamo l'inizio e la fine del codice :-))) Dopo aver fatto tutto questo carichiamo nuovamente il gioco disabilitando il bpx all'OEP, cominciamo una partita, ammazziamo qualche crucco, la salviamo, la ricarichiamo, ci divertiamo coi settaggi e così via... Praticamente dobbiamo eseguire più codice possibile, il perchè ve lo spiego dopo :-))) Quando ci siamo divertiti abbastanza usciamo dal gioco e quando Sice popperà alla linea 0051DE11 ci annotiamo i primi 2 byte (FF15) e facciamo:

bd *                       ; disabilitiamo tutti i bpx
/a eip                    
; assemble eip
xxxx:0051DE11 jmp eip     
; loop infinito
<F5>

Praticamente abbiamo bloccato l'esecuzione del processo in modo da poterlo dumpare :-))) Ora prendiamo LordPE e dumpiamo MOHAA, gli correggiamo l'EP mettendolo al RVA 0011DE71, gli ripristiniamo i 2 bytes all'indirizzo 0051DE11 che avevamo modificato prima del dump e salviamo la sezione .text in c:\ col nome text1.dmp (potete metterci quello che volete, io ho fatto così per ricordarmi meglio) e ci annotiamo il suo VOffset e la sua VSize (00001000 e 00133D0A). Così la prima parte del lavoro l'abbiamo fatta :-)))
Noterete immediatamente che il file appena dumpato sarà grande circa 16 mega... Non preoccupatevi, per ora tenetelo così che ci farà comodo, ma vedrete che alla fine risolveremo pure questo problemino :-)
Ora comincia la parte più bella, dobbiamo fixare la IAT :-)
Cominciamo col darle un'occhiata, si trova giusto all'inizio della sezione .rdata all'indirizzo 00535000 e si estende fino a 005353E0. Vediamo subito che ci sono delle Dword del tipo 02Fxxxxx, altre del tipo BFFxxxxx e qualcuna vuota. Le prime puntano a del codice che fa da ponte con una dll esterna (~df394b.tmp) creata al momento dell'avvio del processo la quale si occupa di calcolare l'indirizzo della funzione da chiamare in quel momento... Le altre invece puntano a degli indirizzi corretti di API, quindi già risolte :-) Quelle vuote invece servono per separare le varie funzioni da libreria a libreria. Per sapere a quale dll importata corrisponde ciascun gruppo basta prendere una funzione già risolta e fare D <indirizzo>. Il primo gruppo non ha funzioni risolte perciò non sappiamo a quali Api punta, il secondo punta a gdi32, il terzo a Kernel32, il quarto non sappiamo, il quinto a user32, il sesto, il settimo e l'ottavo puntano ripettivamente a winmm, a wsock32 e a system86 e sono già completamente risolte :-)))
Ora analizziamo dal "vivo" come la SafeDisc fa a risolvere le Api prendendo come esempio la call GetVersion all'indirizzo 0051DE97 e poi penseremo come fixarle...

:0051DE97 call [005351A4] ; in 5351A4 abbiamo l'indirizzo 02FE35BC
..............
:02FE35BC push BFEA119B
:02FE35C1 pushfd
:02FE35C2 pushad
:02FE35C3 push esp
:02FE35C4 push 02FE35FC
:02FE35C9 call 021F5CE0
:02FE35CE add esp,08
:02FE35D1 push 00
:02FE35D3 pop eax
:02FE35D4 popad
:02FE35D5 popfd
:02FE35D6 ret

Questo pezzo di codice è comune per tutte le chiamate ad Api non risolte, cambiano ovviamente gli indirizzi dei vari push ma la struttura è la stessa. Qua non c'è nulla d'importante, la parte che ci interessa comincia all'indirizzo 021F5CE0 quindi entriamo nella call e curiosiamo per capire dove possiamo "sniffare" l'entry point dell'api che sta risolvendo :-) Una volta entrati troveremo 10.000 jmp, jnz, je, jnb e per di più sarà pure codice automodificante :-( perciò ne riporto solo una piccolissima parte, solo i punti chiave...

:021F5CE0 push ebp          ; punto di partenza...
:021F5CE1 mov ebp, esp      ; da qui in poi
:021F5CE3 sub esp, 40       ; ci sarà una
:021F5CE6 push ebx          ; lunghissima
:021F5CE7 push esi          ; e stressante
:021F5CE8 Push edi          ; routine :-(
:021F5CE9 jnp 021F5CEF      ; vedrete il codice
:021F5CEB xchg ecx, ecx     ; che cambia di continuo
:021F5CED jmp 021F5CF5      ; sotto i vostri occhi
...............
:021F5E87 push eax
:021F5E88 call 021F3F30     ; questa call controlla se l'api corrente è già stata risolta in precedenza
:021F5E8D add esp, 0C
:021F5E90 mov [ebp-0C], eax ; in eax c'è 01 se è già stata risolta, altrimenti ci sarà 00
:021F5E93 cmp [ebp-0C], 00
:021F5E97 jz 021F6018       ; se no, va a risolverla
...............
:021F638B mov esp, [ebp+0C]
:021F638E popad
:021F638F popfd
:021F6390 ret               ; eseguito il ret ci troveremo in Kernel32.GetVersion

Altra cosa importantissima da sapere è che SafeDisc ricava l'indirizzo delle funzioni tramite il caller address, infatti se proviamo a modificare il codice in un punto qualsiasi assemblando un "call [005351A4]", che teoricamente dovrebbe essere una call GetVersion, ora invece sarà tutt'altra cosa....
Ora prendiamo W32Dasm e diamo un'occhiata al codice per capire in che modo avvengono le chiamate alle api. Per far velocemente cerchiamo tramite la funzione "Search->Find Text" la stringa 00535 (parte dell'indirizzo comune per tutte le api). Troviamo:

- jmp dword ptr [00535xxx] Opcode-> FF25
- call dword ptr [00535xxx] Opcode-> FF15
- mov ebp, dword ptr [00535xxx] Opcode-> 8B2D
  call ebp Opcode-> FFD5
- mov ebx, dword ptr [00535xxx] Opcode-> 8B1D
  call ebx Opcode-> FFD3
- mov edi, dword ptr [00535xxx] Opcode-> 8B3D
  call edi Opcode-> FFD7
- mov esi, dword ptr [00535xxx] Opcode-> 8B35
  call esi Opcode-> FFD6

Poi c'è un'altro modo utilizzato, cioè con un jmp 013Fxxxx il quale punta alla sezione .stxt774.
Bon, ora sappiamo tutti i particolari che ci servono per poter fixare la IAT :-)
L'idea è questa:

- cerchiamo nella sezione .text tutte le chiamate alle Api
- le simuliamo imbrogliando il processo
- ci sniffiamo il VERO Entry Point di ciascuna Api
- salviamo in un'altra locazione la VERA IAT
- modifichiamo il codice in modo che le chiamate puntino ai nuovi indirizzi

Ok, diamoci da fare...cerchiamo dello spazio vuoto nella sezione .data dove poter inserire il nostro codice e la nuova IAT. Non c'è nessun problema, abbiamo circa 14 mega solo di 00 :-), decidiamo di posizionarci all'indirizzo 0056EEED, mentre la IAT la inseriremo a partire dall'indirizzo 0056AD3A. Bon, cominciamo... Facciamo partire MOHAA con il bpx all'OEP attivato e quando il Sice poppa spostiamoci tramite un R EIP 0056EEED, ora assembliamo questo codice :

:0056EEED JMP 0056EEED       ; qui arriviamo quando abbiamo finito un giro
:0056EEEF NOP
:0056EEF0 MOV EAX,00401000
  ; inizio sezione .text
:0056EEF5 INC EAX
:0056EEF6 CMP EAX,00534D0F
  ; fine sezione .text
:0056EEFB JAE 0056EEED
      ; se l'abbiamo controllata tutta salta
:0056EEFD MOV [0056F00D],EAX
; memorizza l'EIP sotto controllo
:0056EF02 MOV EDI,[EAX]
:0056EF04 CMP DI,15FF
       ; controlla se è una call [xxxxxxxx]
:0056EF09 JZ 0056EFAE
       ; se sì va a fare un'ulteriore controllo
:0056EF0F CMP DI,25FF
       ; se è un jmp [xxxxxxxx]
:0056EF14 JZ 0056EFAE
:0056EF1A CMP DI,1D8B
       ; se è un mov ebx
:0056EF1F JZ 0056EF36
:0056EF21 CMP DI,2D8B
       ; se è un mov ebp
:0056EF26 JZ 0056EF36
:0056EF28 CMP DI,3D8B
       ; se è un mov edi
:0056EF2D JZ 0056EF36
:0056EF2F CMP DI,358B
       ; se è un mov esi
:0056EF34 JNZ 0056EEF5
      ; altrimenti aumenta l'EIP
:0056EF36 MOV EDI,[EAX+02]
  ; in edi abbiamo la dword [xxxxxxxx]
:0056EF39 CMP EDI,00535018
  ; controlla che si tratti della IAT relativa al 1° gruppo
:0056EF3F JAE 0056EEF5
:0056EF41 CMP EDI,00535000
:0056EF47 JB 0056EEF5
       ; altrimenti aumenta l'EIP
:0056EF49 MOV EDI,[EDI]
     ; in edi abbiamp la dword dell'Api
:0056EF4B CMP EDI,03000000
  ; le Api non risolte sono TUTTE 02fxxxxx,mentre le altre sono TUTTE superiori
:0056EF51 JA 0056EF83
:0056EF53 INC EAX
           ; aumentiamo l'EIP
:0056EF54 MOV EDI,[EAX]
     ; in edi abbiamo gli opcode
:0056EF56 CMP DI,D5FF
       ; cerca la call ebp
:0056EF5B JZ 0056EF72
:0056EF5D CMP DI,D7FF
       ; la call edi
:0056EF62 JZ 0056EF72
:0056EF64 CMP DI,D3FF
       ; la call ebx
:0056EF69 JZ 0056EF72
:0056EF6B CMP DI,D6FF
       ; e la call esi
:0056EF70 JNZ 0056EF53
      ; altrimenti aumenta l'EIP e ricomincia
:0056EF72 ADD EAX,00000002
  ; in eax abbiamo il VA dell'istruzione successiva alla call
:0056EF77 PUSH EAX
:0056EF78 MOV EDI,[0056F00D]
; in edi ora c'è l'EIP sotto controllo
:0056EF7E MOV EDI,[EDI+02]
  ; ora invece l'indirizzo della IAT
:0056EF81 JMP [EDI]
          ; andiamo a risolvere l'Api
:0056EF83 MOV EAX,0056AD39
  ; qui arriviamo dopo aver risolto l'Api
:0056EF88 INC EAX
:0056EF89 MOV EBP,[EAX]
     ; in ebp abbiamo il VA della 1^ dword della nostra IAT
:0056EF8B CMP EBP,00
        ; controlla che sia vuota
:0056EF8E JZ 0056EFAA
:0056EF90 CMP EBP,EDI
       ; in edi abbiamo l'api appena risolta e controlla che non sia già presente
:0056EF92 JNZ 0056EF88
:0056EF94 MOV EBP,[0056F00D]
; in ebp abbiamo il VA sotto controllo
:0056EF9A ADD EBP,02
        ; ora invece abbiamo la dword 00535xxx
:0056EF9D MOV [EBP+00],EAX
  ; la sostituisce con la nuova, in modo che punti alla NOSTRA IAT
:0056EFA0 MOV EAX,[0056F00D]
; rimette in eax l'EIP della sezione .text e prosegue la scansione
:0056EFA5 JMP 0056EEF5
:0056EFAA MOV [EAX],EDI
     ; inserisce l'Api nella nuova IAT
:0056EFAC JMP 0056EF94
      ; va a correggere gli opcode nella sezione .text
:0056EFAE MOV EDI,[EAX+02]
  ; qui arriviamo se si tratta di una call[*] o un jmp[*]
:0056EFB1 CMP EDI,00535018
  ; controlla che si tratti di un'Api del 1° gruppo
:0056EFB7 JAE 0056EEF5
:0056EFBD CMP EDI,00535000
:0056EFC3 JB 0056EEF5
       ; altrimenti prosegue con la scansione
:0056EFC9 MOV EDI,[EDI]
:0056EFCB CMP EDI,03000000
  ; controlla che sia un'Api non risolta
:0056EFD1 JA 0056EF83
       ; altrimenti va ad aggiungerla direttamente alla NOSTRA IAT
:0056EFD3 CMP EDI,00
:0056EFD6 JZ 0056EEF5
:0056EFDC JMP EAX
           ; va a risolverla

Ok, fin qui ci siamo.... Vi ricordate che c'era una call che controllava se l'Api corrente era già stata risolta in precedenza e in caso affermativo evitava la routine di decrypt deviandoci direttamente all'EP della funzione? Bene, il jz alla linea 021F5E97 dobbiamo trasformarlo in un jmp incondizionato siccome, sparando a ripetizione un'Api dopo l'altra, SafeDisc ci risolverebbe solo la prima mentre le successive le considererebbe già risolte fornendoci dati sballati... Forzando quel jmp obblighiamo SafeDisc a risolverle sempre e comunque, in modo tale d'avere sempre dati aggiornati e corretti :-). Ora dobbiamo trovare il punto in cui sniffare l'indirizzo corretto dell'Api. Quando siamo al RET alla linea 021F6390 ce l'abbiamo in ESP perciò 3 linee sopra assembliamo :

:021F638B jmp 0056EEC0     ; spazio vuoto dove inserire il nostro codice
...............
:0056EEC0 mov esp,[ebp+0C] ; da qui...
:0056EEC popad
:0056EEC popfd             ; ...a qui ripristiniamo il codice cancellato col nostro JMP
:0056EEC mov edi,[esp]     ; prendiamo l'EP corretto dell'Api
:0056EEC jmp 0056EF83      ; salta al nostro codice

Finito di assemblare questo codice vi consiglio, visto che è lungo, di salvarlo in modo che se qualcosa va storto non serve che lo rifacciate tutto da capo... Allora fate:

/dump 0056EEC0 11E c:/iatfixer.dmp

Ok, ora dobbiamo caricare la sezione .text che avevamo salvato prima, perciò fate:

/load 00401000 00133D0A c:/text1.dmp

Fatto tutto mettiamo un bpx alla linea 0056EEED (così quando avrà finito il primo giro Sice popperà) e premiamo F5. La prima volta che appare Sice avremo risolto SOLO le Api riguardanti il 1° gruppo, per risolvere il 2° dobbiamo editare le linee 0056EF39 e 0056EFB1 mettendo al posto di 00535018 il VA della dword vuota che termina il 2° gruppo nella IAT originale cioè 00535044. La linea 0056EF83 invece dobbiamo editarla sostituendo 0056AD39 con la dword SUCCESSIVA a quella vuota che termina il gruppo appena risolto, sottraendo 1 byte cioè 0056AD55. Per i gruppi successivi basta seguire lo steso procedimento. Finito tutti gli 8 giri avremo la nostra IAT risolta e tutti gli indirizzi della sezione .text sostituiti con quelli nuovi :-). Ora disabilitate tutti i bpx e dumpate nuovamente il processo. Ok, ora ci mancano ancora quei "jmp 013Fxxxx" e poi avremo finito... Quando incontriamo quei jmp veniamo spediti nella sezione .stxt774, attraverso una call viene calcolato il Return Address e poi veniamo comunque rispediti alla famosa call 021F5CE0, perciò arrivati alla linea 021F6390 avremo sempre in ESP l'EP corretto dell'Api :-). Siccome non ce ne sono molti, apriamo W32Dasm, cerchiamo tutti i jmp 013Fxxxx e ce li segnamo. Poi facciamo partire MOHAA con il bpx all'OEP attivato, modifichiamo il jz in jmp alla linea 021F5E97 e ci tracciamo tutte le chiamate segnandoci di volta in volta l'indirizzo della relativa Api, per esempio BFF77ADB per la funzione CreateFileA... Poi cercheremo tramite UltraEdit il relativo indirizzo nella nostra IAT,ricordando però di invertire i byte, e alla fine avremo questa tabella :

   VA            Funzione chiamata            Indirizzo della nostra IAT

:00428317 --- Kernel32.CreateFileA ------------- 0056ADAE
:00428355 --- Kernel32.CreateFileA ------------- 0056ADAE
:0042850A --- Kernel32.CloseHandle ------------- 0056AD92
:0047E4DC --- User32.SetCursorPos -------------- 0056AFB6
:004813DD --- User32.WsprintfA ----------------- 0056AFBA
:00482075 --- Kernel32.GetDriveTypeA ----------- 0056AEBA
:00482160 --- Kernel32.GlobalUnlock ------------ 0056AE3A
:004822EA --- Kernel32.FreeLibrary ------------- 0056ADCE
:00482566 --- Kernel32.FormatMessageA ---------- 0056AE42
:0048262F --- Kernel32.GetProcAddress ---------- 0056ADCA
:00488DC0 --- User32.DestroyWindow ------------- 0056AFDA
:00489018 --- User32.AdjustWindowRect ---------- 0045AF8A
:004893AA --- User32.SystemParametersInfoA ----- 0056B04E
:00489BE8 --- User32.CallNextHookEx ------------ 0056B06A
:00489D41 --- User32.SetWindowsHookExA --------- 0056B06E
:004A1956 --- User32.MessageBoxA --------------- 0056AFAE
:004A1C2C --- User32.EnumDisplaySettingsA ------ 0056B062
:004A1F33 --- User32.AdjustWindowRect ---------- 0056AF8A
:004A2041 --- User32.UpdateWindow -------------- 0056B02E
:004A2427 --- Gdi32.DescribePixelFormat -------- 0056AD6E
:004A2AB7 --- Kernel32.WaitForSingleObject ----- 0056AE2A
:004A2E2E --- User32.EnumDisplaySettingsA ------ 0056B062
:0051BC66 --- Kernel32.HeapReAlloc ------------- 0056AEA2
:0051C5E3 --- Kernel32.InterlockedIncrement ---- 0056AEC2
:0051CE28 --- Kernel32.DeleteFileA ------------- 0056ADAA
:0051D1C7 --- Kernel32.GetFullPathNameA -------- 0056AF7A
:005223EF --- Kernel32.VirtualFree ------------- 0056ADD6
:005229C5 --- Kernel32.VirtualFree ------------- 0056ADD6
:0052528E --- Kernel32.GetFileType ------------- 0056AF32
:0052969A --- Kernel32.CompareStringA ---------- 0056AF62
:0052A260 --- Kernel32.CreateFileA ------------- 0056ADAE
:0052A281 --- Kernel32.GetFileType ------------- 0056AF32
:0052A28C --- Kernel32.CloseHandle ------------- 0056AD92
:0052D18A --- Kernel32.GetLastError ------------ 0056ADC2

Ora che abbiamo tutte le funzioni risolte prendiamo l'editor esadecimale oppure LordPE tramite la funzione FLC e sostituiamo i vari "jmp 013Fxxxx" (opcode E9xxxxxxxx) con delle "call [0056Axxx]" (opcode FF15xxAx5600). Fatto questo abbiamo ricostruito completamente la IAT e aggiustato tutti gli indirizzi che puntano ad essa :-). Ora, sempre con LordPE, salviamo le sezioni .text e .data con i nomi text2.dmp e data2.dmp che ci serviranno ancora... Ok, ora TEORICAMENTE MOHAA dovrebbe funzionare ma non è così.... Se proviamo a farlo partire ci verrà detto che è avvenuto un errore all' EIP 021B2590... Ma non è un indirizzo che punta alla SafeDisc ? Allora vediamo cosa c'è nello stack al momento dell'errore e troviamo le prime 2 dword che puntano ancora alla dll esterna mentre la terza è 0043D173 che si trova nella sezione .text. Prendiamo W32Dasm, ci rechiamo a quell' indirizzo e vediamo che l'istruzione prima è una call 00403829, proviamo ad entrarci e notiamo che ci sono un'infinità (circa 500) di call che puntano a questo indirizzo, poi c'è una call 00402D54 e poi al suo interno un jmp eax che sicuramente ci porterà all'indirizzo 021B2590... A questo punto non ci resta che osservare con Sice cosa succede in questa call, perciò avviamo il processo con il bpx attivato all'OEP e quando poppa ne mettiamo uno alla linea 00403829 e premiamo F5. Quando Sice appare avremo giustamente in ESP l'indirizzo 0043D173 che sarebbe il Return Address e troveremo questo codice:

:00403829 PUSH ECX
:0040382A PUSH EAX
:0040382B CALL 00402D54
.......................
:00402D54 MOV EAX, FFFFD87C
:00402D59 POP ECX        ; in ecx abbiamo 00403830
:00402D5A ADD EAX, ECX   ; in eax abbiamo 004010AC
:00402D5C MOV EAX, [EAX]
:00402D5E JMP EAX        ; ora abbiamo 021B2590
........................
:021B2590 POP EAX
:021B2591 POP ECX
:021B2592 PUSH 00400000
:021B2597 PUSHFD
:021B2598 PUSHAD
:021B2599 MOV EAX, [ESP+28]
:021B259D MOV ECX, ESP
:021B259F ADD ECX, 24
:021B25A2 PUSH EAX
:021B25A3 PUSH ECX
:021B25A4 CALL 021B24F0 ; questa call calcola il giusto Return Address
:021B25A9 POP ECX
:021B25AA POP EAX
:021B25AB POPAD
:021B25AC POPFD
:021B25AD RET           ; ora in ESP abbiamo il VA corretto :-)

Quando eseguiamo il ret ci ritroveremo in 0043D120... Praticamente ci troviamo di fronte alla stessa situazione che avevamo per le Api non risolte, cioè abbiamo una call che ci manda nella dll esterna del SafeDisc la quale ricava, attraverso il Caller Address, il VA corretto dove una volta effettuato il RET alla linea 021B25AD verremo rediretti. Quindi anche il procedimento per fixare queste call sarà molto simile a quello usato precedentemente :-))). Ora, tanto per avere qualche riferimento, lasciamo attivo il bpx alla linea 00403829, ne mettiamo un'altro sulla 021B25AD e premiamo F5 segnandoci di volta in volta il Caller Address e il relativo VA della destinazione reale. Dopo alcuni passaggi avremo questa tabella:

0043D16E ----> call 0043D120
004812F3 ----> call 0045D450
004571E0 ----> call 0046F960
0043E321 ----> call 0051A9FC
004E5F75 ----> call 004F5A50
004DAE34 ----> call 004F5AD0

Bon, l'idea sarebbe quella di ricercare tutte le "call 00403829" nella sezione .text, tracciarle, e dopo aver sniffato il VA di destinazione corretto andare a sostituire gli opcode con quelli esatti... Però c'è un problemino... Se noi tracciamo TUTTE queste call, quando avremo finito noteremo che le call della tabella qua sopra non combaciano con quelle appena risolte :-( allora prendiamo W32Dasm e cerchiamo nel codice la stringa "call 00403829". Notiamo immediatamente che parecchie di queste sono precedute da un RET oppure da un RET 00xx e non hanno nessuna "Referenced By a Call...." perciò noi, per testare se effettivamente sono queste call che ci sballano il risultato finale, avviamo nuovamente MOHAA con il bpx attivato all'OEP e ,una volta poppato, ci spostiamo tramite un R EIP alla linea 0043D16E e tracciamo la call. Il risultato sarà giustamente 0043D120, ora spostiamoci su una call a caso di quelle "sospette", la tracciamo e poi ci ri-portiamo su quella di prima, la ri-tracciamo e noteremo che il risultato non è più quello esatto ma sarà completamente diverso... Ok, allora nel nostro callfixer dovremo far in modo di evitare questo tipo di call. Bon, cominciamo ad assemblare il nostro codice :-). Avviamo MOHAA sempre con il bpx all'OEP attivato e spostiamoci nuovamente all'indirizzo 0056EEED. Per prima cosa dobbiamo caricare le sezioni .text e .data che avevamo salvato prima, le quali contengono tutte le modifiche che abbiamo fatto fin'ora, quindi facciamo:

/load 00401000 00133D0A c:/text2.dmp
/load 00541000 00EA712D c:/data2.dmp

Ora invece cominciamo a scrivere il nostro codice, quindi :

a eip
:0056EEED JMP 0056EEED       ; qui arriviamo quando avremo finito
:0056EEEF PUSHAD             ; salviamo tutti i registri
:0056EEF0 MOV EAX,00401000   ; inizio sezione .text
:0056EEF5 INC EAX
:0056EEF6 CMP EAX,00534D0F   ; fine sezione .text
:0056EEFB JA 0056EEED        ; se l'abbiamo controllata tutta ci fermiamo
:0056EEFD MOV EBX,[EAX]      ; in ebx abbiamo gli opcode dell'EIP sotto controllo
:0056EEFF CMP BL,E8          ; controlla se si tratta di una call
:0056EF02 JNZ 0056EEF5
      ; se no, continua la scansione
:0056EF04 MOV EBX,[EAX+01]   ; in ebx abbiamo il n° di byte da saltare
:0056EF07 ADD EBX,EAX        ; li sommiamo all'EIP
:0056EF09 CMP EBX,00403824   ; controlla se si tratta di una call 00403829
:0056EF0F JNZ 0056EEF5       ; se no, continua la scansione
:0056EF11 MOV EBX,[EAX-01]   ; in ebx abbiamo l'opcode prima della call
:0056EF14 CMP BL,C3          ; controlla se è un RET
:0056EF17 JZ 0056EEF5        ; se sì, continua la scansione
:0056EF19 MOV EBX,[EAX-03]   ; in ebx abbiamo il 3° byte prima della call
:0056EF1C CMP BL,C2          ; controlla se è un RET 00xx
:0056EF1F JZ 0056EEF5        ; se sì, continua la scansione
:0056EF21 MOV [0056F00D],EAX ; memorizza l'EIP sotto controllo
:0056EF26 JMP EAX            ; va a tracciare la call
:0056EF28 POP ECX            ; da qui....
:0056EF29 POP EAX
:0056EF2A POPAD
:0056EF2B POPFD              ;...a qui, ripristiniamo il codice cancellato col nostro jmp alla linea 021B25A9
:0056EF2C PUSH EAX           ; salviamo eax
:0056EF2D MOV EAX,[0056F00D] ; in eax abbiamo l'EIP della call
:0056EF32 ADD EAX,00000005   ; ora abbiamo il VA dell'istruzione successiva
:0056EF37 PUSH EBX           ; salviamo ebx
:0056EF38 MOV EBX,[ESP+08]   ; ci prendiamo il VA corretto da sostituire allo 00403829
:0056EF3C SUB EBX,EAX        ; ci calcoliamo il n° di byte da saltare
:0056EF3E MOV [EAX-04],EBX   ; li sostituiamo a quelli originali
:0056EF41 POP EBX            ; riprendiamo ebx
:0056EF42 POP EAX            ; riprendiamo eax
:0056EF43 ADD ESP,08         ; rimettiamo ESP com'era in principio
:0056EF46 JMP 0056EEF5       ; continuiamo con la scansione

Ora modifichiamo pure la linea 021B25A9, che sarebbe l'istruzione successiva alla call che calcola il VA corretto in cui verremo rediretti, inserendo un jmp che ci porta al nostro codice. Perciò:

a 021B25A9
:021B25A9 jmp 0056EF28

Bon, ora vi consiglio di salvare nuovamente il codice appena scritto onde evitare di dover rifare tutto in caso di errore... Alcuni di voi (spero in pochi :-)) si chiederanno perchè alla linea 0056EF09 confrontiamo EBX con 00403824 anzichè con 00403829 ... Il motivo è semplice, prendiamo per esempio gli opcode dell'indirizzo 0040140B cioè E819240000, di cui E8 è l'opcode che identifica una call, mentre 00002419 sono i byte da saltare partendo dall'istruzione SUCCESSIVA per arrivare all'indirizzo specificato. Perciò 00401410+2419=00403829, ma siccome in EBX abbiamo l'Eip sotto controllo dobbiamo sottrarre 5 byte dal risultato, quindi 00403824. Ora che abbiamo capito tutto spostiamoci con un R EIP all'indirizzo 0056EEEF, mettiamo un bpx in 0056EEED e premiamo F5. Quando Sice popperà avremo tutte le "call 00403829" sostituite con le call esatte :-) quindi dumpiamo per l'ultima volta il processo chiamandolo final.exe e correggiamogli l'OEP. Ok, finalmente se proviamo a far partire il file appena dumpato, funzionerà senza darci problemi :-) però prima di aver finito ci manca ancora qualche cosetta... Ci manca da ricostruire la IT, perchè quella che abbiamo ora è quella che usa SafeDisc e noi utilizzeremo ImportRec per ricostruircela. Per cui avviamo l'ultimo dump con il bpx all' EP attivato e quando Sice poppa assembliamo un jmp eip in modo da bloccarne l'esecuzione. Fatto ciò avviamo ImportRec scegliendo nella casella in alto il nostro processo, premiamo "iat Auto Search", probabilmente ci dirà che non la riesce a trovare per cui inseriamo nel textbox "RVA" l'indirizzo 0016AD3A mentre in "Size" ci mettiamo 3E0 e premiamo "Get Imports" e poi "Auto Trace". A questo punto abbiamo la nostra IT ricostruita quindi togliamo la spunta nella casella "Add New Section", inseriamo l'indirizzo 0016B150 nel box "RVA" e premiamo "Fix Dump" selezionando il file final.exe. Ora ImportRec ci avrà creato il file Final_.exe, che sarà quello "buono". A questo punto prendiamo LordPE ed eliminiamo, in quest'ultimo file, le ultime due sezioni, la stxt774 e la stxt371 tanto non servono più e sono pure brutte da vedere :-). Bon, abbiamo quasi finito, ci mancano ancora due passaggi :-). Il primo è quello di allegerire il file che ora è di 16335 KB. Per fare ciò prendiamo ProcDump e gli facciamo un bel rebuild così diventerà solo di 1508 KB. Il secondo invece è un pò più lungo... Vi ricordate che all'inizio vi avevo detto di eseguire il più codice possibile prima di dumpare il processo invece che dumparlo immediatamente all'OEP? Il motivo è che in questa versione SafeDisc fa utilizzo di alcuni trucchi anti-dump. Praticamente sostituisce parecchi byte della sezione .text con dei 0F0B (UD2), 0FAA (RSM), CD03 (INT03), CCCC, CC03, CC08, CC05 (INT03). Durante l'esecuzione normale del programma, questi opcode generano un'eccezione e di conseguenza veniamo sparati nel SEH Handler precedentemente installato dal SafeDisc il quale si occupa di sostituire questi opcode con quelli esatti. Nel file dumpato invece, avendo eliminato la SafeDisc, quando li incontriamo generano l'eccezione mandando in crash il programma. Eseguendo il più codice possibile prima di dumparlo abbiamo sfruttato la SEH in modo che sia proprio SafeDisc ad aggiustarci il maggior numero di opcode possibile. Ora il file che abbiamo dumpato è funzionante ma, se passiamo sopra del codice che non avevamo eseguito prima del dump, sicuramente giocandoci prima o poi andrà in crash..:-(( Per risolvere questo problema dobbiamo cercare con W32Dasm tutti questi opcode, segnarci i vari VA e poi tramite Sice passarli uno ad uno copiando gli opcode sostituiti. Fatto questo prendiamo LordPE e tramite la funzione FLC aggiustiamo il codice con i byte esatti... Qui di seguito vi riporto una tabella con TUTTI gli opcode che andrebbero sostituiti, più della metà saranno già corretti ma questo dipende da quanto codice avete eseguito prima di dumpare il processo...

0F0B (UD2):
0041E59D --> 85C0
0042AEC5 --> 3BD8
0042BEC7 --> 3BD8
0042DA95 --> 85C0
004595E1 --> 33C0
0047F440 --> 85C0
004813A3 --> 8BD8
0048311C --> 33C0
004845D7 --> 33C0
00488552 --> 8B1D1C505300 (ATTENZIONE!!!!)
00488877 --> 33C0
00488B3E --> 8B0DA0E77F00
0049FAD6 --> 33C0
004A1C73 --> 85C0
004A203A --> 8B0D28B6CB00
004DCEB1 --> 33C0
004E38D1 --> 33C0
004EC23D --> 85C0
004F43D2 --> 85C0
00500831 --> 33C0
00512555 --> 33C0
0051D100 --> 85C0
0051E0D5 --> 85C0
00521897 --> 85C0
005223F5 --> 8B0DF0803E01
00523B2D --> 85C0

0FAA (RSM):
004285A9 --> B8E81B5700
0042AFEE --> B805000000
00483EFF --> 3BC3
00488DA8 --> 8B15B4E77F00
004A2257 --> B801000000
004E2EE1 --> B801000000
004F9D9F --> 8BC3

CD03 (INT 03):
00405A21 --> 33C0
004157C5 --> 85C0
0042BC56 --> 85C9
004571D4 --> 85C9
0047E40D --> 85D2
0048182F --> 33C0
004822F0 --> 85C0
00482A6F --> 85C0
004832D4 --> 33C9
00484052 --> 2BC3
004883DC --> 33C0
0048D3B2 --> 85DB
004A092C --> 85C0
004A2BD9 --> 85C0
004E486A --> 85C0
004ED209 --> 85C0
004F1051 --> 33C0
004F6B7C --> 3BC8
004FD943 --> 75D8
005040D1 --> 3BC3
0050AC5B --> 85C9
0051BB3B --> 85C0
0051D35D --> 85C0
0051EC9B --> 85C0

CCCC-CC03-CC05-CC08 (INT 03):
00403D8D --> 3BC1
00408F70 --> 85C0
00410C07 --> 8B1548F05600
0041C4AE --> 85C0
00429F12 --> 3BC3
0042B2B6 --> 33C0
0042B723 --> 2BC2
0042DE4E --> B910000000
0043D168 --> 85C0
0043E2F3 --> 8BD1
004579FE --> 85C0
004615DA --> 8B1500B5D800
00470260 --> 3BC3
004705B0 --> 3BCB
004810C3 --> 8B15088FCC00
0048162A --> 8B1D1C535300 (ATTENZIONE!!!)
00482089 --> 33C0
004821B9 --> 9090
00482484 --> 85C0
00482622 --> 8B1578CF7F00
00482FD7 --> 85C0
00483DA0 --> 33C0
00484344 --> 8B0DECE17F00
00489516 --> 8B1528B6CB00
0049F78D --> 85C0
004A0CAB --> 85C0
004A28EA --> B9150A0000
004DC0CD --> 33C0
004E30D1 --> 33C0
004E5F67 --> 85C0
004EF69F --> 3BC3
004EFB59 --> C7450000000000
004F1612 --> 85C0
004F7BE6 --> 3BC3
004FF472 --> 3BC3
00504F81 --> 33C0
00506917 --> 3BCB
00511A48 --> 85C0
0051DDB3 --> 85C0
00520255 --> BAFFF3FFFF
0052292A --> 33C0

Una volta controllati tutti i vari indirizzi avremo il nostro file perfettamente funzionante :-). Una cosa importante da notare è che agli indirizzi 00488552 e 0048162A, ora che abbiamo sostituito i byte, c'è un "mov ebx,[00535xxx]" che sarebbero delle call non risolte... Nel caso che non siano già state risolte in precedenza, dobbiamo tracciarle nel file originale, segnarci l'Api chiamata e poi tramite LordPE aggiustare le dword puntate dalle due call con i relativi VA della nostra IAT. In pratica sono:

00488552: mov ebx,[0056AD56]-->Gdi32.GetDeviceCaps ------>OP=8B1D56AD5600
0048162A: mov ebx,[0056AFFA]-->User32.DispatchMessageA -->OP=8B1DFAAF5600

Fatto questo abbiamo finalmente finito tutto quanto :-)) Come dite???.... Dobbiamo ancora fare la patch NO-CD ???... Bhè, se provate a togliere il cd originale dal lettore cd-rom vi accorgerete che il gioco parte tranquillamente senza fare nessuna patch :-))) Praticamente eliminando totalmente la SafeDisc abbiamo anche eliminato ogni accesso al cd-rom :-)))

Byeeez

ViGàN

Note finali

Bon, questo è il mio primo tutorial, spero di essere stato abbastanza chiaro per tutti :-)
Il codice prendetelo come spunto... So che non è del migliore ed è piuttosto incasinato ma l'importante è che funzioni :-))
Detto questo passiamo ai saluti :-)
Saluto Quequero, che ringrazio per aver pubblicato questo tute :-), tutti gli iscritti alla ML, i frequentatori del canale #crack-it (ci sono stato un paio di volte, devo cominciare a frequentarlo un pò più spesso :-)) e tutti quelli del forum della UIC :-)
Saluto il mio amico BauD-0 che mi ha fornito la materia prima (il gioco originale :-)), ora dovrebbe darmi altro materiale...vedremo :-)

Ok, ora ho finito sul serio....ci sentiamo al prossimo tutorial :-))

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 ;))))