Hex WorkShop reversing
Da oggi "saltiamo" in Virtual Address!!!

Data

by "ZaiRoN"

 

o4-o4-2oo3

UIC's Home Page

Published by Quequero


Computer... ammazza Flanders!

H.J.Simpson  

Zai complimenti... L'idea e' davvero bella oltre che utile, bravo davvero

...

...


Home page se presente:
macchè...
E-mail:
ZaiRoNcrk(at)hotmail.com
Nick, canale IRC frequentato:

ZaiRoN su irc: #crack-it
ZaiRoN su efnet: #cracking4newbies e #win32asm

...

Difficoltà

()NewBies (Z)Intermedio ( )Avanzato ( )Master

 

Un nuovo adding functionality project.



Hex WorkShop reversing

Da oggi "saltiamo" in Virtual Address!!!


Written by
ZaiRoN

Introduzione


Ecco un altro tutorial di puro reversing. Il programma vittima è Hex WorkShop, un editor esadecimale niente male che però può essere leggermente migliorato! Il programma (come tutti gli altri editor esadecimali del resto) offre la possibilità di saltare da un byte all'altro inserendo semplicemente il nuovo offset, decimale o esadecimale che sia. Per i nostri "loschi" scopi però è spesso conveniente saltare specificando direttamente il Virtual Address... al lavoro!

Letteratura consigliata: una qualsiasi guida sul formato PE.

Tools usati


- Un debugger (Ollydbg)
- Un disassemblatore (Ida v4.3)
- Hex WorkShop v4.00
- Un editor di risorse (Resource Hacker v3.4)
- Code Snippet Creator (indispensabile!)

- Win32 Programmer's Reference

Musica: Frank Zappa - Hot Rats

URL o FTP del programma


http://www.bpsoft.com/downloads/

Notizie sul programma


Uno dei tanti editor esadecimali in circolazione, non credo necessiti di ulteriori spiegazioni.

Essay


Cominciamo subito partendo da una cosa che, inizialmente, potrà sembrarvi inutile ed estranea a questo tutorial ma che in realtà sarà il fulcro di tutto il lavoro che faremo.

Preambolo: conversione da Virtual Address a Offset.
Questo tipo di conversione è abbastanza semplice da effettuare e si riduce ad applicare la seguente formula:

Offset = Relative Virtual Address - Virtual Offset + Raw Offset
dove:
- Relative Virtual Address (RVA) = indirizzo relativo all'image base. Si calcola come:

Relative Virtual Address = Virtual Address - Image Base

  In seguito sarà indicato semplicemente con la sigla RVA.
- Virtual Address (VA) = valore che volete convertire. E' il classico indirizzo di memoria nella forma "401F69".
- Image Base = indirizzo dal quale il file viene mappato in memoria.
- Virtual Offset = indirizzo dal quale la sezione verrà mappata in memoria.
- Raw Offset = indirizzo fisico di inizio sezione nel file.

Gli ultimi due valori, Virtual Offset e Raw Offset, sono relativi ad una specifica sezione ma in generale un file contiene più di una sezione perciò come facciamo a sapere quale sezione scegliere? Sceglieremo quella che contiene il nostro Virtual Address; in particolare deve essere verificata la seguente espressione:
Virtual Offset =< RVA < (Virtual Offset + Virtual Size)

La conversione si può effettuare soltanto se esiste una sezione i cui valori soddisfano questa semplice espressione. Così semplice? Si...

Vediamo un esempio concreto e poi partiamo con il tutorial vero e proprio.
Supponiamo di avere un file con Image Base = 400000h e con sezioni:

Name Virtual Offset Virtual Size Raw Offset Raw Size Flags
.text 00001000 00000342 00000400 00000400 60000020
.rdata 00002000 00000174 00000800 00000200 40000040
.data 00003000 00000164 00000A00 00000200 C0000040
.rsrc 00004000 00000540 00000C00 00000600 C0000040

Convertiamo il VirtualAddress = 4030A2h in offset.

1. Conversione in RVA:  RVA = Virtual Address - Image Base = 4030A2h - 400000h = 30A2h
2. Controllo se il valore è nella sezione .text:
   30A2h >= 1000h   <----------- SI
   30A2h < (1000h + 342h)   <--- NO!!!
3. Controllo se il valore è nella sezione .rdata:
   30A2h >= 2000h   <----------- SI
   30A2h < (2000h + 174h) <----- NO!!!
4. Controllo se il valore è nella sezione .data:
   30A2h >= 3000h   <----------- SI
   30A2h < (3000h+164h)   <----- SI
5. Abbiamo trovato la sezione giusta, il Virtual Address verifica l'espressione e possiamo utilizzare la formula finale per ottenere il valore dell'offset cercato:  Offset = 30A2h - 3000h + A00h = AA2h

Il valore esadecimale AA2h è l'offset cercato; niente di complicato, sono soltanto dei semplici passi da applicare meccanicamente!

Strategia utilizzata.
Questa nuova versione di Hex WorkShop dovrà essere in grado di saltare al byte puntato dal Virtual Address specificato. Ovviamente questo non vuol dire che dobbiamo implementare da zero la routine di salto perché esiste già una routine di salto; Hex WorkShop, infatti, permette di saltare da una posizione all'altra all'interno di un file specificando semplicemente un Offset. Noi lavoreremo con Virtual Address che -come abbiamo visto prima- possiamo facilmente convertire in Offset quindi perché mai dovremmo scrivere una nuova routine di salto?

