W32Dasm Disassembled

Part2: Taking Command of the CommandLine.
 

by Harlequin

  November  2000

 

  

Tools Used:

 
 W32Dasm V 8.93
Softice
 Text/Hex Editor

 

 

Introduction

W32Dasm is an outstanding dissembler, there probably isn't a Fravia alive who hasn't used it extensively. The big question is how many of you have paid for the pleasure? It is a sad fact of our pastime that the people who suffer the most are those that write the tools which best serve our purposes. There have been many versions of W32Dasm from author Peter Urbanik but unfortunately there don't seem to have been many improvements. Perhaps it is hard to become motivated to refine a product which is so freely available and a new version so quickly cracked and made available. The only real alternative to W32Dasm is still IDA which I tried myself but found a little complex for the often quick tasks I use a dissembler for. (Perhaps this is my fault for not taking more time to become familiar with it :-). As a dissembler there are many modifications and improvements which could be made to W32Dasm. One of these in IMHO is the addition of command line parameters, particularly in the form of passing the filename to be disassembled. This is what I am going to attempt to do in this essay, more for the fun of it than for any other reason.

Note:

I have read some excellent essay's recently with particular note to NeuRaL_NoiSE's Hnotepad development essay.(you can find it at +Fravia's \ Quanticos \and on your RCE cd (thanks +Sandman)) NeuRaL_NoiSE is an outstanding Fravia and an excellent writer and there is a lot to learn from his essays. Due to NeuRaL's excellent writing skills however he made the whole thing seem very very easy, and perhaps indeed it was for him.

On this note I would like to say about this essay. While I can only put down the relevant points of the reversing process there was much much more involved. More time, more research, and more determination than I could measure. The procedure in this essay is by far the most complicated project I have ever undertaken and you should make no mistake about that. Should you read it and feel you do not follow it immediately then you should persist, I did. One thing I can say though is that the measure of difficulty of the task was also the equal of the reward and satisfaction of completing it.

All things are possible given enough time and determination!

Preliminaries

I am using W32Dasm v8.93 currently and have been since I started reversing so all references in this essay will be to this version.
Before starting I made a copy of W32Dasm called Target which is the file on which I will be working and making changes, as I will be using W32dasm in the making of many of the changes I will refer to the new W32Dasm as Target and W32Dasm as itself.
All the changes in this essay are made after the installation of my previous W32patchit modification to add the 'Make Permanent' button to the W32Dasm patch window. All references in this essay will assume that you to have W32patchit installed if you do not then there is no problem but you may find you have more free space to insert your code than I.
So first of all you should disassemble Target using W32Dasm and save the deadlisting.

 

The Essay

Objectives: 

  1. Detect and access a file name and path in the W32Dasm commandline

  2. After W32Dasm has completed loading, process the commandline.
  3. Open the file from the commandline and hand control back to W32Dasm.
     

It immediately struck me that should objective #3 not be possible then none of it would be. For this reason I decided to tackle the objectives in reverse order.

Information Gathering:

#3

We know W32Dasm uses the standard dialog to open its files so in Softice make sure you have the Export module COMDLG32.dll loaded. Place a breakpoint on:
BPX GetOpenFileNameA

Start target.exe and open a file to be disassembled. Softice breaks.
Initially I was looking for an easier place to enter from but after hours of tracing and becoming more familiar with the code it became obvious that the code following the GetOpenFileNameA call was not clear cut and many functions of W32Dasm were integral to this call and the subsequent opening of the selected file.
Just figuring out where to make my entrance took me a full day but in the end I considered that filling out the data as if the GetOpenFileNameA call had been made successfully and then jumping in:

* Reference To: COMDLG32.GetOpenFileNameA, Ord:0000h
:0043E2CD E8B20F0700 Call 004AF284
:0043E2D2 85C0 test eax, eax
:0043E2D4 0F8498030000 je 0043E672
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0043E1B2(U), :0043E1C5(C)
:0043E2DA 33C0 xor eax, eax -------- Here

was my best option.
So in order to fill out the data of the GetOpenFileNameA we must consult our API reference to see it uses the OPENFILENAME structure as follows:

OPENFILENAME structure:                                                                                   
typedef struct tagOFN { // ofn
    DWORD         lStructSize;        --- Not important to us
HWND hwndOwner; --- Not important to us
HINSTANCE hInstance; --- Not important to us
LPCTSTR lpstrFilter; --- Not important to us
LPTSTR lpstrCustomFilter; --- Not important to us
DWORD nMaxCustFilter; --- Not important to us
DWORD nFilterIndex; --- Not important to us
LPTSTR lpstrFile; --- We need this one at Base+1Ch
DWORD nMaxFile; --- Not important to us
LPTSTR lpstrFileTitle; --- Not important to us
DWORD nMaxFileTitle; --- Not important to us
LPCTSTR lpstrInitialDir; --- Not important to us
LPCTSTR lpstrTitle; --- Not important to us
DWORD Flags; --- Not important to us
WORD nFileOffset; --- Not important to us
WORD nFileExtension; --- Not important to us
LPCTSTR lpstrDefExt; --- Not important to us
DWORD lCustData; --- Not important to us
LPOFNHOOKPROC lpfnHook; --- Not important to us
LPCTSTR lpTemplateName; --- Not important to us
} OPENFILENAME;

Much of this structure is of no importance to us as we will not actually be calling the OpenFile dialog so we do not need to initialize it. The return data is important though so we need to locate where Target.exe stores this data. If you take a look just above the call to GetOpenFileNameA you will see the following:

:0043E23A mov edx, dword ptr [ebx+0063E133]      --- Clear filename address
:0043E240 mov byte ptr [edx], 00 --- of any left over strings
:0043E243 mov dword ptr [ebx+0001EB94], 0000004C --- lStructSize;
:0043E24D mov ecx, dword ptr [ebx+006F118C]
:0043E253 mov eax, dword ptr [ecx+0C]
:0043E256 mov dword ptr [ebx+0001EB98], eax --- hwndOwner;
:0043E25C mov edx, dword ptr [ebx+0063E137]
:0043E262 mov dword ptr [ebx+0001EBA0], edx --- lpstrFilter;
:0043E268 xor ecx, ecx
:0043E26A mov dword ptr [ebx+0001EBA4], ecx --- lpstrCustomFilter;
:0043E270 mov eax, dword ptr [ebx+0063E119]
:0043E276 mov dword ptr [ebx+0001EBAC], eax --- nFilterIndex;
:0043E27C mov edx, dword ptr [ebx+0063E133] --- This will point to filename
:0043E282 mov dword ptr [ebx+0001EBB0], edx --- after the call
:0043E288 mov dword ptr [ebx+0001EBB4], 00000050 --- nMaxFile;
:0043E292 mov ecx, dword ptr [ebx+0063E133]
:0043E298 mov dword ptr [ebx+0001EBB8], ecx --- lpstrFileTitle;
:0043E29E mov dword ptr [ebx+0001EBBC], 00000050 --- nMaxFileTitle;
:0043E2A8 lea eax, dword ptr [ebp+FFFFFF14]
:0043E2AE mov dword ptr [ebx+0001EBC0], eax --- lpstrInitialDir;
:0043E2B4 mov dword ptr [ebx+0001EBC4], edi --- lpstrTitle;
:0043E2BA mov edx, dword ptr [ebx+0063E12B]
:0043E2C0 mov dword ptr [ebx+0001EBC8], edx --- Flags;
:0043E2C6 lea ecx, dword ptr [ebx+0001EB94]
:0043E2CC push ecx
* Reference To: COMDLG32.GetOpenFileNameA, Ord:0000h

As a quick check a d *(ebx+1EBB0) after the GetOpenFileNameA call will reveal your selected file.
So now we know the addresses into which we need to place our data the only problem is that they are referenced with respect to the value in ebx. This value is 078C58Ch and while we could simply use this value, as NeuRaL_NoiSE pointed out it is always better to use pointers when patching in our own code, this way should the target be loaded into a different area of memory our addresses will still all be good. So after some heavy searching above the GetOpenFileNameA call I discovered that the value is derived from [ebp+08]. Obviously we cannot guarantee that the value will be in [ebp+08] at the point where we insert our code, so we will have to leave this value for just now and search ebp once we have a location for our code.

#2

This is where we have to make the jump from the normal target code to our own little routine. Obviously we want Target.exe to have completed it's normal initialization routines before we do this so where do we start???

Well if we look at our results from objective #3 we know that we want Target.exe to execute this code except for the GetOpenFileNameA call. We also know that we will be supplying the information normally returned by this call in our little function. So If we jump to our function just before this call and then decide within our function wether there are any command line parameters to be processed or not we can replace the overwritten calls in our function and return.

:0043E2C6 lea ecx, dword ptr [ebx+0001EB94]      
:0043E2CC push ecx

So if we replace the above with a jmp to our function, we have our point of re-direction.

#1

Finally the easiest part of the information gathering section. We need to access the commandline parameters that we pass to Target.exe. Looking through the Import lists of our deadlisting we can see that Target.exe already uses GetCommandLineA so place a breakpoint:

BPX GetCommandLineA

Run Target.exe and Softice breaks, F11 and we can see Target.exe stores the Commandline at [004D1F8Ch]. We can access this from our code.

So that ends the information gathering part of this essay now we need to find a place to insert our code and to write it.

The Code:

Firstly it should be noted that a major consideration when inserting our own code into a target always has to be space space space. The less we use the better. For this reason I am going to insert most of my code into an external DLL, and guess what? as I already have the W32patch.dll at my disposal I am going to use it. It is my intention at a later date (if I can be bothered after this) to add a few more functions to W32Dasm such as a recent file list. For this reason I need to be particularly conservative of my space. To this end I decided to create a small function which loads my DLL and loads a function within it called Combi. By passing a value through variables I can then decide within the DLL itself which code should be executed. In this fashion I can add more functions later with the minimal bytes used. This function I am going to insert at offset AE8F9h RVA 004AF2F9h just after my W32Patch code. So using W32Dasm's patch window goto address: 004AF2F9h and patch in the following.

1  pushad                        --- save all registers  
2 push 004B0000 --- Our DLL name 'W32Patch.dll'
3 call 004AEAF2 --- LoadLibrary
4 mov dword ptr [004B001E], eax --- Save library handle into address
5 push 004B0018 --- Our function name 'Combi'
6 push eax --- Our Library handle
7 call 004AEAD4 --- GetProcAdress
8 test eax, eax --- Check if loaded ok
9 je 004AF32D --- See below for more info
10 mov edx, 004AF3E0 --- Address of first Variable for passing to Combi
11 mov cx, 0005 --- Counter for variables
12 push edx --- Push variable onto stack
13 add dl, 04 --- increment to next variable
14 dec cx --- decrement counter
15 test cl, cl --- check if all variables are on stack
16 jne 004AF321 --- if not go round again (push edx instruction)
17 call eax --- Call our Combi function
18 mov eax, dword ptr [004B001E] --- Library handle
19 push eax --- Onto stack
20 call 004AECDE --- Call FreeLibrary
21 popad --- restore addresses
22 ret --- Get outta here

For the instruction at line 9 I just used a temporary address to jump to due to the fact that making the patch in W32Dasm's patch window the place we wanted to jump to didn't yet exist. Once you have these instructions entered press the 'Make permanent' button from W32Patch if you have it installed or copy them to your editor and manually enter the bytes into target.exe. When you have finished the patch you will be able to see that the actual je address should be as above. You can enter this manually by changing the instruction:

:004AF316 74## je 004AF3##

(004AF32D - 004AF316) - 2 (the jump bytes) = 15h

:004AF316 7415 je 004AF32D

Or just re-patch it using W32patch.

The little loop from lines 12-16 simply pushes the variable addresses for Combi onto the stack. I did it this way because I currently have catered for five variables, to push these individually would require the following:

:004AF3## 68E0004AF3 push 004AF3E0

which is 5 bytes long. For 5 variables this would be 25 bytes, using the loop only takes 19 bytes but additionally I can up the number of variables should I ever need to simply by changing the value loaded into cx without adding extra space usage.
You should note that I have used the CODE section to store my variables in so we have to change the Section info to write enabled. Change it from 60000020h to E0000020h. We will be doing some patching into the CODE section later too so this change would have to be made anyway.

Ok so now we have our all purpose calling function now we need to call it.
So at offset AE93Bh (RVA 004AF33Bh) we will insert the code to which we will divert the normal target.exe code

:004AF33B 60             pushad                        --- Save all
:004AF33C B8E0F34A00 mov eax, 004AF3E0 --- Address of variable5 for combi
:004AF341 C70001000000 mov dword ptr [eax], 00000001 --- so combi knows the function to exec
:004AF347 E8ADFFFFFF call 004AF2F9 --- call our previous loader function
:004AF34C 61 popad --- reset everything
:004AF34D 8D8B94EB0100 lea ecx, dword ptr [ebx+0001EB94]--- the line we replaced
:004AF353 E974EFF8FF jmp 0043E2CC --- jump back to normal target.exe code

You may notice that when we have finished our little function we replace the line we removed to get here:

:0043E2C6 lea ecx, dword ptr [ebx+0001EB94]      
:0043E2CC push ecx

and return to the next line. Now by doing this we will still execute the GetOpenFileNameA call. I have done this intentionally so that if there is a problem loading the W32patch.dll then target.exe will just carry on as normal. See a problem here??? :-)

