italy 19-o5-2oo3
Hex WorkShop Reversing
We wil "jump" in Virtual Address
 
written by ZaiRoN

 
Introduction:
Hex WorkShop is a nice hexadecimal editor but it may be improved! The program lets you to jump from a byte to another, specifying the new offset where you want to land. For our job it's often useful to jump using a Virtual Address...
 
Literature:
A PE guide
 
Target:
Hex WorkShop v4.00
http://www.bpsoft.com/downloads/
 
Program description:
Don't you know this program? I can't believe you :-p
 
Tools:
- a debugger (Ollydbg)
- a disassembler (Ida)
- a resource editor (Resource Hacker)
- Code Snippet Creator
music: Frank Zappa - Hot Rats

 
Before starting I would like to say two things:
1. I will try to give you the most general solution so you can personalize it as you like.
2. I made this reversing session some months ago when the new version (4.10) was not out yet; I discovered the new version only after the tutorial was written. I looked at the new version, I have not modified this new version but, from what I have seen, you should be able to follow the guidelines I will give you; the addresses won't be the same but it should work. Sorry but I am too busy (and also lazy :-p) and I can't check more deeply...
 
We will start from something that, initially, you could find not related to this program but that without it we won't do anything.
 
Preamble: conversion from Virtual Address to Offset.
This conversion is quite easy to apply and it's only a simple formula:
 
Offset = Relative Virtual Address - Virtual Offset + Raw Offset
where: 
- Relative Virtual Address (RVA) =  relative address to the image base:
 
Relative Virtual Address = Virtual Address - Image Base
 
Afterwards I will refer to it simply using RVA.
- Virtual Address (VA) = the value to be converted. It's the classic memory address in the form "401F69".
- Image Base = address from which the file is being mapped into memory.
- Virtual Offset = address from which the section is being mapped into memory.
- Raw Offset = physical address of the beginning of the section inside the file.
The last two values, Virtual Offset and Raw Offset, are relative to a specific section but, in general, a file contains more than a single section so, how do we know which section to choose? We will choose the section which contains our Virtual Address; in particular, this formula must be verified:
Virtual Offset =< RVA < (Virtual Offset + Virtual Size)
 
The conversion can be done if and only if there is a section whose values satisfies this simple expression. It's so easy? Yes...
 
A real example and then, we will start with the tutorial.
You suppose to have a file with Image Base = 400000h and sections:
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
You want to convert the VirtualAddress = 4030A2h in the relative offset.

1. Conversion in RVA:  RVA = Virtual Address - Image Base = 4030A2h - 400000h = 30A2h
2. Check: is the value in the section .text:
   30A2h >= 1000h   <----------- YES
   30A2h < (1000h + 342h)   <--- NO!!!
3. Check: is the value in the section .rdata:
   30A2h >= 2000h   <----------- YES
   30A2h < (2000h + 174h) <----- NO!!!
4. Check: is the value in the section .data:
   30A2h >= 3000h   <----------- YES
   30A2h < (3000h+164h)   <----- YES
5. You have found the right section, the Virtual Address verifies the expression and you can use the final formula to obtain the offset value: 
Offset = 30A2h - 3000h + A00h = AA2h
 
The hexadecimal value AA2h is the offset you are looking for; these are only simple steps to be mechanically applied! 
 
Strategy.
This new version of Hex WorkShop will have to be able to jump to the byte pointed by the specified Virtual Address. Obviously, this does not mean that we have to write the entire jump routine because Hex WorkShop is already able to jump from byte to another; infact, it lets you jump specifying an offset. We will work with Virtual Address which -as we saw before- we can easily convert into Offset so, why we should write a new jump routine?
 