Detto questo, ecco ciò che dovremmo fare: leggere il VA, convertirlo in Offset ed effettuare il salto.
Per leggere il VA dobbiamo prima di tutto trovare una dialog in cui creare un nuovo controllo; tale controllo verrà utilizzato per l'inserimento del VA. Aggiungeremo un edit box direttamente nella dialog 'GoTo' e leggeremo il VA inserito nel momento in cui verrà premuto il tasto 'Go'.
La conversione da VA a Offset è il passo più delicato di tutti. Come abbiamo visto prima, per convertire un VA in Offset è necessario conoscere determinati valori del file su cui stiamo lavorando e dato che Hex WorkShop permette di aprire più file contemporaneamente dobbiamo rispondere alla seguente domanda: come facciamo ad identificare il file con cui stiamo lavorando e a recuperare tutti i valori necessari alla conversione? Il modo più logico è sicuramente quello di recuperare tutte le informazioni relative al file quando il tasto "Go" viene premuto; a quel punto, con tutti i dati a disposizione, potremmo tranquillamente effettuare la conversione. Purtroppo però ho già provato a seguire questa strada con la precedente versione di Hex WorkShop ottenendo scarsissimi risultati: il programma spostava continuamente il file e le varie strutture dati da una zona all'altra della memoria rendendo questa ricerca complicata. Data la mia incapacità nel risolvere il problema in questo modo, per questa nuova versione ho deciso di optare per un'altra soluzione sicuramente non elegante ma che sembra funzionare bene. In fondo anche questo è reversing!
Ogni volta che viene aperto un nuovo file HexWS visualizza il contenuto del file appena aperto all'interno di una nuova finestra MDI child. Tutto il lavoro che faremo si baserà esclusivamente sugli handle di queste finestre MDI contenenti i file aperti! L'idea di salvare l'handle della MDI child creata e i dati necessari alla conversione da Virtual Address a Offset durante la fase di apertura di un file. Avremo tante strutture quanti sono i file aperti (con pe header valido!) e l'handle della finestra MDI sarà la chiave di riconoscimento di una specifica struttura. Nel momento in cui dovremmo effettuare la conversione ci basterà recuperare l'handle della MDI child con il focus e cercare tale handle attraverso le varie strutture salvate fino a quel momento.
La parte finale sarà la più semplice di tutte. Come vedremo tra poco, passare l'Offset alla routine di salto presente in HWS sarà l'ultimo dei nostri problemi!

Concettualmente la modifica sta tutta qua ma come si dice tra il dire e il fare...
Fate una copia di HexWS (non si sa mai!!!) e vediamo come effettivamente verrà realizzato il tutto.

Modifichiamo la dialog 'Goto'.
Partiamo dalla cosa più semplice, la modifica della dialog 'Goto', quella che gestisce i parametri necessari alla definizione del tipo di salto da effettuare. Cosa aggiungiamo? La sola cosa necessaria è un edit box ma per rendere il tutto più carino aggiungeremo anche un controllo di tipo static. Per modificare la dialog utilizzeremo un comunissimo editor di risorse; solitamente io uso resource hacker ma qualsiasi editor andrà bene. Caricate Hex WorkShop col vostro editor preferito e cercate, all'interno di 'DIALOG', la dialog 'Goto'. Bene, è la numero 112. Modificatela come volete, compilate e salvate le modifiche; ecco qua come ho aggiunto i due nuovi controlli:


Ricordatevi di dare un ID ai due nuovi controlli; l'ID è necessario per poter -in seguito- identificare i controlli e accedere ai dati che contengono. In realtà potete evitare di dare un ID al controllo di tipo static perché non lo utilizzeremo mai. Al controllo di tipo edit ho dato ID = 40000; voi potete mettere quello che più vi piace, l'importante è che non sia un valore già usato da qualche altro controllo.

Come salviamo i dati.
Per effettuare la conversione abbiamo bisogno di svariati valori contenuti all'interno del PE-header di un generico file aperto. Considerando ciò che vi ho detto prima, dobbiamo salvare questi valori da qualche parte nel momento in cui il file sarà aperto. Prima di vedere dove salveremo i dati, vediamo per ogni file quali valori dobbiamo salvare:
- handle dell'MDI child contenente il generico file aperto
- Image Base
- Numero di sezioni presenti nel file
- VirtualOffset, RawOffset e PointerToRawData (di tutte le sezioni!!!)

Salvare il numero delle sezioni non è strettamente necessario ma per il tipo di implementazione che ho scelto mi fa comodo avere questo valore. I dati li salveremo nella maniera più semplice possibile: sequenzialmente. Per ogni file verranno salvate due strutture:


INFOFILE STRUCT
   handleMDI DWORD ?   ; Handle dell'MDI child
   imageBase DWORD ?   ; Image Base
   nSections WORD  ?   ; Numero di sezioni
INFOFILE ENDS

SECTIONVALUES STRUCT
   virtualOffset    DWORD ?   ; Virtual Offset
   rawOffset        DWORD ?   ; Raw Offset
   pointerToRawData DWORD ?   ; Pointer to Raw Data
SECTIONVALUES ENDS


Il significato delle due strutture mi sembra molto intuitivo. Da notare che la struttura SectionValues memorizza i dati relativi ad un'unica sezione; per ogni file salviamo nSections strutture di questo tipo.
D'ora in poi con il termine StrutturaFile farò riferimento all'insieme dei dati salvati, o da salvare, relativo ad un singolo file.

Iniziamo a salvare i dati.
Purtroppo non possiamo salvare tutti i dati di una StrutturaFile allo stesso momento perciò spezzeremo il processo in due parti: prima di tutto vedremo come salvare l'handle dell'MDI child mentre dopo vedremo come salvare tutti gli altri dati.