If the dll loads ok and we put our information into the OPENFILENAME structure satisfactorily then it won't make any difference as we will overwrite it again with the GetOpenFileNameA call which we of course don't want to execute. To get around this I shall detect for commandline parameters in the dll. Should there be some I will patch the instruction at :004AF353 to jump back to our chosen line just after the GetOpenFileNameA call.

:004AF353 E982EFF8FF     jmp 0043E2DA

The dll will also destroy the commandline parameters so that on the second run there won't be any, in this instance it will patch the instruction at :004AF353 back to.

:004AF353 E974EFF8FF     jmp 0043E2CC     --- jump back to normal target.exe code

So now it remains to write our Combi function1 code as follows:

Combi proc var1:DWORD, var2:DWORD, var3:DWORD, var4:DWORD, var5:DWORD

pushad ; Save all
mov eax,var5 ; Get the function control variable
.IF dword ptr[eax] == 00000001h ; Function1 begin
mov esi,004D1F8Ch ; Commandline address
mov edi,dword ptr[esi] ; into edi
mov ecx, -1 ; counter -Dolphinz Code
mov al, 0 ; search byte
push edi ; save pointer
repnz scasb ; search for end of pointer
not ecx ; number of bytes read
pop edi ; pointer to command line
mov al, 20h ; search byte
repnz scasb ; get length of parameter
test ecx,ecx ; something there?
cmp ecx,1
ja command_line_ok ; -End Dolphinz Code
                                        ; no commandline found
