W32Dasm Disassembled

Part3: Adding a Recent Files List
 

by Harlequin

  December  2000

 

  

Tools Used:

  W32Dasm V 8.93
Softice
 Text/Hex Editor
ExeScope
Resource Hacker
 

 

Introduction

Although W32Dasm is an outstanding program it has a few deficiencies, one of these is a lack of a recent files list. In this essay I shall be adding just such a thing.

This is part 3 in a series of essays on adding functionality to W32Dasm v.8.93. What more is there to say let's go......

 

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 two W32Dasm Disassembled essays . All references in this essay will assume that you have installed them, if you have not then not only will you not be able to follow this essay but much of the code will not work!!!!!
So first of all you should disassemble Target using W32Dasm and save the deadlisting.
 

The Essay

Objectives: 

  1. To add a recent files list menu item to W32Dasm

  2. To load a list of the most recent files
  3. To re-load a file when it is selected from the list
  4. To save a new file to the list when it is opened

Ok before we can attempt any of the above there is a small problem to address, the matter of space. Last time we used nearly all our space and we probably won't have enough in the location we were using. So we need some more!
There are many ways to achieve this including adding a new section of our own, this would however add bytes to the file size and as I want to maintain my updates in the form of patches this is not the best solution. We could simply go looking for new spaces in other sections but if you do this you will find very little. I have another solution however which serves me twofold, and it is staring you in the face every time you start up W32Dasm. Yep that big ugly bitmap in the middle of the screen. We could start changing all the code which loads the bitmap so that it doesn't and use it's space but better still.

I created my own little bitmap with a whopping 5kb file size. (the W32Dasm one is 38kb) Then using ExeScope I import it in place of BITMAP #333. Next save the file but make sure you have the 'Permit to change the filesize' checkbox UNCHECKED. ExeScope will write the new bitmap into target without changing the file size. This also has the advantage that the extra bytes from the old W32Dasm bitmap remain, so in a byte comparison to make a patch we only have the 5kb of our bitmap to alter. Now we have about 33kb of free space from offset E7110h to EF3A0h, enough to drive a herd of camels through never mind, make the changes I shall be making here. This space is in the .rsrc section though so we won't be able to execute any of our code unless we change the section characteristics from 50000040h to +CODE+EXECUTABLE = 70000060h.

If you installed Part2 from my patch you will already have this bitmap. I made a mistake on Part2 and had to quickly update it after I had started Part3 so I just left the bitmap in place.

Although I have all this space I am going to continue to do most of the work inside my W32patch.dll simply because I can and I prefer to, besides less bytes changed means a smaller patch. I shall also continue to use the 'Combi' function which we created in part2. Another reason for this is that you may not always have a glorious 33kb of space in which to play.

Ok then:

#1

This is very very simple. Simply open Target.exe in Resource Hacker (an outstanding program with a few lacking features, such as the ability to update without changing the file size as in ExeScope, and the distinct lack of information about the resource, such as offset and size etc, a recent file list and the ability to just close a file without closing Resource Hacker. Perhaps it could be the next project :-) open the menu resource and type in the following:

MENUITEM "&Open File to Disassemble..",  24332
MENUITEM SEPARATOR
POPUP "Recent Files"
{
MENUITEM " ", 701
MENUITEM " ", 702
MENUITEM " ", 703
MENUITEM " ", 704
MENUITEM " ", 705
}
MENUITEM SEPARATOR
MENUITEM "&Save Disassembly Text File and Create Project File", 24333, GRAYED

Save it. That's it done.

#2

Next we need to load in the current list of recently opened files. Obviously we need somewhere to load them from and as Target.exe uses its own .ini file W32dasm8.ini to store the rest of its setting why not use it. Firstly though we need to decide when we are going to load this file list. A good time to do this so that we don't add extra bytes to the file might be in our Combi function2 that we created in Part2 to send the windows message.

I added the following code to Combi function2:

No_commandline:
mov eax, 004941FBh ;The address where we jumped out of target.exe code
mov dword ptr [eax], 0001D1E9h ;Patched back to normal
    INVOKE     GetMenu,hinst       ;Get the handle of the applications window