Ogni finestra figlia viene creata mandando il messaggio WM_MDICREATE tramite la funzione SendMessage che ha la seguente sintassi:


LRESULT SendMessage(
   HWND   hWnd,    // handle dell'oggetto al quale verrà mandato il messaggio
   UINT   Msg,     // messaggio da mandare: WM_MDICREATE
   WPARAM wParam,  // primo parametro del messaggio: non usato per questo si pone a 0
   LPARAM lParam,  // secondo parametro: puntatore alla struttura MDICREATESTRUCT di definizione dell'MDI child
);


La funzione restituisce l'handle della finestra creata, il valore che ci interessa! Cerchiamo dove viene mandato questo messaggio mettendo un breakpoint condizionale su SendMessage oppure utilizzando IDA. In entrambi i casi dovreste riuscire ad arrivare qui:


004ED198 mov ebx, ds:SendMessageA
004ED19E lea eax, [ebp+lParam]
004ED1A1 push eax    <-------------------- lParam: puntatore a MDICREATESTRUCT
004ED1A2 push 0    <---------------------- wParam: 0
004ED1A4 push WM_MDICREATE    <----------- il messaggio
004ED1A9 push dword ptr [esi+0BCh]    <--- hWnd: handle padre di tutti gli MDI child
004ED1AF call ebx    <-------------------- SendMessageA
004ED1B1 mov [ebp+hWnd], eax    <--------- eax = handle della finestra figlia appena creata
004ED1B4 call AfxUnhookWindowCreate
004ED1B9 test eax, eax
004ED1BB jnz short loc_4ED1C7

Abbiamo trovato l'handle che cercavamo; adesso dobbiamo decidere dove deviare il normale flusso del programma in modo che venga eseguito il nostro codice, codice che ci permetterà di salvare l'handle. L'indirizzo 4ED1B1 rappresenta un buon punto dove inserire il jump al nuovo codice.
Per aggiungere il nuovo codice al programma userò Code Snippet Creator. Se non avete mai provato questo tool, date un'occhiata al mio tutorial sul reversing di W32Dasm; troverete un po' di informazioni sul come creare e settare un nuovo progetto.
Continuiamo. Lanciate CSC e create un nuovo progetto (SaveHandle); ecco qua come impostare le proprietà del progetto:


- Patch as New Section
- Redirect Control From Code Section: Virtual Address = 004ED1B1
- Return To Program: Virtual Address = 004ED1B9


Come spesso faccio, il nuovo codice verrà aggiunto in una nuova sezione in modo da lasciare intatto il codice originale. Per saltare al nostro codice verrà modificata l'istruzione all'indirizzo 4ED1B1. Il ritorno al programma originale è gestito direttamente da CSC ma ricordiamoci che le istruzioni agli indirizzi 4ED1B1 e 4ED1B4 verranno cambiate dal CSC per cui sarà compito nostro inserirle nello snippet.
Vediamo il nostro primissimo snippet:


include HWS4.inc    ; file contenente la definizione di costanti e strutture varie

jmp @start
fileStructure         BYTE   300h dup(0)     ; indirizzo a partire dal quale memorizzeremo le varie StrutturaFile
AfxUnhookWindowCreate DWORD  004D44FDh       ; indirizzo funzione sovra scritta dalla patch
nextFreeFileStructure DWORD  fileStructure   ; indirizzo dove inserire la prossima struttura StrutturaFile
mdiHandle             HANDLE ?               ; handle dell'MDI client (il padre di tutte le future finestre...)

@start:
    pushad   ; salviamo tutto!

    ;salvo l'handle dell'MDI client (capirete dopo perché)

    push [esi+0BCh]    ; so che è [esi+BCh] perché è uno dei parametri della SendMessageA precedente
    pop [mdiHandle]    ; salvo, nella struttura StrutturaFile corrente, l'handle della finestra appena creata
    mov esi, [nextFreeFileStructure]
    mov [esi], eax

    popad   ; ripristiniamo tutto!

    ; prima di tornare al programma eseguo le due istruzioni originali

    mov dword ptr [ebp+18h], eax
    call AfxUnhookWindowCreate


Compilate e patchate il programma, CSC vi chiederà di settare le caratteristiche della nuova sezione; le caratteristiche più comuni sono già settate, potete lasciare tutto così com'è. Ah, date un nome alla sezione!
Il codice racchiude in se soltanto due semplici salvataggi ma prima di passare al salvataggio degli altri dati fermiamoci un attimo perché voglio fare un piccolo commento al codice. In particolare, parliamo prima di tutto dei 0x300h byte riservati per il salvataggio delle future StrutturaFile e allocati staticamente all'inizio del codice. Questo è sicuramente un modo poco elegante per risolvere il problema; poiché non sappiamo a priori quanti file verranno aperti e quanti byte occuperà ogni struttura, la cosa più logica da fare è quella di allocare spazio in memoria in maniera dinamica utilizzando funzioni tipo HeapAlloc ad esempio. Girellando tra i vari forum sparsi nella rete, ho notato che spesso questo tipo di funzioni sono in un certo modo OS dipendenti; non avendo la possibilità di provare il programma su macchine con OS diversi da w98 (avete ragione, mi dovrei aggiornare...) ho deciso di optare per l'allocazione statica. Forse mettendo 300h bytes ho un po' esagerato; diciamo che volevo andare sul sicuro!
E' bene spendere altre due parole sul significato di nextFreeFileStructure. In generale, ogni volta che viene aperto un nuovo file dobbiamo scorrere le varie strutture StrutturaFile già presenti per salvare i dati del file appena aperto in una zona di memoria non ancora utilizzata. Per evitare di scorrere tutte queste strutture, utilizziamo una DWORD (nextFreeFileStructure appunto) per salvare l'indirizzo di partenza di una ipotetica prossima StrutturaFile.