mov eax,004AF353h ; Our hand back control line
mov dword ptr[eax],0f8ef74e9h ; Patch jump
popad ; Restore all
ret ; Let's go

command_line_ok:
dec ecx
push edi ; save parameter
add edi,ecx ; point edi to end of parameter
std ; set direction flag to decrement
mov al,5Ch ; put '\' char in al
repnz scasb ; Search '\' in param from end backwards
test ecx,ecx ; check to see if was found
jz skip
add edi,2 ; if found set edi to beginning of filename
mov byte ptr[edi-1],00 ; replace '\' with 00 to terminate path from filename
skip:                                   ; if not found edi already at beginning of filename
mov ebx, dword ptr[ebp+2ch] ; Get our magic EBX offset number from target.exe
mov dword ptr[ebx+0063E133h],edi ; Set up OPENFILENAME structure
pop edi ; set edi back to beginning of parameters
mov byte ptr[edi-1],00 ; Replace 20h in CmdLine with 00h so parameters
; are not detected on the next pass
invoke SetCurrentDirectoryA,edi ; Set the current directory to the path
; This would normally be done by GetOpenFileNameA
mov eax,004AF353h ; Our hand back control line
mov dword ptr[eax],0f8ef82e9h ; Patch jump
.ENDIF
popad ; Restore all
ret ; Let's go GPF
Combi Endp