Here is what we have to do: to read the VA, to convert into Offset and to jump to the new byte.
First of all, to read the VA we should find a dialog in which create a new control; this control will be used to insert the VA. We will add a new edit box directly into 'GoTo' dialog and we will read the inserted VA when the 'Go' button is been pressed.
The conversion from Va to Offset is the most delicate step, we need to pay attention. To convert a VA to a Offset it's necessary know some file's informations and, since Hex Workshop lets you open (simultaneously) more than a single file we need to answer these questions: how can we identify the file on which we are working and, how can we recover all the values necessary to the conversione? The simplest and logical way is certainly to retrieve the information (of the file) when the button, labelled 'Go', is pressed; then, with all the datas we should perform the conversion. Unfortunately, I already have tried to follow this way with the previous version of the program without luck. Being unable to solve the problem in this way, I decided to take another way for this new version of the program; the solution is not really elegant but seems to work pretty well. Also this is "reversing" at the end!
Every time a new file is opened, HexWS displays the content of the file inside a MDI_child window. All the work we will do will be based on the handles of these MDI windows containing the opened files! The idea is to save the handle of the MDI_child created, and all the data needed for the conversion, during the phase of opening of the file; one structure for each file. We will have as many structures as many open files (with valid PE header!) and the handle of the MDI_child window will be the key to recognise a specific structure. When we will have to perform the conversion we will only have to retrieve the handle of the focused MDI_child and look for that handle through the various structures saved until that moment.
The simplest part of the tutorial will be the last. How we will see sooner, to pass the Offset to the jump routine will be the last of our problems!
 
The reversing of the program is conceptually all here. Make a copy of the exe file; we are ready to start with the tutorial.
 
We modify the 'Goto' dialog.
We start from the easiest part, the modification of the 'Goto' dialog, the one used to personalize the jump. What we will add? The only needed thing is the edit box but to make the new dialog more cool we will add a static control (a label...). To modify the dialog I have used Resource Hacker but you can use your preferred resource editor. You load Hex WorkShop with your editor and look for, inside 'DIALOG', the 'Goto' dialog. Well, it's the number 112. You modify as you prefer, compile and save the new file; here is my new dialog:
 
  
 
You have to give an ID to the new 2 controls; the ID is necessary for identifying the controls and for reading the data they will contain. Indeed, you can give the ID only to the edit control, we won't use anymore the static control. I put ID=40000 to the edit control; you can put the number you prefer but pay attention because it's better to use a number never used before by other controls.
 
How we save the data.
To perform the conversation we need various values from the PE-header of a opened file. Considering what I told you before, we need to save these values from some part when the file is opened. Before seeing where we will save these data, we see what we need to save:
- MDI child handle of the generic opened file
- Image Base
- Number of sections inside the file
- Virtual Offset, Raw Offset and PointerToRawData (for each section!!!)
 
To save the number of section is not really necessary but it's useful for the implementation I will use. We will save all the data in the easiest way: sequentially. For each file we will use two structures:
 
INFOFILE STRUCT
    handleMDI   DWORD ?   ; MDI child handle
    imageBase   DWORD ?   ; Image Base
    nSections   WORD  ?   ; Number of sections
INFOFILE ENDS
 
SECTIONVALUES STRUCT
    virtualOffset      DWORD ?   ; Virtual Offset
    rawOffset          DWORD ?   ; Raw Offset
    pointerToRawData   DWORD ?   ; Pointer to Raw Data
SECTIONVALUES ENDS
 
The meaning of these two structures is intuitive. SectionValues structure will contain the data of a single section; for each file, we will use "nSections" structures of this type. 
From now on, with the 'FileStructure' term I will refer to the set of the saved (or to save) data concerning a single file.
 
We start saving the data.
Unfortunately we can't save all the FileStructure's data at the same time, so we will split this process into two distinct parts: first of all, we will see how to save MDI_child handle and then we will see how to save all the other data.
 
Each child window is created sending the WM_MDICREATE message by the function SendMessage; the function has this syntax:
 
LRESULT SendMessage(
    HWND     hWnd,     // handle of destination window
    UINT     Msg,      // message to send: WM_MDICREATE
    WPARAM   wParam,   // first message parameter: not used, it's setted to 0
    LPARAM   lParam    //second message parameter: pointer to MDICREATESTRUCT structure, the definitionof thhe MDI child
);
 
The function returns the handle of the window that is created, the value we want! We will find where the message is sent putting a conditional breakpoint on SendMessage or using IDA. In both cases you will be able to reach this point:
 