Proseguiamo con il resto del salvataggio. Gli altri dati da salvare sono tutti inclusi nel file quindi andremo a leggerli non appena il file sarà mappato in memoria. HWS utilizza la classica funzione MapViewOfFile per mappare il file; mettiamo un breakpoint su questa funzione e vediamo il codice che ci troviamo di fronte:


00416A25 push eax
00416A26 push 4    <-------------------------- dwDesiredAccess: mappa l'oggetto in sola lettura
00416A28 push [ebp+hFileMappingObject]    <--- hFileMappingObject: handle del mapping object creato con CreateFileMapping
00416A2B call ds:MapViewOfFile    <----------- MapViewOfFile: ritorna l'indirizzo di partenza del file in memoria
00416A31 test eax, eax    <------------------- controlla che il file sia stato mappato in maniera corretta
00416A33 mov [esi+14h], eax    <-------------- salva l'indirizzo ritornato dalla funzione
00416A36 jz short loc_416A40    <------------- salta se c'è stato qualche errore in fase di "mapping"
00416A38 mov [esi+28h], edi
00416A3B mov [esi+2Ch], ebx
00416A3E jmp short loc_416A5B


Come potete vedere abbiamo la chiamata della funzione seguita dal classico controllo sul valore di ritorno. Se tutto è andato bene, all'indirizzo 416A38 eax punta all'indirizzo a partire dal quale il file è stato mappato in memoria. Molto bene, direi che 416A38 rappresenta un buon punto per saltare al nuovo codice.
Creiamo il secondo progetto (SaveValuesForConvertion) con CSC e settiamo le proprietà:


- Snippet VA = 0058E33D
- Patch into Existing Section
- Redirect Control From Code Section: Virtual Address = 00416A38
- Return To Program: Virtual Address = 00416A5B


Il seguente snippet verrà inserito nella sezione creata in precedenza e l'indirizzo della prima istruzione sarà 58E33D; perché proprio questo indirizzo? Questo perché è l'indirizzo del byte successivo al codice che abbiamo inserito in precedenza con il primissimo snippet. Nulla e nessuno vi vieta di metterlo qualche byte più sotto :-). Come prima, CSC gestirà il ritorno al programma ma noi dobbiamo ricordarci di aggiungere le istruzioni sovra scritte.


include HWS4.inc