mov Hmenu,eax ;Save the handle in Hmenu
mov ebx,offset [Key1] ;address of 1st key name in ini file
mov ecx,5 ;total # of keys
loop1:
push ecx ;save ecx as it gets changed in the API's
INVOKE GetPrivateProfileStringA,ADDR SectionNm,ebx,ADDR DefaultNm,\
ADDR RetVal, SIZEOF RetVal, ADDR IniNm ;Get the ini entry
INVOKE lstrcmpiA,ADDR DefaultNm , ADDR RetVal ;Check if there was anything
.if eax!=0 ;If so
INVOKE ModifyMenuA, Hmenu, MenuID, 0,
MenuID, ADDR RetVal ;Make menu text ini entry text
.ENDIF
inc ebx ;Point ebx to next key address
inc ebx
inc MenuID ;Point to next menu item
pop ecx ;Get ecx back
loopnz loop1 ;Go again if not zero
  .ENDIF
popad ; Restore all
ret ; Let's go GPF

The above function reads the five Recent file strings from the W32dasm8.ini file and if a string is found it then sets the menu item text to the string value. I am keeping this very simple and very basic, my menus are normally just spaces showing as blank when they are empty. I do not add or delete menu items should they be full or empty, nor do I cater for hotkeys. These are all simple additions that could be added without difficulty.
I haven't shown my variable definitions above but you can see them in the source code (sure you can figure that out though :-). The only other addition I made which I have not shown above was to mov the window handle into hinst at the start of function2.

Ok we now have our menu but if you run target.exe I am sure you will very quickly discover a small problem. All our new menu items are disabled!!.
This little beastie kept me amused for a good couple of hours I can tell you. At first I thought I had made a mistake somewhere and checked and double checked everything. Then I added an EnableMenuItem call to my above function and still they were disabled. After some consideration I decided that Target.exe must be sabotaging my new menu's. So I placed a

BPX EnableMenuItem

Ran target and selected a menu item. Softice breaks F11 and we are here:

:0049C5AC 83C408                  add esp, 00000008
:0049C5AF 33C0 xor eax, eax
:0049C5B1 84DB test bl, bl
:0049C5B3 7501 jne 0049C5B6
:0049C5B5 40 inc eax
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0049C5B3(C)
:0049C5B6 0D00040000 or eax, 00000400
:0049C5BB 50 push eax
:0049C5BC FF7614 push [esi+14]
:0049C5BF FF7610 push [esi+10]
* Reference To: USER32.EnableMenuItem, Ord:0000h
|
:0049C5C2 E8232A0100 Call 004AEFEA
:0049C5C7 5E pop esi
:0049C5C8 5B pop ebx
:0049C5C9 5D pop ebp
:0049C5CA C3 ret

The above little function is called every time a menu item is shown, I have no idea how! I tried a breakpoint :

bpx 004A566A IF *(edi+4)==116 || *(edi+4)==117 || *(edi+4)==11F

but nothing showed. (I am not going to explain the above breakpoint get your API reference and your window.inc out and do some research :-). I tried tracing the code through but soon tired of that messy little job. So I still don't know how we get here. How is irrelevant however, so long as we know that we do. So what does the above snippet do??
Well dependant on the value in 'bl' it sets all the menu items in the current POPUP to either enabled or disabled. Again where the hell the value in 'bl' comes from I don't know and to be honest I don't care. A quick look around the registers showed the menu item id stored in edi+4, our way out of this thing:

:0049C5AC 83C408              add esp, 00000008
:0049C5AF 33C0 xor eax, eax
:0049C5B1 84DB test bl, bl
:0049C5B3 7501 jne 0049C5B6

becomes:

:0049C5AC E95FE30400          jmp 004EA910
:0049C5B1 84DB test bl, bl

To take us to our new space :-) then:

:004EA910 817F04D0020000       cmp dword ptr [edi+04], 000002D0 ;cmp to 720d
:004EA917 7902 jns 004EA91B ;jump if bigger
:004EA919 B301 mov bl, 01 ;move 1 into bl if item is one of ours
:004EA91B 83C408 add esp, 00000008 ;replaced instruction
:004EA91E 31C0 xor eax, eax ;and another
:004EA920 E98C1CFBFF jmp 0049C5B1 ;that'll teach it