004ED198 mov ebx, ds:SendMessageA 
004ED19E lea eax, [ebp+lParam] 
004ED1A1 push eax   <-------------------- lParam: pointer to MDICREATESTRUCT 
004ED1A2 push 0   <---------------------- wParam: 0 
004ED1A4 push WM_MDICREATE   <----------- the message 
004ED1A9 push dword ptr [esi+0BCh]   <--- hWnd: handle 'father' of all MDI_child 
004ED1AF call ebx   <-------------------- SendMessageA 
004ED1B1 mov [ebp+hWnd], eax   <--------- eax = handle of the child window that is been created
004ED1B4 call AfxUnhookWindowCreate 
004ED1B9 test eax, eax 
004ED1BB jnz short loc_4ED1C7 
We have found the handle we were looking for; now, we need to find where to put the jump to our personal new snippet, snippet that will let us to save the handle. Address 4ED1B1 represents a good point. To add new code to the program I will use Code Snippet Creator.
Run CSC and create a new project (SaveHandle); these are the properties of the project:
 
- Patch as New Section
- Redirect Control From Code Section: Virtual Address = 004ED1B1
- Return To Program: Virtual Address = 004Ed1B9
 
How I often do, the new code will be added in a new section. To jump to our new code I will modify the instruction at 4ED1B1. The way to return to the original program will be managed by CSC but we need to remember that instructions at 4ED1B1 and 4ED1B4 will be changed by CSC (to perform the jump) so we should put them into the snippet.
The snippet:

include HWS4.inc    ; This file contains constants definition and various structures 

jmp @start 
fileStructure         BYTE   300h dup(0)     ; Address from where we will start to save all the FileStructure 
AfxUnhookWindowCreate DWORD  004D44FDh       ; Address of the function overwritten by CSC's patch
nextFreeFileStructure DWORD  fileStructure   ; Address where to insert the next FileStructure 
mdiHandle             HANDLE ?               ; Handle of MDI_client (the father of all the future child window...) 

@start: 
    pushad   ; Save all! 

    ; Save the handle of the MDI client (you will understand later the reason why...) 
    push [esi+0BCh]    ; I know it's [esi+BCh] because it's one of the parameters of the previous SendMessageA 
    pop [mdiHandle]    ; I save, into the current FileStructure, the handle of the window that is been created 
    mov esi, [nextFreeFileStructure] 
    mov [esi], eax 

    popad    ; Restore all! 

    ; Two original instruction overwritten by CSC'patch 
    mov dword ptr [ebp+18h], eax 
    call AfxUnhookWindowCreate 

Compile and patch HexWS, CSC will ask you to set the characteristics of the new section; the most common characteristics are already setted. Ah, remember to give a name to the section!
This snippet contains two simple rescues but before passing to the next rescues I would like to comment the snippet. In particular, first of all, the reserved 0x300h bytes for saving FileStructures. These bytes are statically allocated at the beginning of the snippet. It's surely a non elegant way for solving the problem; since we do not know the number of files that will be opened and the number of the bytes used by each structure, the logical thing to do is to reserve the space into memory in dynamic way using function like HeapAlloc for example. Reading some messages all over the net, I saw that type of functions are in some way OS dependent; so I decided to use a static allocation. Maybe 0x300h bytes are too much, you personalize it as you want!
I would like to spend some words for the meaning of nexFreeFileStructure. Every time a new file is opened, we should move through all the FileStructure structures saved before and, to save the new FileStructure after all the structures. We use a DWORD (nextFreeFileStructure) to avoid this boring movement and to save the starting address of a new hypothetical FileStructure.
 
Go on with the rest of the saving. The others data to save are all inside the file so we will go to read them after the file is mapped into memory. HexWS uses the function MapViewOfFile to map the file; put a breakpoint onto this function and when the debugger will break we will see:
 