jmp @start
currentStrutturaFile DWORD 0058E309h   ; Indirizzo iniziale della StrutturaFile corrente (contiene solo l'handle)

@start:
   pushad

   ; Per salvare le prossime informazioni devo aver prima di tutto salvato l'handle

   mov edi, [currentStrutturaFile]
   mov edi, [edi]
   .IF dword ptr [edi] == 0
      jmp @done
   .ENDIF

   mov esi, eax   ; Indirizzo di memoria a partire dal quale il file appena aperto è stato mappato
   assume esi:ptr IMAGE_DOS_HEADER
   ; Controllo che il file abbia un PE header valido

   .IF [esi].e_magic == IMAGE_DOS_SIGNATURE       ; "MZ" è presente?
      add esi, [esi].e_lfanew
      assume esi:ptr IMAGE_NT_HEADERS
      .IF [esi].Signature == IMAGE_NT_SIGNATURE   ; "PE" è presente?
         jmp @PE                                  ; PE header presente!
      .ENDIF
   .ENDIF

   ; Il file non ha un formato PE valido, elimino l'handle salvato in
   ; precedenza, non salvo nient'altro e salto alla fine del codice.

   mov dword ptr [edi], 0
   jmp @done

  @PE:    ; Salvo tutti i dati necessari
   push currentStrutturaFile
   assume edi: ptr INFOFILE
   movzx ecx, [esi].FileHeader.NumberOfSections
   mov [edi].nSections, cx    ; Memorizzo il numero di sezioni presenti nel file
   mov eax, [esi].OptionalHeader.ImageBase
   mov [edi].imageBase, eax   ; Memorizzo ImageBase
   add edi, sizeof INFOFILE
   assume edi: ptr SECTIONVALUES
   add esi, sizeof IMAGE_NT_HEADERS     ; PE + F8 = inizio del SectionHeaders
   assume esi:ptr IMAGE_SECTION_HEADER
   .WHILE ecx>0                         ; Devo leggere tutte le sezioni
      ; Salvo i dati della sezione corrente

      mov eax,[esi].VirtualAddress
      mov [edi].virtualOffset, eax      ; Memorizzo il VirtualOffset
      mov eax,[esi].SizeOfRawData
      mov [edi].rawOffset, eax          ; Memorizzo il RawOffset
      mov eax,[esi].PointerToRawData
      mov [edi].pointerToRawData, eax   ; Memorizzo il PointerToRawData
      ; Aggiorno i puntatori per memorizzare i dati della prossima sezione

      add edi, sizeof SECTIONVALUES
      add esi, sizeof IMAGE_SECTION_HEADER
      dec ecx
   .ENDW

   assume esi:nothing
   assume edi:nothing

   ; Salvo l'indirizzo di partenza per la memorizzazione della prossima StrutturaFile

   pop esi
   mov [esi], edi  

  @done:
   popad

   ; Le istruzioni originali
   mov [esi+28h], edi
   mov [esi+2Ch], ebx


In questo caso abbiamo aggiunto qualche linea in più di codice ma alla fin fine si tratta soltanto di qualche semplice salvataggio! Vediamo in dettaglio i passi effettuati:
1. Controllo che sia stato salvato l'handle dell'MDI child appena creato. Questo controllo può sembrare -inizialmente- inutile ma è necessario perché quando un file viene salvato, HWS provvede nuovamente a mapparlo in memoria causando l'esecuzione di questo snippet. Se omettiamo questo controllo verrebbe creata una copia di questa StrutturaFile con la sola differenza del campo handle che resterebbe vuoto; infatti, al momento del salvataggio del file, non verrà creato nessun nuovo MDI child e di conseguenza non verrà salvato nessun handle.
2. Il salto in Virtual Address ha senso soltanto per file che hanno un PE header valido per cui eseguo un semplice controllo sul tipo di file aperto. Il classico metodo per controllare ciò consiste nel verificare se il file contiene (in posizioni ben precise) le due parole magiche: "MZ" e "PE". Per un file non-PE non salvo niente e dopo aver azzerato il campo handle esco dalla routine.
3. Parte finalmente il salvataggio dei dati. Quando ho salvato tutto aggiorno il puntatore all'indirizzo dove verrà salvata una prossima StrutturaFile.

Abbiamo terminato la parte relativa al salvataggio dei dati necessari alla conversione, adesso dobbiamo utilizzare questi dati per realizzare la conversione da VirtualAddress a Offset.

Implementazione della conversione da Virtual Address a Offset.
In questa parte del tutorial andremo ad aggiungere il codice necessario a:
- leggere il VA
- convertire il VA letto in offset
- passare l'offset ottenuto alla routine di salto presente in HWS

Come al solito dobbiamo trovare un buon punto d'attacco. Carichiamo un qualsiasi file (con PE header valido) e apriamo la dialog di GoTo. Dobbiamo intercettare la routine che gestisce la pressione del tasto "Go", come facciamo? Come Neural_Noise insegna, quando c'è un tasto "Help" è bene sfruttarlo! Mettiamo un breakpoint sulla funzione WinHelpA e premiamo questo tasto. Il vostro debugger popperà esattamente all'interno della routine che gestisce la visualizzazione della guida per cui uscite da questa routine e risalite al punto in cui viene chiamata:


004D77D0 mov ecx, [ebp+8]
004D77D3 call [ebp+14]          <--- chiamata della routine che gestisce la visualizzazione della guida
004D77D6 jmp short loc_4D7857   <--- noi siamo qui


Cosa c'entra questo con la routine che stiamo cercando? C'entra eccome! La call a 4D77D3 è parametrica, dipende infatti dal valore puntato da ebp+14; in casi come questo, spesso accade che la call si comporti come un "selettore", nel senso che tutte le routine chiamate dalla dialog GoTo passano per quella call. Controllare che sia effettivamente così è molto semplice; mettete un breakpoint sulla call, riaprite la dialog GoTo e saltate ad un offset a vostro piacere. Il debugger poppa, avevamo ragione!
In linea teorica, il programma leggerà l'offset che abbiamo inserito, effettuerà tutti i controlli del caso e in seguito si posizionerà sul nuovo offset.
Vi ricordo qual'è la nostra idea: leggere il Virtual Address, convertirlo in offset e scriverlo nel box relativo agli offset lasciando fare tutto il resto al programma. Affinché il tutto funzioni correttamente, dovrete selezionare 'Hex' e 'Beginning of File'.
Mi sembra ovvio che devieremo il programma qualche linea prima che Hex WorkShop legga l'offset; offset che presumibilmente verrà letto con funzioni tipo GetDlgItemText, GetWindowText o simili. Invece di steppare singolarmente tutte le linee di codice interne alla routine possiamo provare a mettere un breakpoint su queste funzioni e vedere cosa succede. Anche stavolta siamo fortunati:


004D49F5 push esi
004D49F6 mov esi, ecx
004D49F8 mov ecx, [esi+38h]
004D49FB test ecx, ecx
004D49FD jnz short loc_4D4A2D
004D49FF push dword ptr [esi+1Ch]   <------- hWnd: handle dell'edit box contenente l'offset
004D4A02 call ds:GetWindowTextLengthA   <--- legge il numero di caratteri presenti nel box di offset
004D4A08 lea ecx, [eax+1]
004D4A0B push ecx
004D4A0C mov ecx, [esp+4+arg_0]
004D4A10 push eax
004D4A11 call GetBufferSetLength
004D4A16 push eax   <----------------------- indirizzo del buffer destinato a contenere l'offset
004D4A17 push dword ptr [esi+1Ch]   <------- hWnd: handle dell'edit box contenente l'offset
004D4A1A call ds:GetWindowTextA   <--------- legge l'offset inserito


Bene, abbiamo trovato ciò che stavamo cercando. Apriamo di nuovo CSC e creiamo un nuovo progetto (FromVAtoOffset) con le seguenti proprietà:


- Snippet VA = 58E3C4
- Patch into Existing Section
- Redirect Control From Code Section: Virtual Address = 4D49FF
- Return To Program: Virtual Address = 4D4A08


Vediamo il codice e poi lo commentiamo.


include HWS4.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

jmp @start
fileStructure   DWORD   0058E005h
offsetValue     dd      ?           ; Offset ottenuto dopo la conversione
VA              db      9 dup(0)    ; Virtual Address letto dal box (se è stato inserito...)
format          db      "%lX", 0    ; Parametro della funzione wsprintf
gotoHandle      HANDLE  ?           ; Handle della dialog GoTo
mdiHandle       DWORD   58E30Dh     ; Handle dell'MDI client (il padre di tutti gli MDI child aperti)
errorMessage    db      "VA is not valid...";,0
notVAMessage    db      "Only offset for this file!",0

@start:
   pushad

   ; Recupero e salvo l'handle della dialog GoTo

   invoke GetParent, [esi+1Ch]
   mov [gotoHandle], eax

   ; Leggo (e controllo) il Virtual Address inserito

   invoke GetDlgItemText, [gotoHandle], 40000, offset VA, 9
   .IF eax == 0
      jmp @endConversion   ; Nessun Virtual Address è stato inserito e esco
   .ENDIF

   ; Recupero l'handle dell'MDI child con il focus

   mov edi, mdiHandle
   invoke SendMessage, [edi], WM_MDIGETACTIVE, NULL, NULL
   mov edi, eax

   ; Cerco la StrutturaFile relativa al file corrente; la chiave che
   ; utilizzo per la ricerca è l'handle dell'MDI child con il focus

   mov esi, FileStructure
   assume esi: ptr INFOFILE
   .WHILE edi != [esi].handleMDI       ; Controllo che il file sia presente nella lista. Non c'è se non ha un PE header
      .IF [esi].handleMDI == NULL      ; Il file non ha formato PE ma è stato inserito lo stesso un Virtual Address
         ; Stampo un semplice messaggio di errore

         invoke SetDlgItemText, [gotoHandle], 1008, offset notVAMessage
         invoke SetDlgItemText, [gotoHandle], 40000, NULL
         jmp @endConversion
      .ENDIF
      ; Mi sposto sulla prossima StrutturaFile

      movzx eax, [esi].nSections
      imul eax, sizeof SECTIONVALUES
      add eax, sizeof INFOFILE
      add esi, eax
   .ENDW

   ; Conversione del Virtual Address da ascii a dword

   xor eax, eax
   lea edi, dword ptr [VA]      ; esi punta al Virtual Address inserito
   .WHILE byte ptr [edi] != 0   ; Proseguo finchè non ho letto tutti i caratteri componenti il Virtual Address
      ; Converto il singolo carattere

      .IF (byte ptr [edi] > 60h && byte ptr [edi] < 67h)
         sub byte ptr [edi], 57h
      .ELSEIF (byte ptr [edi] > 40h && byte ptr [edi] < 47h)
         sub byte ptr [edi], 37h
      .ELSEIF (byte ptr [edi] > 2Fh && byte ptr [edi] < 3Ah)
         sub byte ptr [edi], 30h
      .ELSE
         jmp @error
      .ENDIF
      ; Lo aggiungo alla dword finale

      shl eax, 4
      add al, byte ptr [edi]
      inc edi
   .ENDW

   ; Inizia la conversione!

   mov edi, eax
   sub edi, [esi].imageBase   ; Relative Virtual Address = Virtual Address - ImageBase
   movzx ecx, [esi].nSections
   add esi, sizeof INFOFILE
   assume esi: ptr SECTIONVALUES
   ; Cerco la sezione in cui è contenuto l'RVA ed effettuo la conversione

   .WHILE ecx > 0
      mov eax, [esi].virtualOffset
      .IF edi >= eax                 ; Mi chiedo se: RVA >= Virtual Offset
         add eax, [esi].rawOffset   ; Virtual Offset + Size of Raw Data
         .IF edi < eax               ; Mi chiedo se: RVA < (Virtual Offset + Size of Raw Data)
            ; Ho trovato la sezione giusta!

            mov eax, [esi].virtualOffset
            sub edi, eax             ; RVA - Virtual Offset
            mov eax, [esi].pointerToRawData
            add eax, edi             ; eax = (RVA - Virtual Offset) + Raw Offset = offset
            jmp @after_convert
         .ENDIF
      .ENDIF

      ; La sezione corrente non è quella giusta. Mi sposto sulla prossima

      add esi, sizeof SECTIONVALUES
      dec ecx
   .ENDW

  @error:
   ; Arrivo qui se il Virtual Address inserito non è corretto.
   ; Stampo un semplice messaggio di errore nel box relativo all'Offset

   invoke SetDlgItemText, [gotoHandle], 1008, offset errorMessage
   jmp @endConversion

  @after_convert:
   ; Conversione dell'offset appena calcolato

   invoke wsprintfA, offset offsetValue, offset format, eax
   ; Stampo l'offset calcolato nel box relativo agli offset

   invoke SetDlgItemText, [gotoHandle], 1008, offset offsetValue

  @endConversion:
   popad

   ; Rispristino le istruzioni originali

   push dword ptr [esi+1Ch]
   call GetWindowTextLength


La conversione sarà eseguita soltanto se un Virtual Address è stato inserito perciò, come prima cosa, proviamo a leggere tale valore. Per leggere il Virtual Address ho utilizzato la funzione GetDlgItemText:


UINT GetDlgItemText(
     HWND   hDlg,       // handle del dialog box
     int    nIDDlgItem, // identificatore del controllo
     LPTSTR lpString,   // buffer dove sarà salvato il valore letto
     int    nMaxCount   // numero massimo di caratteri da leggere
);


Come potete vedere, non possiamo chiamare la funzione se prima non recuperiamo l'handle del dialog box. Ecco perché all'inizio della routine ho chiamato la funzione GetParent, la cui sintassi è:


HWND GetParent(
     HWND hWnd   // handle del controllo figlio
);


L'utilizzo della funzione è veramente banale, gli passiamo l'handle dell'edit box di Offset e lui ci restituisce l'handle della dialog box.
Se nessun Virtual Address è stato specificato usciamo dalla routine e il flusso del programma prosegue normalmente leggendo l'offset inserito. In caso contrario dobbiamo controllare che il Virtual Address inserito sia corretto e, in tal caso, convertirlo.
Se fate bene attenzione al codice noterete che ho aggiunto un ulteriore controllo per evitare che venga preso in considerazione un Virtual Address quando il file aperto non ha un PE-header valido.
Torniamo alla conversione vera e propria descrivendo i passi che ho effettuato:
1. Converto il Virtual Address in forma di stringa nel Virtual Address in forma di "DWORD". Questo passo poteva essere notevolmente semplificato usando una delle funzioni di conversione predefinite; ad esempio, per chi usa masm esiste la funzione htodw. Il problema è che questa funzione non controlla la correttezza dei caratteri e utilizzarla potrebbe portare ad avere errori indesiderati per cui è necessario scrivere una routine ad-hoc. Se è stato inserito qualche carattere non valido, la procedura termina con la segnalazione dell'errore.
2. Utilizzando la formula che abbiamo visto nel preambolo effettuo la conversione. Non sempre però! Se vi ricordate, nella formula iniziale avevamo imposto delle condizioni affinché la conversione potesse essere effettuata; in caso di fallimento sarà segnalato un semplice errore.
3. Ultimo passo: stampa dell'offset nell'edit box di offset. E' la parte conclusiva di questo snippet e consiste nel formattare e nello scrivere l'offset ottenuto nel giusto edit box. Per far ciò ho utilizzato la combinazione delle funzioni wsprintf/SetDlgItemText.


int wsprintf(
    LPTSTR  lpOut,   // puntatore al buffer che riceverà il valore formattato
    LPCTSTR lpFmt,   // puntatore alla stringa di definizione del formato
    ...              // argomenti opzionali
);


Il secondo parametro definisce il modo in cui volete che sia effettuata la formattazione. Nel caso specifico, noi abbiamo bisogno che il valore in output sia un esadecimale quindi ecco perché abbiamo utilizzato il valore %lX (la 'X' rende il valore maiuscolo e potete ometterla se volete). Per quanto riguarda la lista degli argomenti opzionali, noi abbiamo un solo argomento da specificare: il valore da convertire. Tutto qui...


BOOL SetDlgItemText(
    HWND    hDlg,       // handle del dialog box
    int     nIDDlgItem, // identificatore del controllo nel quale sarà inserito il testo
    LPCTSTR lpString    // testo da inserire nel controllo
);


Questa è in sostanza il duale rispetto alla ben nota funzione GetDlgItemText, inserisce il valore puntato dal terzo parametro nel controllo scelto (in questo caso un editbox).

Eliminazione di una struttura.
Ci resta un'ultimissima cosa da fare: eliminare una StrutturaFile quando un file viene chiuso.
Partiamo con la solita ricerca di un punto d'attacco. Fondamentalmente dobbiamo gestire due casi diversi: il file è stato modificato e il file non è stato modificato. Un modo molto semplice e veloce per trovare un buon punto d'attacco è il seguente: ogni volta che chiudete un file che avete modificato ma non salvato HexWS vi chiede se volete salvare le modifiche apportate mostrando un messagebox a scelta multipla: "Salvare le modifiche" e "Non salvare le modifiche"; sfruttiamo questo box! Aprite un qualsiasi file, cambiate almeno un byte, mettete un breakpoint sulla funzione MessageBox e provate a chiudere il file. Il vostro debugger brecka; uscite dalla routine che visualizza il box e steppate per qualche linea, dovreste trovarvi di fronte al seguente codice:


004DC64A dec eax    <-------------------- eax rappresenta la scelta fatta: 7 non salvate le modifiche e 6 le salvate
004DC64B dec eax
004DC64C jz short loc_4DC666
004DC64E sub eax, 4
004DC651 jnz short loc_4DC661    <------- salto se ho deciso di non salvare le modifiche
004DC653 mov eax, [edi]
004DC655 mov ecx, edi
004DC657 call dword ptr [eax+9Ch]    <--- salva le modifiche apportate
004DC65D test eax, eax    <-------------- eax = 1 se tutto è andato bene
004DC65F jz short loc_4DC666
004DC661 push 1    <--------------------- in entrambi i casi arrivo qui
004DC663 pop esi
004DC664 jmp short loc_4DC668
004DC666 xor esi, esi


4DC661 sembra un buon punto per deviare il normale flusso del programma perché è eseguito in entrambi i casi. Purtroppo però non basta, stavolta i punti da patchare saranno due perché dobbiamo considerare il caso in cui il file da chiudere non sia stato modificato. Come facciamo a trovare l'altro punto d'attacco? La miglior cosa da fare è quella di mettere un breakpoint all'inizio della routine che contiene il codice qua sopra e vedere cosa succede. Mettiamo un breakpoint a 4DC591 e chiudiamo un file non modificato:


004DC5A2 call dword ptr [eax+58h]
004DC5A5 test eax, eax
004DC5A7 jnz short loc_4DC5B1    <--- se il file è stato modificato viene eseguito il salto
004DC5A9 push 1    <----------------- se il file non è stato modificato arrivo qui
004DC5AB pop eax
004DC5AC jmp loc_4DC683
004DC5B1 mov eax, dword_550BCC


Siamo riusciti a trovare anche il secondo punto: 4DC5A9. Giacché dobbiamo modificare il codice in due punti diversi abbiamo due possibilità: scrivere due snippet identici con proprietà diverse oppure fare tutto con un unico snippet. Ho deciso di optare per la seconda possibilità quindi aggiungeremo soltanto una routine. Definiamo le proprietà di quest'ultimo progetto (RemoveStrutturaFile):


- Snippet VA = 58E59A
- Patch into Existing Section
- Redirect Control From Code Section: Virtual Address = 4DC5A9
- Don't Return Control To Program


Stavolta saremo noi a gestire il ritorno al programma originale perché dobbiamo differenziare i due casi.


include HWS4.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

jmp @start

fileStructure         DWORD   0058E005h
nextFreeFileStructure DWORD   0058E309h    ; Indirizzo dove memorizzare la prossima strutturaFile
mdiHandle             DWORD   0058E30Dh    ; Handle dell'MDI client (il padre di tutte gli MDI child aperti)

@start:
  @firstPlace:          ; Arrivo qui da 4DC5A9
   ; Le seguenti due istruzioni sono del codice originale

   push 1
   pop eax
   push 004DC683h    ; Pusho l'indirizzo per la gestione del ritorno ad Hex WorkShop
   jmp @sameSnippet

  @secondPlace:         ; Arrivo qui da 4DC661, chiudo il file modificato (salvato o non salvato)
   ; Le seguenti due istruzioni sono del codice originale

   push 1
   pop esi
   push 004DC668h    ; Pusho l'indirizzo per la gestione del ritorno ad Hex WorkShop

  @sameSnippet:
   pushad
   mov edi, fileStructure
   assume edi: ptr INFOFILE

   ; Recupero l'handle dell'MDI child che verrà chiuso

   mov esi, mdiHandle
   invoke SendMessage, dword ptr [esi], WM_MDIGETACTIVE, NULL, NULL
   mov esi, eax      ; esi = handle dell'MDI child relativo al file che verrà chiuso

   ; Cerco la StrutturaFile da eliminare

   .WHILE esi != [edi].handleMDI
      ; La StrutturaFile relativa al file da chiudere potrebbe non essere stata salvata, controllo...

      .IF [edi].handleMDI == NULL
         jmp @done
      .ENDIF
      ; Mi sposto sulla prossima struttura

      movzx eax, [edi].nSections
      imul eax, sizeof SECTIONVALUES
      add eax, sizeof INFOFILE
      add edi, eax
   .ENDW

   ; Calcolo la dimensione della struttura da eliminare

   movzx esi, [edi].nSections
   imul esi, sizeof SECTIONVALUES
   add esi, sizeof INFOFILE
   push esi        ; Pusho la dimensione della StrutturaFile da eliminare
   add esi, edi    ; esi -> StrutturaFile successiva (se c'è) a quella da eliminare
   push esi        ; Pusho il puntatore alla StrutturaFile successiva quella da eliminare

   ; Calcolo il numero dei byte delle StrutturaFile successive. Il valore è salvato in ecx

   xor ecx, ecx
   assume esi: ptr INFOFILE
   .WHILE [esi].handleMDI != NULL
      movzx eax, [esi].nSections
      imul eax, sizeof SECTIONVALUES
      add eax, sizeof INFOFILE
      add esi, eax
      add ecx, eax
   .ENDW
   pop esi

   ; Controllo se la StrutturaFile da rimuovere è l'ultima salvata

   .IF ecx != 0       ; Non è l'ultima quindi devo spostare indietro tutte le strutture che seguono quella da eliminare
     @shiftBack:
      lodsb
      stosb
      loop @shiftBack
   .ENDIF

   ; Aggiorno il puntatore nextFreeFileStructure

   mov esi, dword ptr [nextFreeFileStructure]
   mov dword ptr [esi], edi

   ; Adesso pulisco i byte finali. Sono tanti quanti quelli della StrutturaFile eliminata

   pop ecx
  @zeroLastStructure:
   mov byte ptr [edi+ecx-1], 0
   loop @zeroLastStructure

  @done:
   popad
   retn


Inizialmente gestisco i due diversi punti d'attacco e, diversamente dal solito, ripristino immediatamente le istruzioni originali del programma; ho deciso di farlo subito solo per una questione di comodità. Oltre a questo pusho anche l'indirizzo di ritorno.
Dopo questa prima parte passo a rimuovere la StrutturaFile. Anche in questo caso, basando tutto il lavoro sui vari handle in gioco, devo prima di tutto recuperare l'handle dell'MDI child con il focus; questo passo è necessario per identificare la giusta StrutturaFile da eliminare. L'eliminazione della StrutturaFile avviene sovra scrivendola con i dati delle strutture che la seguono; se è l'ultima della lista provvedo a coprirla di byte 00. Ricordatevi di aggiornare la variabile nextFreeFileStructure mettendo il nuovo indirizzo altrimenti sarà un bel casino!!!
Ancora un'ultima cosa e abbiamo finito; c'è da modificare l'istruzione a 4DC661 nella nuova "jmp 0058E5B2", vi ricordate il perché vero?. 58E5B2 è l'indirizzo di "@secondPlace". Per cambiare i byte utilizzeremo un qualsiasi editor esadecimale; dall'offset DC661h mettete i seguenti byte: E9 4C 1F 0B 00.

Bug (s)conosciuti.
Ho testato questa nuova versione su una macchina con w98se e sembra non avere bug. Se trovate uno (o più...) bug contattatemi pure senza problemi che li sistemiamo per bene!

Postambolo: conclusioni.
Abbiamo finito, mi sembra di aver detto tutto e (spero) senza errori. Il lavoro non è stato semplice e immediato come il reversing del win32dasm ma ne è valsa la pena! Ho cercato di fornirvi una soluzione il più generale possibile in modo che possiate personalizzare il tutto come meglio volete.
In allegato trovate i 4 progetti completi.


bona!

                                          ZaiRoN

Note finali


Un doveroso saluto va al Que che finalmente ha ripreso con la UIC. Contribuiamo ragazzi!
Saluto tutti gli amici di #crack-it! Alla prossima!!!

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... che fissa!