Ok fixed, all this does is check if the menu item we are processing has an ID greater than 720d which of course mine don't where as poor old Peters all do. Then we just set bl accordingly and let target.exe have control again.

#3

Right now we have some pretty nice looking little menu entries. (At least you do if you edited some file names into W32dasm8.ini like I did. You should put them under a new section [RECENT] with keys 1,2,3,4,5) So next I guess we had better do something with them.

I am sure there must be another way of doing this but after a little tracing it doesn't appear to me as though there is an easier way, however I learn more every day so if you see another option please humor me (and let me know of course).
It is pretty easy to find our way to the normal Wmsg comparison checks with a

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

Then simply F8 into the calls until we arrive here:

:00452617 C745E8261F4C00          mov [ebp-18], 004C1F26
:0045261E 8B4604 mov eax, dword ptr [esi+04]
:00452621 3D21010000 cmp eax, 00000121
:00452626 7F41 jg 00452669
:00452628 0F84A30D0000 je 004533D1
:0045262E 3D11010000 cmp eax, 00000111
:00452633 7F1B jg 00452650
:00452635 7463 je 0045269A
:00452637 2DA0000000 sub eax, 000000A0
:0045263C 0F84530D0000 je 00453395
:00452642 83E860 sub eax, 00000060
:00452645 0F8493000000 je 004526DE
:0045264B E92F120000 jmp 0045387F

and you recognize the normal checks with :0045262E checking if a WM_COMMAND was received.
From here on in tracing the route of your messages gets a little more complex, so why bother? If we stop and think about what we want to do for a second....
Our menu click will produce a WM_COMMAND with the menu ID in *(edi+8) what we want to do next is fake an 'Open File' menu command. If we are smart and place the Recent file string into the command line and then call the 'Open File' menu our code entered in Part2 of W32Dasm Disassembled will do the rest for us. So lets give it a go:

:00452635 E9ED820900         jmp 004EA927         --- jmp to our space
:0045263A 90 nop --- keep it tidy
:0045263B 90 nop

Then in our space:

:004EA927 9C                 pushfd                --- save flags
:004EA928 60 pushad --- save registers
:004EA929 817F08BC020000 cmp dword ptr [edi+08], 000002BC --- cmp wparam to 700d
:004EA930 7C26 jl 004EA958 --- if its less its not ours
:004EA932 817F08C6020000 cmp dword ptr [edi+08], 000002C6 cmp wparam to 710d
:004EA939 791D jns 004EA958 --- if its greater its not ours
:004EA93B B8E0F34A00 mov eax, 004AF3E0 --- var5 for w32patch.dll Combi function
:004EA940 C70003000000 mov dword ptr [eax], 00000003 --- function3
:004EA946 83C004 add eax, 00000004 --- point eax to var4
:004EA949 8B5708 mov edx, dword ptr [edi+08] --- wparam
:004EA94C 8910 mov dword ptr [eax], edx --- into var4
:004EA94E 83C004 add eax, 00000004 --- point eax to var3
:004EA951 8938 mov dword ptr [eax], edi --- MSG address into var3
:004EA953 E8A149FCFF call 004AF2F9 --- our Combi calling function
:004EA958 61 popad --- restore registers
:004EA959 9D popfd --- restore flags
:004EA95A 0F843A7DF6FF je 0045269A --- replace the overwritten
:004EA960 81E8A0000000 sub eax, 000000A0 --- instructions and
:004EA966 0F84298AF6FF je 00453395 --- carry on as if nothing had happened
:004EA96C E9D47CF6FF jmp 00452645 --- except the odd GPF of course

As we have all this new found space in the shrunken bitmap memory I have gone crazy with the bytes a little :-). All we are doing in this function is saving all flags and registers then checking to see if the wparam part of the MSG is within range to be one of our menu items. If it is not we carry on as normal, if it is we call Combi function3 then carry on as normal.