00416A25 push eax 
00416A26 push 4   <-------------------------- dwDesiredAccess: FILE_MAP_READ, read only access to the maped object 
00416A28 push [ebp+hFileMappingObject]   <--- hFileMappingObject: handle of the mapping object (returned by CreateFileMapping) 
00416A2B call ds:MapViewOfFile   <----------- MapViewOfFile: return the starting address of the mapped view 
00416A31 test eax, eax   <------------------- is the file mapped correctly? 
00416A33 mov [esi+14h], eax   <-------------- save the address returned by the function 
00416A36 jz short loc_416A40   <------------- jump if an error occurred in the mapping phase 
00416A38 mov [esi+28h], edi 
00416A3B mov [esi+2Ch], ebx 
00416A3E jmp short loc_416A5B
 
As you can see we have the call of the function followed by the check above the returned value. If all it's ok, at 416A38 eax points to the address where the file has been mapped in memory. Very good, I say that 416A38 is a good point to jump to the new code.
Using CSC we create the second project (SaveValuesForConvertion); these are the properties:
 
- Snippet VA = 0058E33D
- Patch into Existing Section
- Redirect Control From Code Section: Virtual Address = 00416A38
- Return To Program: Virtual Address = 00416A5B
 
The next snippet will be inserted in the section created before and the address of the first instruction will be at 58E33D; why exactly this address? This is the address of the next byte after the previous snippet. Obviously, you can put the code wherever you like :-)
CSC will manage the return to the program.

include HWS4.inc 

jmp @start 
currentStrutturaFile DWORD 0058E309h   ; Initial address of the current FileStructure (contains only the handle) 

@start: 
   pushad 

   ; In order to save the next information I must have first of all saved the handle 
   mov edi, [currentStrutturaFile] 
   mov edi, [edi] 
   .IF dword ptr [edi] == 0 
      jmp @done 
   .ENDIF 

   mov esi, eax   ; Memory addres from where the file is mapped 
   assume esi:ptr IMAGE_DOS_HEADER 
   ; Has the file a valid PE header 
   .IF [esi].e_magic == IMAGE_DOS_SIGNATURE       ; Is "MZ" in the file? 
      add esi, [esi].e_lfanew 
      assume esi:ptr IMAGE_NT_HEADERS 
      .IF [esi].Signature == IMAGE_NT_SIGNATURE   ; Is "PE" in the file? 
         jmp @PE                                  ; A valid PE header is in the file! 
      .ENDIF 
   .ENDIF 

   ; The file does not have a valid PE header, I remove 
   ; the handle saved before, I don't save anything else. 
   mov dword ptr [edi], 0 
   jmp @done 

  @PE:    ; Saves all the necessary data 
   push currentStrutturaFile 
   assume edi: ptr INFOFILE 
   movzx ecx, [esi].FileHeader.NumberOfSections 
   mov [edi].nSections, cx    ; Saves the number of sections of the file 
   mov eax, [esi].OptionalHeader.ImageBase 
   mov [edi].imageBase, eax   ; Saves ImageBase 
   add edi, sizeof INFOFILE 
   assume edi: ptr SECTIONVALUES 
   add esi, sizeof IMAGE_NT_HEADERS     ; PE + F8 = beginning of SectionHeaders 
   assume esi:ptr IMAGE_SECTION_HEADER 
   .WHILE ecx>0                         ; I have to read all the sections 
      ; Saves the data of the current section 
      mov eax,[esi].VirtualAddress 
      mov [edi].virtualOffset, eax      ; Saves VirtualOffset 
      mov eax,[esi].SizeOfRawData 
      mov [edi].rawOffset, eax          ; Saves RawOffset 
      mov eax,[esi].PointerToRawData 
      mov [edi].pointerToRawData, eax   ; Saves PointerToRawData 
      ; Updates the pointers used to save the data of the next section 
      add edi, sizeof SECTIONVALUES 
      add esi, sizeof IMAGE_SECTION_HEADER 
      dec ecx 
   .ENDW 

   assume esi:nothing 
   assume edi:nothing 

   ; Saves the starting address used to save the next FileStructure 
   pop esi 
   mov [esi], edi   

  @done: 
   popad 

   ; The original instructions 
   mov [esi+28h], edi 
   mov [esi+2Ch], ebx