So there we have it all done. Only one minor problem! we only get to run our code and hence the commandline parameters when we select the 'Open File' menu item in target.exe. This sort of defeats the whole idea, so how do we get around it?

We send target.exe a message as if the 'Open File' menu had been selected.
Obviously we want Target.exe to have completed it's normal initialization routines before we do this so where do we start???
There may well be an easier way to do this but I afraid I don't know it so here is my method.
It is reasonable to assume that one of the last things target.exe would do when running its initialization routine would be to set up the status bar text. To this end I did a search in the deadlisting for 'Select a File' and found the text at :0043C018h. Scanning down a little you see several initializations and then the window title text at :0043C1E0h. So I placed a breakpoint on this line:

bpx 43C1E0

Closed target.exe and re-opened it. Softice breaks.
F10 F10 F10....... and you will see the window being built and finally the crucial call at:

:00493E43 FF5010 call [eax+10]

This jump into :00494197 which does a little processing but finally ends up in the main window WaitMessage loop. We want the loop to run through once to do all its setup routines then afterwards at:

:004941FB E9D1010000              jmp 004943D1

We can insert our re-direction. However as we only want our code to execute once this could be a problem as we are inside the main window loop. So we will have to cover our tracks after we have been here.
So how do we know what message we want to send to the application? Well we could do it manually using the deadlisting and our windows.inc file to find the parameters, but another way is to set a breakpoint in Softice. So fire up target.exe and ctrl+D for softice and enter:

bpx PeekMessageA

Now you are going to get Softice popping continuously as the various applications running on your system process their message threads. Just press F11 to jump back to the calling procedure and press Ctrl+D until you get the PeekMessageA which belongs to target.exe. You should come out at line: 004A566A. If you check your API reference for PeekMessageA you will see the first parameter is the address of the message structure. The second parameter within this structure is the message itself. So we want to place a breakpoint if the message is a WM_COMMAND and we know the address structure is in edi so clear your PeekMessageA breakpoint and enter the following:

bpx 004A566A IF *(edi+4)==111

This will cause softice to break at line 004A566A if the message is 111 which of course is the value for WM_COMMAND.
CTRL+D and then select the 'Open File' menu item in target.exe. Softice breaks and you can see with a 'd edi' the msg structure.

typedef struct tagMSG {     // msg
    HWND   hwnd;     ###
UINT message; 111 WM_COMMAND
WPARAM wParam; 5F0C Menu Identifier
LPARAM lParam; 0000
DWORD time;
POINT pt;
} MSG;

So now we have all the information we need so lets make the jump and create the message. This is a very simple process but believe it or not (perhaps I am having a bad day, not enough beer???) this is what gave me the most trouble. In order to create a message for the process we need the window handle (Hwnd) something which I have done many times before. This time however I could not find it. By placing a breakpoint on CreateWindowExA I could see Hwnd being saved into [ebx+0C] however depending on how target.exe was started (either double click in explorer or run from the start menu) the value of ebx was always different. As I thought I should be able to find Hwnd normally, I spent quite some time trying, in the end I just thought stuff it and saved Hwnd to a location of my own. Here's how:
A breakpoint on CreateWindowExA will show you that target.exe always uses the code at 00491C5Ah to save the Hwnd value. The code looks like this:

:00491C5A 89430C                  mov dword ptr [ebx+0C], eax
:00491C5D 5B pop ebx
:00491C5E 5D pop ebp
:00491C5F C3 ret

Where eax contains Hwnd. I changed the above to this:

:00491C5A E90FD70100              jmp 004AF36E
:00491C5F C3 ret

Jumping into a little bit more of my valuable space. At address 004AF36Eh I then added the following:

:004AF36E 89430C      mov dword ptr [ebx+0C], eax  --- Save Hwnd normally
:004AF371 BBFCF34A00 mov ebx, 004AF3FC --- Save Hwnd to my address
:004AF376 8903 mov dword ptr [ebx], eax --- After Combi Variables
:004AF378 5B pop ebx --- replaced instructions
:004AF379 5D pop ebp
:004AF37A C3 ret --- as stack is reset we can just return