So now for Combi function3

.ELSEIF dword ptr[eax] == 00000003h      --- Function3
mov ebx,var4 --- Our menu item ID
mov eax,dword ptr[ebx] --- Value into eax
sub eax,2BDh --- Subtract 701d from it
shl eax,1 --- Multiply by 2
mov ebx,offset [Key1] --- 1st ini file key address
add ebx,eax --- Add eax to the address to give correct key

INVOKE GetPrivateProfileStringA,ADDR SectionNm,ebx,ADDR DefaultNm,\
ADDR RetVal, SIZEOF RetVal, ADDR IniNm --- Get the ini entry
INVOKE lstrcmpiA,ADDR DefaultNm , ADDR RetVal --- Check if there was anything
.if eax!=0 --- If value found at the key
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
mov dword ptr[edi-2],00000020h --- clear end if string
pop edi --- start of commandline
INVOKE lstrcatA,edi,ADDR Retval --- Add on our Recent file

mov eax, 004941FBh --- The door from Part2 we closed
mov dword ptr [eax], 01B159E9h --- Lets open it again
.ENDIF
.ENDIF

An explanation:
We first get our menuID from the MSG wparam contained in edi. Then by subtracting 701 decimal we are left with a value which when multiplied by 2 and added to the offset of our Key1 address will give us the offset to the correct Key for the menu item which was selected. So we then get the string from the ini file using this key. Next we check if there was a string and that a blank menu item was not selected. If there was we start processing.
What we do here is load the address for the commandline, seek the end, set the last character to a space (if it was already a space it won't make any difference) then we place our ini file string on the end, creating an artificial commandline. All we do then is re-open the door which we closed behind us in Part2. If you remember this will jump out of the main windows message loop into Combi function2. If there is a commandline it will generate a message to call the 'Open file' dialog code and then shut the door again. We then finally and simply return control to Target.exe so it can process our current message, which of course as there is no procedure, it cannot do, so it will simply ignore it.

Now we have our first big problem. If you run target.exe it will work fine normally, if you load a recent file it will work fine also. However if we try to load another recent file after this we have a problem. The file doesn't load and when you exit target.exe you get a major GPF a complete system reboot. I can see nothing in our code that would cause this so lets debug.

Load target.exe, BPX enablemenuitem, click 'Disassemble', softice breaks. F11 we are in target code. As everything of note that we have done is in Combi place a BPX 004AF2F9 to break just before we enter Combi. F10 F10 and a few F8's and we are in Combi. Now if we trace the code we can watch our command line being built. There is a small problem here:

mov dword ptr[edi-2],00000020h

 that each time we do this we erase one character from the end of the commandline. What happens when we run out of characters?? I am not to concerned about this as the minimum characters we could start with are ("c:\W32dsm89.exe") 17 including the "s. As W32dasm is not the sort of program in which you are constantly loading new files (like a text editor) I am prepared to live with this little bug, you could fix it if you like :-). So if we continue on everything seems to work fine, Ctrl+D and we break this time into Combi function1. Once again everything looks fine, but we know that the first recent file load works anyway so we wouldn't expect to see anything. Now if we repeat the process.

Combi function3 works without issue. However when we get to Combi function1 here:

mov     esi,004D1F8Ch               ; Commandline address
mov edi,dword ptr[esi] ; into edi

If you check our commandline it has become corrupted somewhere along the way. Somebody has thoughtlessly placed a '00' byte in our string part way through. If you edit back in the missing character and CtrL+D everything runs fine. We have our culprit. But how to find him? Well if you repeat the process a couple of times you will see it is always the same address that gets written into, this address is edi+29. So if we run through to the above lines then place a:

BPMD edi+24

Softice will break whenever anybody reads\writes to the dword of which our mischievous '00' address is part. Disable the above breakpoint Ctrl+D and let the recent file load. Ctrl+D and re-enable the breakpoint. Now try loading another recent file. We see kernel reads it then our Combi function3 then we break here:

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

Recognize this?? you should I just cut and paste it from W32Dasm Disassembled Part2. This is the setup for the OPENFILENAME structure prior to the GetOpenFileNameA call. So this is the little sod who is destroying our commandline.
The question is how to stop him? At first I was thinking of writing in a live patch and patching it back in when we had done and a few other over complicated ideas, then it occurred. Sometimes when you start doing this sort of detailed work it is easy to forget the simple things, why not just open up our hex editor and 909090 it? it doesn't do much anyway.
As you may guess this fixes all the problems and we are on our way again.

#4

Our final objective at last :-). We have a working menu but it is not a recent file list, it is just a list of your edited W32Dsm8.ini file. Our final task is to update this list every time a new file is opened. Let's be about it.....