In this case we have added some more lines but at the end it's only a simple saving! We see in detail the made steps:
1. I check if the MDI_child handle of the opened file has been saved. This check may seem -initially- useless but it's necessary. When a file is saved, HexWS re-map the file in memory causing the execution of this snippet. If we omit this control, a copy of this FileStructure would be created with the only difference of the handle field which would stay empty; infact, when the file is saved, no new MDI child will be created and the result is that a new handle won't be saved.
2. The new option has sense only for a file with a valid PE header so I use a simple check onto the type of the file. The classical method is looking for two magic words: "MZ" and "PE". For an not PE file, I do not save nothing, I only remove the handle saved before.
3. Finally, I save the data. When all the need data are saved I update the pointer to the address where the next FileStructure will be saved.
 
We have done this part of the tutorial where the necessary data useful to perform the conversion are saved, now we have to used this data to perform the conversion from VirtualAddress to Offset.
 
Implementation of the conversion from Virtual Address to Offset.
In this part of the tutorial we will add the code necessary to:
- read the VA
- convert the readed VA in Offset
- pass the obtained Offset to the HWS's jump routine
 
As usual, we need to find a good attack point. Load a file (with a valid PE header) and open the 'Goto' dialog. We need to intercept the routine that manage the pressure of the 'Go' button, how can we do? How Neural_Noise teachs, when a 'Help' button is present it's good to use it! Put a breakpoint on the function WinHelpA and hit that button. Your debugger will pop exactly inside the routine that manage the visualization of the guide so, you exit from this routine and jump back to the point where the function is called:
 
004D77D0 mov ecx, [ebp+8] 
004D77D3 call [ebp+14]   <---------- call to the routine that manage the visualization of the guide 
004D77D6 jmp short loc_4D7857   <--- we are here!
 
Is this code related with the routine we are looking for? Surely! The call at 4D77D3 it's parametric, it depends on the value pointed by ebp+14; in this case, this call is like a "selector", all the routines called by the 'Goto' dialog come from that call. To check if it's really like this is very simple; put a breakpoint on this call, open the 'Goto' dialog and jump to a new offset. The debugger will pop, we were right!              
Theoretically, the program will read the offset you have inserted, will do all the necessary checks and then will move the cursor on the new offset. 
I remember to you our idea: to read the Virtual address, to convert into offset and to write it in the box relative to the offset; we will let the program do all the rest. This method will work if and only if 'Hex' and 'Beginning of File' are signed! 
Obviously, some lines before HexWS reads the offset, we will deviate the normal flow of the program; offset that (I suppose) will be read with one of the functions like GetDlgItemText, GetWindowText . I am too lazy to step all the instruction inside the routine, I will try to put a breakpoint on these functions and I will see what will happen. Wow, we are lucky:
 
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 of the edit box containing the offset 
004D4A02 call ds:GetWindowTextLengthA   <--- reads the number of characters in the offset's edit box 
004D4A08 lea ecx, [eax+1] 
004D4A0B push ecx 
004D4A0C mov ecx, [esp+4+arg_0] 
004D4A10 push eax 
004D4A11 call GetBufferSetLength 
004D4A16 push eax   <----------------------- address of the buffer which will contain the offset 
004D4A17 push dword ptr [esi+1Ch]   <------- hWnd: handle of the edit box containing the offset 
004D4A1A call ds:GetWindowTextA   <--------- reads the offset inserted 
 
Well, we have found what we were looking for. We open again CSC and we create a new project (FromVAtoOffset) with these properties:
 
- Snippet VA = 58E3C4
- Patch into Existing Section
- Redirect Control From Code Section: Virtual Address = 4D49FF
- Return To Program: Virtual Address = 4D4A08
 