This code is pretty much self explanatory, it just saves a copy of Hwnd for my own use into the address 004AF3FCh. If you are following closely you will notice that I have left a small space between my last code and this. The reason for this is that I had already written the following code before I discovered the Hwnd problem. So now to fill that gap and send a message to target.exe we need to open W32Dasm's patch window at 004941FB and patch in the following:

:004941FB E959B10100                    jmp 004AF359

This simply jumps out of the normal window handling loop to my code, which I enter as follows:

:004AF359 B8E0F34A00     mov eax, 004AF3E0              --- Address of Combi var5
:004AF35E C70002000000 mov dword ptr [eax], 00000002 --- load it with 2
:004AF364 E890FFFFFF call 004AF2F9 --- call combi
:004AF369 E96350FEFF jmp 004943D1 --- return to target.exe

Here we just load the value 2 into the Combi Variable 5 to let it know to execute procedure2 then we call our multi - purpose function to load and call Combi.
Now in Combi we add the second prodedure:

 

 .ELSEIF dword ptr[eax] == 00000002h    ; Function2
mov esi,004D1F8Ch ; Commandline address
mov edi,dword ptr[esi] ; into edi
mov ecx, -1 ; counter -Dolphinz Code
mov al, 0 ; search byte
push edi ; save pointer
repnz scasb ; search for end of pointer
not ecx ; number of bytes read
pop edi ; pointer to command line
mov al, 20h ; search byte
repnz scasb ; get length of parameter
cmp ecx,1
jna No_commandline ; -End Dolphinz Code

push 00000000h ;I use PostMessageA API pre-loaded by target
push 00005F0Ch ;The menu ID
push 00000111h ;WM_COMMAND
mov ecx,004AF3FCh ;Get our Hwnd
mov eax,dword ptr[ecx] ;into eax
push eax ;Push the damn thing
mov eax,004AF164h
call eax ;Call PostMessageA

No_commandline:
mov eax, 004941FBh ;The address where we jumped out of target.exe code
mov dword ptr [eax], 0001D1E9h ;Patched back to normal

Basically all we are doing here is jumping out of the main window handling loop calling our Combi function with the parameter var5=2. This function then checks if there are any parameters on the commandline. We do this because we don't want the GetOpenFileNameA dialog opening every time we open Target.exe.
If there are parameters we place a message of our own (simulating the 'OpenFile' menu click) onto the message queue.
If there are no parameters we skip this part so no mesage is sent.
Then because we don't want to do this every time the main window loop is processed we close the door behind us by patching the jump that brought us here back to its original code. In this way we only ever come here once.

That's it, simple! I think not!! but well worth it, not only did I enjoy myself I learnt a great deal in the process. The only way to learn is by doing after all. I have to be honest though, when I started this I didn't think it would be possible but hey the impossible always attract me :-)

So in summary:

 

In Conclusion 

We can now load a program directly into W32Dasm either from another program (your IDE) or by adding W32Dasm to the shell extensions in your registry so that you can right click a file and open it directly into W32DAsm. Useful??? Who the hell cares!!.

Part3 will make more use of some of the code inserted here as I will be adding a recent file list menu, hopefully.

I learnt a good many things from this exercise but have some unanswered questions too, if any one can help me with the following (before I help myself) then please mail me at: Harlequin00 AT cjb . net.

I originally tried jumping to the main DLL function directly from the main window loop at:

:004941B7 E915020000 jmp 004943D1

But when I arrived in my dll I found that the memory which should have contained the code for the GetOpenFileNameA functions actually contained nothing at all. This I assume (perhaps stupidly) is something to do with the thread not having been initialized yet??? I tried reading my API reference for info but all it says is that a thread is a thread and a process is a process. Thanks for that Bill!!!. If anyone can explain this or point me in the direction of the info I would be grateful.

If anyone can find the Hwnd address please let me know how!! I only have two strands of hair left. 

Final Notes

Thanks go to:

 NeuRaL_NoiSE and anybody else who takes the time to write an essay so that the rest of us can learn too.

Dolphinz for the little snippet of code to handle the commandline.

Peter Urbanik for such a great tool.

 

PS: I know my assembler is as rough as a bears arse but give me a break I am still learning!

 

 

Essay by:      Harlequin
Page Created: 27th November 2000