As always firstly we need to think about exactly what we want to do.
If target.exe opens a new file we want to first check that it is not already on the recent file list, if not we want to place it in position 1 in the file list and move all the other entries down one. However we only want to do this if Target.exe opens the file successfully. Finally we want to update the current menu items with the recent file list.

So as we want to do all this only if the file is successfully opened a good place to start would be:

BPX CreateFileA

Then open a file, a little bit of the usual F'in (and a little blindin') and we end up here:

* Reference To: KERNEL32.CreateFileA, Ord:0000h
|
:00451C04 E8D1CE0500 Call 004AEADA
:00451C09 83F8FF cmp eax, FFFFFFFF
:00451C0C 7506 jne 00451C14
:00451C0E 33C0 xor eax, eax
:00451C10 EB02 jmp 00451C14

Where target.exe opens the file and then checks if the open was successful, looks like a good spot to me :-)
So as usual:

:00451C09 83F8FF                  cmp eax, FFFFFFFF
:00451C0C 7506 jne 00451C14
:00451C0E 33C0 xor eax, eax

Becomes:

:00451C09 E9658D0900              jmp 004EA973
:00451C0E 33C0 xor eax, eax

To jump to our space again, then:

:004EA973 83F8FF                  cmp eax, FFFFFFFF    --- replaced instruction
:004EA976 0F849272F6FF je 00451C0E --- if file not open go away
:004EA97C 60 pushad --- save all as always
:004EA97D B8E0F34A00 mov eax, 004AF3E0 --- Var5
:004EA982 C70004000000 mov dword ptr [eax], 00000004 --- Function4
:004EA988 E86C49FCFF call 004AF2F9 --- Call Combi
:004EA98D 61 popad --- Restore all
:004EA98E E98172F6FF jmp 00451C14 --- Run away

Now for Function4:

.ELSEIF dword ptr[eax] == 00000004h      --- Function4
--- Create Path string for new file
INVOKE GetCurrentDirectoryA,SIZEOF New file,ADDR New file
INVOKE lstrcatA,ADDR Newfile,ADDR Backslsh
INVOKE lstrcatA,ADDR Newfile,esi
     --- Get the ini entries and compare each to the New file name. If we get a match leave.
INVOKE GetPrivateProfileStringA,ADDR SectionNm,ADDR Key1,ADDR DefaultNm,\
ADDR RetVal, SIZEOF RetVal, ADDR IniNm
INVOKE lstrcmpiA,ADDR Retval,ADDR Newfile
test eax,eax
je Already_Exists
INVOKE GetPrivateProfileStringA,ADDR SectionNm,ADDR Key2,ADDR DefaultNm,\
ADDR RetVal1, SIZEOF RetVal1, ADDR IniNm
INVOKE lstrcmpiA,ADDR Retval1,ADDR Newfile
test eax,eax
je Already_Exists
INVOKE GetPrivateProfileStringA,ADDR SectionNm,ADDR Key3,ADDR DefaultNm,\
ADDR RetVal2, SIZEOF RetVal2, ADDR IniNm
INVOKE lstrcmpiA,ADDR Retval2,ADDR Newfile
test eax,eax
je Already_Exists
INVOKE GetPrivateProfileStringA,ADDR SectionNm,ADDR Key4,ADDR DefaultNm,\
ADDR RetVal3, SIZEOF RetVal3, ADDR IniNm
INVOKE lstrcmpiA,ADDR Retval3,ADDR Newfile
test eax,eax
je Already_Exists
INVOKE GetPrivateProfileStringA,ADDR SectionNm,ADDR Key5,ADDR DefaultNm,\
ADDR RetVal4, SIZEOF RetVal4, ADDR IniNm
INVOKE lstrcmpiA,ADDR Retval4,ADDR Newfile
test eax,eax
je Already_Exists