We see the code and then we will comment it:

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 obtained by the conversion 
VA              db      9 dup(0)    ; Virtual Address readed from the box (if it's been inserted...) 
format          db      "%lX", 0    ; One of the parameters of the function wsprintf 
gotoHandle      HANDLE  ?           ; Handle of the 'GoTo' dialog 
mdiHandle       DWORD   58E30Dh     ; Handle of the MDI_client (the father of all the MDI child opened) 
errorMessage    db      "VA is not valid...";,0 
notVAMessage    db      "Only offset for this file!",0 

@start: 
   pushad 

   ; Recover and save the handle of the 'Goto' dialog 
   invoke GetParent, [esi+1Ch] 
   mov [gotoHandle], eax 

   ; Read (and check) the Virtual Address inserted 
   invoke GetDlgItemText, [gotoHandle], 40000, offset VA, 9 
   .IF eax == 0 
      jmp @endConversion   ; No Virtual Address is been inserted. Quit... 
   .ENDIF 

   ; Recover the MDI child's handle that has the focus 
   mov edi, mdiHandle 
   invoke SendMessage, [edi], WM_MDIGETACTIVE, NULL, NULL 
   mov edi, eax 

   ; I look for the FileStructure relative of the current file; the key 
   ; used for the search is the MDI child's handle with the focus 
   mov esi, FileStructure 
   assume esi: ptr INFOFILE 
   .WHILE edi != [esi].handleMDI       ; Check if the file is in the list. The file is not in the list if it has not a PE header 
      .IF [esi].handleMDI == NULL      ; The file has not a PE header but a Virtual Address was inserted in the box 
         ; I print a simple error message 
         invoke SetDlgItemText, [gotoHandle], 1008, offset notVAMessage 
         invoke SetDlgItemText, [gotoHandle], 40000, NULL 
         jmp @endConversion 
      .ENDIF 
      ; Move on the next FileStructure 
      movzx eax, [esi].nSections 
      imul eax, sizeof SECTIONVALUES 
      add eax, sizeof INFOFILE 
      add esi, eax 
   .ENDW 

   ; Conversion of the Virtual Address from ascii to dword 
   xor eax, eax 
   lea edi, dword ptr [VA]      ; esi points to the Virtual Address inserted 
   .WHILE byte ptr [edi] != 0   ; Go on until I have not read all the characters of the Virtual Address 
      ; I convert the single character 
      .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 
      ; I add it at the final dword 
      shl eax, 4 
      add al, byte ptr [edi] 
      inc edi 
   .ENDW 

   ; The conversion begin! 
   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 
   ; I look for the section where the RVA is and I perform the conversion 
   .WHILE ecx > 0 
      mov eax, [esi].virtualOffset 
      .IF edi >= eax                 ; Is (RVA >= Virtual Offset) ?
         add eax, [esi].rawOffset   ; Virtual Offset + Size of Raw Data 
         .IF edi < eax               ; Is (RVA < (Virtual Offset + Size of Raw Data)) ? 
            ; I have found the right section! 
            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 

      ; The current section is not the right one. I move on the next 
      add esi, sizeof SECTIONVALUES 
      dec ecx 
   .ENDW 

  @error: 
   ; I arrive here if the inserted Virtual Address is not correct. 
   ; I print a simple error message in the Offset box 
   invoke SetDlgItemText, [gotoHandle], 1008, offset errorMessage 
   jmp @endConversion 

  @after_convert: 
   ; Format the offset 
   invoke wsprintfA, offset offsetValue, offset format, eax 
   ; Print the converted offset in the right box 
   invoke SetDlgItemText, [gotoHandle], 1008, offset offsetValue 

  @endConversion: 
   popad 

   ; The original instructions 
   push dword ptr [esi+1Ch] 
   call GetWindowTextLength
 

The conversion will execute if a Virtual Addres has been inserted, so, first of all, we try to read the value. To read the Virtual Address I use the GetDlgItemText function:
 
UINT GetDlgItemText( 
     HWND   hDlg,         // handle of dialog box 
     int    nIDDlgItem,   // identifier of control 
     LPTSTR lpString,     // address of buffer for text 
     int    nMaxCount     // maximum size of string 
);
 
As you can see, we can't call the function if we don't have the handle of the dialog box. Now you can understand the reason why, in the beginning of the routine, I called the function GetParent; here is the syntax of that function:
 
HWND GetParent( 
     HWND hWnd   // handle of the child control 
); 
 
Very simple, we pass the handle of the Offset's edit box and the function returns the handle of the dialog box.
If no Virtual Address is specified we exit from the routine and the flow of the program will run normally, reading offset inserted. Otherwise we have to check whether the Virtual Address inserted is correct and, in this case, to convert it.
If you take attention to the code you will notice that I added a further check to avoid that a Virtual Address is read when the opened file has not a valid PE header.
It's time to describe the passes used to perform the conversion:
1. I convert the Virtual Address in string form into the Virtual Address in *DWORD* form. This task may be simplify using one of the most common conversion functions; as an example, for those who uses masm, they can use the function htodw. The problem regarding the use of this function is related to the fact that it does not control the correctness of the characters and to use it may bring us to have error (i.e.: 40697G is not valid!). I decide to write it ad-hoc. If a non valid character has been inserted, the routine finishes showing an error message.  
2. Using the formula we have seen in the preamble, I make the conversion. Not always however! If you remember, in the initial formula we have imposed some conditions about the conversion; if one of these conditions is not verified, an error message will be showed.
3. The last task: print the converted offset on his edit box. It's the conclusive part of this snippet; it consists in the formatting of the obtained offset and in the writing of it in the right edit box. To do the job I used two functions: wsprintf and SetDlgItemText.
 
int wsprintf( 
    LPTSTR  lpOut,   // pointer to buffer for output 
    LPCTSTR lpFmt,   // pointer to format control string 
    ...              // optional arguments 
); 
 
The second parameter defines the way in which you want that the formatting is made. In this specific case, the output value should be a hexadecimal number so, this is the reason why I have used the value %lX ('X' stands for uppercase, you can omit it). Optional arguments: we have only one parameter to specify: the value we want to convert. That is all...
 
BOOL SetDlgItemText( 
    HWND    hDlg,         // handle of dialog box 
    int     nIDDlgItem,   // identifier of control 
    LPCTSTR lpString      // text to set 
); 
 
This function is the dual function of GetDlgItemText, inserts the value pointed by the third parameter in the chosen control (in this specific case an edit box).
 
Elimination of a structure.
We have to do the last thing: when we close a file, we have to eliminate a FileStrutture.
We start with the search of a good attack point. Fundamentally we have to manage two different cases: the file has been modified and the file has not been modified. 
A quick way for finding a good attack point is: every time you close a modified file that has not been saved, HexWS ask you whether you want to save it, showing a messagebox with multiple choice: save and do not save; we use this box! You open a file, change at least one byte, put a breakpoint on MessageBox and try to close the file. Your debugger will break; you exit from the routine that display the box and step some lines, you should be able to reach:
 
004DC64A dec eax   <-------------------- eax represents your choice: 7 you does not save the file, 6 otherwise 
004DC64B dec eax 
004DC64C jz short loc_4DC666 
004DC64E sub eax, 4 
004DC651 jnz short loc_4DC661   <------- jump if the file won't be saved 
004DC653 mov eax, [edi] 
004DC655 mov ecx, edi 
004DC657 call dword ptr [eax+9Ch]   <--- save the file
004DC65D test eax, eax   <-------------- eax = 1 if everything it's ok
004DC65F jz short loc_4DC666 
004DC661 push 1   <--------------------- in both cases I will arrive here 
004DC663 pop esi 
004DC664 jmp short loc_4DC668 
004DC666 xor esi, esi
 
4DC661 seems to me a good point for changing the flow of the program because is executed in both cases. Unfortunately we need to take care about another attack point; the program can be closed even if it's not modified. What can we do to find this new attack point? The better thing to do is to put a breakpoint at the beginning of the routine containing the code above and to see what happens. Put a breakpoint on 4DC591 and close a non modified file:
 
004DC5A2 call dword ptr [eax+58h] 
004DC5A5 test eax, eax 
004DC5A7 jnz short loc_4DC5B1   <--- jump if the file has been modified 
004DC5A9 push 1   <----------------- here if the file has not been modified 
004DC5AB pop eax 
004DC5AC jmp loc_4DC683 
004DC5B1 mov eax, dword_550BCC
 
Well, we have found the second point: 4DC5A9. We have to modify the program in two distinct points and, we have two choices: to write two identically snippets with different properties or put all the code into only one snippet. I choose the second option so, I will add only one routine. These are the properties for the last project (RemoveStrutturaFile):
- Snippet VA = 58E69A
- Patch into Existing Section
- Redirect Control From Code Section: Virtual Address = 4DC5A9
- Fon't Return Control To Program
 
This time we manage the return because we must differentiate two cases.

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   ; Address where the next FileStructure will be saved 
mdiHandle             DWORD   0058E30Dh   ; MDI client handle (the father of all opened MDI child) 

@start: 
  @firstPlace:          ; Here from 4DC5A9 
   ; The next two instructions are from the original program 
   push 1 
   pop eax 
   push 004DC683h       ; The address for the managment of the return to Hex WorkShop 
   jmp @sameSnippet 

  @secondPlace:         ; Here from 4DC661 (the modified file is closed, saved or non saved) 
   ; The next two instruction are from the original program 
   push 1 
   pop esi 
   push 004DC668h       ; The address for the managment of the return to Hex WorkShop 

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

   ; Retrieve the handle of the MDI child that will be closed 
   mov esi, mdiHandle 
   invoke SendMessage, dword ptr [esi], WM_MDIGETACTIVE, NULL, NULL 
   mov esi, eax      ; esi = handle of the MDI child relative to the file that will be closed 

   ; Look for the FileStructure to eliminate 
   .WHILE esi != [edi].handleMDI 
      ; The FileStructure relative of the file that has to be closed might be not saved, it's better to check 
      .IF [edi].handleMDI == NULL 
         jmp @done 
      .ENDIF 
      ; Move on the next FileStructure 
      movzx eax, [edi].nSections 
      imul eax, sizeof SECTIONVALUES 
      add eax, sizeof INFOFILE 
      add edi, eax 
   .ENDW 

   ; Calculus the dimension (n° of bytes) of the FileStructure to eliminate 
   movzx esi, [edi].nSections 
   imul esi, sizeof SECTIONVALUES 
   add esi, sizeof INFOFILE 
   push esi        ; Push the dimension of the FileStructure to eliminate 
   add esi, edi    ; esi -> next FileStructure (if exist) of the one to eliminate 
   push esi        ; Push the pointer to the FileStructure after the one to eliminate 

   ; Calculus the number of bytes of the successive FileStructure. The values is saved 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 

   ; Check whether FileStructure to eliminate is the last saved 
   .IF ecx != 0       ; It is not the last one, I have to move back all the successive FileStructure(s) 
     @shiftBack: 
      lodsb 
      stosb 
      loop @shiftBack 
   .ENDIF 

   ; Update the pointer to nextFreeFileStructure 
   mov esi, dword ptr [nextFreeFileStructure] 
   mov dword ptr [esi], edi 

   ; Clear the final bytes; the number of bytes to clear is the number of FileStructure's bytes 
   pop ecx 
  @zeroLastStructure: 
   mov byte ptr [edi+ecx-1], 0 
   loop @zeroLastStructure 

  @done: 
   popad 
   retn
 

Initially you can see an unusual thing, I retrieve immediately the original instruction; I have decided to do it soon only for a convenience matter. Besides this, I also push the return address.
After that, I delete the FileStructure. Even in this case, basing all the job above the various handles, first of all I have to retrieve the handle of the MDI child focused; this task is necessary to identify the right FileStructure to cut. The elimination of the FileStructure happens writing over the data of the structures that follow it; if it is the last one of the list I cover it of byte 00. Remember you to update NextFreeFileStructure putting the new address!
Another thing and we have finish; we have to modify the instruction at 4DC661 into the new "jmp 0058E5B2", do you know the reason why? Simple, 58E5B2 is the address of "@secondPlace". To change these bytes you can use your preferred hexadecimal editore; from DC661h offset put the following bytes: E9 4C 1F 0B 00.
 
Postamble: conclusion.
I tested this new version of Hex WorkShop on w98se and winxp machines and it seems does not have bug. If you find one (or more) bug, please, contact me, I will try to solve the problem!
 
Greetings and thanks.
I would like to thank all the mates at RCE messageboard and all the members of UIC. Feel free to mail me for comments, suggestions, bug reports and/or whatever you want...         
ciao,
 
ZaiRoN
zaironcrk(at)hotmail.com 

Disclaimer
All the information given are for educational purposes only. I would like to remember that you have to buy software after the trial period.