--- If there was no match then write the new ini entries
INVOKE WritePrivateProfileStringA,ADDR SectionNm,ADDR Key1,ADDR Newfile,ADDR IniNm
INVOKE WritePrivateProfileStringA,ADDR SectionNm,ADDR Key2,ADDR RetVal,ADDR IniNm
INVOKE WritePrivateProfileStringA,ADDR SectionNm,ADDR Key3,ADDR RetVal1,ADDR IniNm
INVOKE WritePrivateProfileStringA,ADDR SectionNm,ADDR Key4,ADDR RetVal2,ADDR IniNm
INVOKE WritePrivateProfileStringA,ADDR SectionNm,ADDR Key5,ADDR RetVal3,ADDR IniNm

--- Code copied from function2 to update the menu entries
mov ecx,004AF3FCh --- Get our Hwnd
mov eax,dword ptr[ecx] --- into eax
INVOKE GetMenu,eax --- Get the handle of the applications window
mov Hmenu,eax --- Save the handle in Hmenu
mov ebx,offset [Key1] --- address of 1st key name in ini file
mov ecx,5 --- total # of keys
loop2:
push ecx --- save ecx as it gets changed in the API's
INVOKE GetPrivateProfileStringA,ADDR SectionNm,ebx,ADDR DefaultNm,\
ADDR RetVal, SIZEOF RetVal, ADDR IniNm --- Get the ini entry
INVOKE lstrcmpiA,ADDR DefaultNm , ADDR RetVal --- Check if there was anything
.if eax!=0 --- If so
INVOKE ModifyMenuA, Hmenu, MenuID, 0,MenuID, ADDR RetVal --- menu text=ini text
.ENDIF
inc ebx --- Point ebx to next key address
inc ebx
inc MenuID --- Point to next menu item
pop ecx --- Get ecx back
loopnz loop2 --- Go again if not zero

Already_Exists:

.ENDIF

Th th th th that's all folks!

We have completed Part3!!

In Conclusion 

I refer quite heavily to my previous two essays throughout this one. Also until now each essay has been a standalone capable of being installed independent of the others and working successfully (fingers crossed). In this essay though much of the code is intermingled with the code of the previous two essays. For this reason you must have the previous patches installed to follow and complete this essay, indeed if you do not your system will crash!

For this reason the patch I will provide with this essay will patch in all three added functions. It will not do a byte comparison however, so if you already have the first two patches installed it will just repatch over them without problem.

 

Final Notes

Just as a point of interest, and this is probably something you already know, I discovered quite by accident that if you drag a file in explorer and drop it on Target.exe as modified in part2 then the file is automatically opened. I guess this works for any program which is passed files on the command line. Amazing! here I am taking programs to pieces and rebuilding them and I don't even know some of the features of my own operating system. Like I say, everybody has something to learn, some of us have more than others :-)

That's it for just now, I have many plans for Part4 ..... but I am taking a little break from this (got some real life going on) so I won't drop any hints as to what they are. Hope you have enjoyed so far, hope mayby I have helped in some way. Any comments, questions mail me: Harlequin00 AT cjb . net

Please go out and buy W32Dasm, I am sure that if more people start paying for using it Peter Urbanik will add all these and the many more features himself. Perhaps Peter should indeed contact Fravia+'s board and see if someone can't put together a decent protection for W32Dasm, a program this good deserves it.

 

Thanks go to:

RalDnor for saying nice things to me and for posting my essays.

Everybody out there writing essays! I wouldn't be here without them.

Peter Urbanik for such a great tool

Bill Gates for GPF's. Never of any use until I started this reversing stuff, now I couldn't do it without them.
Mind you I am a little nervous quitting W32Dasm after this essay ;-)

  

 

Essay by:      Harlequin
Page Created: 4th December 2000