by Kayaker
Target: Filemon v4.29 (File Date Sep 2/00, File Size 86,016 bytes) www.sysinternals.com
Tools Used: SoftIce, W32Dasm, Procdump, HIEW, Borland Resource Workshop, Win32 Programmer's Reference
Preamble: Greetings fellow Reverser's! Filemon. Well if you don't know what this is, you shouldn't be here ;) This all started because I was getting fed up with every time I saved a Filemon log, having to find the directory I wanted to save it in and then on top of that having to type in a filename. Why, thought I, can't there be a default filename come up in the Save or SaveAs Dialog box? And why stop there? What about the default directory being the one containing the file you are monitoring? That's where I keep my Filemon logs anyway, not the Filemon program directory as it defaults to. And while we're at it, why not add an accelerator key to the SaveAs function? Reverser's deserve to be lazy too ;-)
One caveat before we start. These modifications were done on a Win98SE system and may not work as written on Win95, where Filemon seems to behave slightly different. I have no experience with other Windoze versions, but I'll discuss what to look for in order to adapt this patch for your system.
Important! Since these "enhancements" will require inline patching, you NEED to change the Code Characteristics of the .text section from 60000020 to E0000020 with Procdump or similar. This will change the Flag for the section to "Writeable", which is necessary for the inline patch to change bytes without crashing. I'll assume you know the basics of inline patching, if not I refer you to other tutorials.
To start with, we need to find out what happens when you select Save or SaveAs from a Windows menu.
The OPENFILENAME structure:
When a program uses the Common Dialogs DLL Comdlg32.dll to Save a file, the API GetSaveFileNameA is used to open a standard dialog box. To Open a file GetOpenFileNameA is used. Both of them have only 1 parameter PUSHed, a pointer to an OPENFILENAME structure, or array, that contains information used to initialize the dialog box. When the API calls return, the Structure contains information about the user's file selection.
The Win32 Programmer's Reference gives the following structure for OPENFILENAME (which I split into two columns):
typedef struct tagOFN { // ofn DWORD lStructSize; HWND hwndOwner; HINSTANCE hInstance; LPCTSTR lpstrFilter; LPTSTR lpstrCustomFilter; DWORD nMaxCustFilter; DWORD nFilterIndex; LPTSTR lpstrFile; DWORD nMaxFile; LPTSTR lpstrFileTitle; |
DWORD nMaxFileTitle; |
Two of these parameters are important to us:
lpstrFile
Pointer to a buffer that contains a filename used to initialize the File Name edit control. The first character of this buffer must be NULL if initialization is not necessary. When the GetOpenFileName or GetSaveFileName function returns successfully, this buffer contains the drive designator, path, filename, and extension of the selected file.lpstrInitialDir
Pointer to a string that specifies the initial file directory. If this member is NULL, the system uses the current directory as the initial directory.
If you do a search in W32Dasm for GetSaveFileNameA you find it's sole parameter, which is a pointer to the OPENFILENAME structure, being PUSHed at :00403FCB push eax, and the various bits and pieces being MOVed into the structure afterwards:
:00403F74 8A84249C810000 mov al, byte ptr [esp+0000819C] ; Flag = 0 if Save selected, 1 if SaveAs selected
:00403F7B 84C0 test al, al ; AND al, al without saving, set Zero flag if result 0
:00403F7D 7539 jne 00403FB8 ; jump to SaveAs routine
:00403F7F A05C1D4100 mov al, byte ptr [00411D5C] ; Flag = 0 if file not previously saved, 1 if already saved once
:00403F84 84C0 test al, al
:00403F86 7430 je 00403FB8 ; jump to SaveAs routine, need to build up OPENSTRUCTURE array
.
.
:00403FB3 E987000000 jmp 0040403F ; File saved before, skip OPENSTRUCTURE building* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00403F7D(C), :00403F86(C) ; SaveAs or new Save routine
|
:00403FB8 8B0D482C4100 mov ecx, dword ptr [00412C48]
:00403FBE 8D442418 lea eax, dword ptr [esp+18]
:00403FC2 33ED xor ebp, ebp
:00403FC4 8D94248C000000 lea edx, dword ptr [esp+0000008C] ; lpstrFile value
:00403FCB 50 PUSH EAX ; esp + 1c = pointer to start of array
:00403FCC C744241C4C000000 mov [esp+1C], 0000004C ; lStructSize = 4C bytes
:00403FD4 895C2420 mov dword ptr [esp+20], ebx
:00403FD8 894C2424 mov dword ptr [esp+24], ecx* Possible StringData Ref from Data Obj ->"File Info (*.LOG)"
|
:00403FDC C744242860064100 mov [esp+28], 00410660 ; lpstrFilter = "File Info (*.log)"
:00403FE4 896C242C mov dword ptr [esp+2C], ebp
:00403FE8 896C2430 mov dword ptr [esp+30], ebp
:00403FEC C744243401000000 mov [esp+34], 00000001
:00403FF4 89542438 mov dword ptr [esp+38], edx ; lpstrFile = [esp+8C] = 00000000
:00403FF8 C744243C00010000 mov [esp+3C], 00000100 nMaxFile = 256 bytes
:00404000 896C2440 mov dword ptr [esp+40], ebp
:00404004 896C2444 mov dword ptr [esp+44], ebp
:00404008 896C2448 mov dword ptr [esp+48], ebp ; lpstrInitialDir = EBP = 00000000* Possible StringData Ref from Data Obj ->"Save File Info..."
|
:0040400C C744244C4C064100 mov [esp+4C], 0041064C ; lpstrTitle = "Save File Info"
:00404014 66896C2454 mov word ptr [esp+54], bp
:00404019 66896C2456 mov word ptr [esp+56], bp* Possible StringData Ref from Data Obj ->"*.log"
|
:0040401E C744245844064100 mov [esp+58], 00410644 ; lpstrDefExt = "*.log"
:00404026 896C2460 mov dword ptr [esp+60], ebp
:0040402A C744245006002000 mov [esp+50], 00200006* Reference To: comdlg32.GetSaveFileNameA, Ord:000Bh
|
:00404032 E8A5220000 Call 004062DC
:00404037 85C0 test eax, eax
:00404039 0F84A3010000 je 004041E2* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00403FB3(U) ; direct jump to Save routine
|
:0040403F 8D8C248C000000 lea ecx, dword ptr [esp+0000008C] ; full path of previously saved log* Possible StringData Ref from Data Obj ->"w"
|
:00404046 6840064100 push 00410640
:0040404B 51 push ecx
:0040404C E848260000 call 00406699 ; to CreateFileA
If you do a "dd eax" or "dd esp + 1c" , which is the address of the pointer PUSHed at :00403FCB push eax, after the structure has been built up, you can get a better picture of just what the OPENFILENAME array really looks like:
EAX=00657130 EBX=000004C0 ECX=00400000 EDX=006571A4 EBP=00000000 ESP=00657114
:00657130 | 0000004C | 000004C0 | 00400000 | 00410660 | |
:00657140 | 00000000 | 00000000 | 00000001 | 006571A4! | |
:00657150 | 00000100 | 00000000 | 00000000 | 00000000! | |
:00657160 | 0041064C | 00200406* | 0018* | 0010* | 00410644 |
:00657170 | 00000000 | 00000000 | 00000000 |
A few notes:
By stepping through the code as it is processing you can also build up the following generalized template of the OPENFILENAME structure. I figure if the programmer is following convention, this should be valid for most programs using Comdlg32.dll to Open and Save a file. I've added the indirect addresses used in the above code just underneath each OPENFILENAME entry for reference.
lStructSize | hwndOwner | hInstance | lpstrFilter | |
esp+1c | esp+20 | esp+24 | esp+28 | |
lpstrCustomFilter | nMaxCustFilter | nFilterIndex | lpstrFile! | |
esp+2c | esp+30 | esp+34 | esp+38 | |
nMaxFile | lpstrFileTitle | nMaxFileTitle | lpstrInitialDir! | |
esp+3c | esp+40 | esp+44 | esp+48 | |
lpstrTitle | Flags | nFileOffset | nFileExtension | lpstrDefExt |
esp+4c | esp+50 | esp+54 | esp+56 | esp+58 |
lCustData | lpfnHook | lpTemplateName | ||
esp+5c | esp+60 | esp+64 |
Creating a default Filename:
Well, if you've followed this so far you can probably see that we need to have [esp+0000008C], which is pushed into lpstrFile via edx at :00403FF4 mov dword ptr [esp+38], edx contain some default filename such as "Filemon", or whatever strikes your fancy.
Let's start by finding a cave to insert our inline patch. Use Procdump and a Hexeditor to find that nice big block of 0's between the end of the useful code in the .text section (D21A) and the start of the .rdata section (E000). While you're in Procdump, remember to change the Code Characteristics of the .text section from 60000020 to E0000020 to change the Flag for the section to "Writeable".
This task is relatively easy. What I did was to force a jump at the very start of the SaveAs routine at :00403FB8 8B0D482C4100 mov ecx, dword ptr [00412C48] to the Virtual address 40D270 (Raw offset D270 in that block of 0's) and MOV the hex equivalent of "Filemon" into [esp+0000008C]:
Jump to Patch 1------------------------------------
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00403F7D(C), :00403F86(C)
|
:00403FB8 E9C3920000 jmp 0040D270 ; jump to inline patch #1
:00403FBD 90 nop ; add a necessary byte* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040D29E(U)
|
:00403FBE 8D442418 lea eax, dword ptr [esp+18] ; original code return address
---------------------------------------------------------Patch 1-----------------------------------------------
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00403FB8(U)
|
:0040D270 C784248C00000046696C65 mov dword ptr [esp+0000008C], 656C6946 ; "eliF"
:0040D27B C78424900000006D6F6E00 mov dword ptr [esp+00000090], 006E6F6D ; "nom"
:0040D286 C744244400D34000 mov [esp+44], 0040D300 ; our new lpstrInitialDir - ignore this for now
:0040D28E C70500D4400001000000 mov dword ptr [0040D400], 00000001 ; anti-crash Flag - ignore for now
:0040D298 8B0D482C4100 mov ecx, dword ptr [00412C48] ; replace overwritten original code
:0040D29E E9156DFFFF jmp 00403FBE ; jump back to original code
End Patch 1-----------------------------------------
Now when you select SaveAs, or Save for the first time, to save a log file, "Filemon" will be the default filename in the Dialog box.
OK, you got a sneak preview of the next step ;)
Forcing a SaveAs Initial Directory:
:0040D286 C744244400D34000 mov [esp+44], 0040D300 ; our new lpstrInitialDir
40D300 is the address in our cave where we'll store the full directory path of the file we are monitoring. Note the address here for lpstrInitialDir is [esp+44] and not [esp+48], as it actually is in the OPENFILENAME array. This is because our inline patch is occurring before the pointer to the structure is PUSHed at :00403FCB 50 push eax and ESP at this point is +4 more than it will be after that line is executed. Don't be confused by this, you can really only tell when you are in SoftIce.
Anyway, how do we find the directory path of the file we are monitoring?? Well if you open a file and then look at the Filemon output carefully you see that the first record for that file is unique in that there is an entry for "Directory" under Request and "CHECK" under Other. The "CHECK" item can be used as an identifier to extract the Path information from that particular record, which is exactly what we want. Note that this refers to Win98SE Filemon output. I found that with Win95, there was no "CHECK" entry that referenced a discrete Path string. There was however a "QUERY" item that did, but only IF 'Explorer' was NOT filtered from the output. This is something you'll have to check on your own system. You just need to find a unique way to isolate the directory path, perhaps with some fancy parsing of your own and filtering of any background processes that might give a bogus result ;)
From the Filemon homepage:
For the Windows 9x driver, the heart of Filemon is in the virtual device driver, Filevxd.vxd. It is dynamically loaded, and in its initialization it installs a file system filter via the VxD service, IFSMGR_InstallFileSystemApiHook, to insert itself onto the call chain of all file system requests.
When Filemon sees an open, create or close call, it updates an internal hash table that serves as the mapping between internal file handles and file path names. Whenever it sees calls that are handle based, it looks up the handle in the hash table to obtain the full name for display. If a handle-based access references a file opened before Filemon started, Filemon will fail to find the mapping in it hash table and will simply present the handle's value instead. Information on accesses is dumped into an ASCII buffer that is periodically copied up to the GUI for it to print in its listbox.
What we want to do is to break into the parsing routine for the records after Filevxd.vxd returns with it's load of information. We can use DeviceIoControl, which is a Kernel32 API used to communicate with the vxd. When its output buffer is filled with records, or whatever triggers the periodic updating, command returns to the program.
The DeviceIoControl function sends a control code directly to a specified device driver, causing the corresponding device to perform the specified operation. BOOL DeviceIoControl( |
If you set a BPX DeviceIOControl with Filemon running, SoftIce breaks immediately at 405C62:
* Reference To: KERNEL32.DeviceIoControl, Ord:0063h
|
:00405C3D 8B35ACE04000 mov esi, dword ptr [0040E0AC] ; address DeviceIOControl
:00405C43 6A00 push 00000000
:00405C45 6864244100 push 00412464 ; lpBytesReturned
:00405C4A 68F3FF0000 push 0000FFF3 ; size of output buffer
:00405C4F 68002E4100 push 00412E00 ; starting address of lpOutBuffer
:00405C54 6A00 push 00000000
:00405C56 6A00 push 00000000
:00405C58 680B000083 push 8300000B
:00405C5D 52 push edx
:00405C5E 8BD8 mov ebx, eax
:00405C60 FFD6 call esi ; Call DeviceIOControl
:00405C62 85C0 test eax, eax ; was function successful?
:00405C64 8BAC2498050000 mov ebp, dword ptr [esp+00000598] ; Hwnd of FileMonClass
:00405C6B 744C je 00405CB9 ; if not jump to Error Handling* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00405CB7(C)
|
:00405C6D A164244100 mov eax, dword ptr [00412464] ; lpBytesReturned
:00405C72 85C0 test eax, eax ; is there anything to process?
:00405C74 746A je 00405CE0 ; if not continue to monitor
:00405C76 A168244100 mov eax, dword ptr [00412468] ; Hwnd of SysListView32
:00405C7B 6A00 push 00000000
:00405C7D 50 push eax
:00405C7E 55 push ebp
:00405C7F E8CCD7FFFF call 00403450 ; start parsing for GUI output into SysListView32
Filemon updates its monitoring about once a second, so a break on DeviceIOControl will occur almost continually, giving you no chance to select a file. So we want to set the BP at 405C76 where SoftIce will break only when you actually click on a file (or Filemon monitors some other call). It's useful to set as many filters as you can on background programs such as Explorer, Regmon, msgsrv32, kernel32, etc. which might trigger Filemon to start processing. These won't interfere however with getting the correct directory pathname.
Now open some program like Notepad with the BPX 405C76 set and display the starting address of the buffer that receives the ouput data, lpOutBuffer, and you see the 1st record followed by the start of the 2nd:
:00412E00 00 00 00 00 28 A9 4B 29-00 00 00 00 4E 6F 74
65 ....(.K)....Note :00412E10 70 61 64 09 44 69 72 65-63 74 6F 72 79 09 43 3A pad.Directory.C: :00412E20 5C 57 49 4E 44 4F 57 53-09 43 48 45 43 4B 09 53 \WINDOWS.CHECK.S :00412E30 55 43 43 45 53 53 00 01-00 00 00 28 A9 4B 29 00 UCCESS.....(.K). :00412E40 00 00 00 4E 6F 74 65 70-61 64 09 46 69 6E 64 4F ...Notepad.FindO |
The 5 string items in each record are what's output into the SysListView32 columns of Process, Request, Path, Other and Result. What we want to do now is to trace until the Path (C:\Windows) is parsed into a separate address, which we can do by perhaps setting a BPM on the 1st byte, or just start stepping through code and watch the registers. Here's the route:
:00405C7F E8CCD7FFFF call 00403450 ; F8 into this call
F10 until
:0040352F E87CFBFFFF call 004030B0 ; F8
F10 until
:004030C9 E892FFFFFF call 00403060 ; F8
F10 until:00403077 8906 mov dword ptr [esi], eax ; mov address 1st item in record ( 412E0C = "Notepad") into address pointed to by esi
:00403079 E842340000 call 004064C0
:0040307E 83C408 add esp, 00000008
:00403081 85C0 test eax, eax
:00403083 741B je 004030A0
:00403085 83C604 add esi, 00000004 ;inc esi to next DWORD
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040309E(C)
Start Loop
:00403088 C60000 mov byte ptr [eax], 00
:0040308B 40 inc eax
:0040308C 57 push edi
:0040308D 8906 mov dword ptr [esi], eax ; mov addresses 2nd-5th item in record
:0040308F 50 push eax
:00403090 43 inc ebx
:00403091 83C604 add esi, 00000004 ;inc esi
:00403094 E827340000 call 004064C0
:00403099 83C408 add esp, 00000008
:0040309C 85C0 test eax, eax
:0040309E 75E8 jne 00403088
End Loop, 1st record is now fully processed
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00403083(C)
|
:004030A0 5F pop edi ; we'll jump to an inline patch here and
:004030A1 8BC3 mov eax, ebx ; will have
:004030A3 5E pop esi ; to replace
:004030A4 5B pop ebx ; this code
:004030A5 C3 ret ; original code retained
If you "dd esi" at the 1st instance you'll see the addresses of each of the 5 items being nicely laid out:
016F:0065F650 | 00412E0C | 00412E14 | 00412E1E | 00412E29 |
Process = "Notepad" | Request = "Directory" | Path = "C:\Windows" | Other = "CHECK" | |
016F:0065F660 | 00412E2F | |||
Result = "Success" |
Great, so far we know that the address to the string representing the Path that we want to input into lpstrInitialDir of the OPENFILENAME array is stored in that 3rd DWORD over, or at 65F658. While it's 412E1E in this case, it will change depending on the length of the 1st item in the table, Process. i.e, "Notepad" is 7 characters long, but "DeDe" would only be 4, shifting every subsequent address over.
Now the only problem is that the circular buffer used to hold the data from filevxd is of limited size and this 1st entry with the "CHECK" will be overwritten if there are many records processed by Filemon (somewhere around 50). So we need to snag the Path string NOW! and tuck it safely away in our little cave address of 40D300 until the SaveAs routine.
Here's the code I came up with to do that, starting with a jump just after the Loop above is finished processing each record:
Jump to Patch 2------------------------------------
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00403083(C)
|
:004030A0 E98BA10000 jmp 0040D22D ; jump to inline patch #2* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040D267(U)
|
:004030A5 C3 ret ; original code return address
--------------------------------------------------------
The 1st part checks to see if this record actually contains the word "CHECK". If not, we're not interested in it. There is also a check of this anti-crash Flag we set in the 1st Patch at :0040D28E which checks to see if the SaveAs Dialog box is open, in which case skip the check for "CHECK". I found that if you tried to Save or SaveAs while continuing to monitor with Filemon, the program would sometimes roll over and die on the :0040D23A cmp statement when [esi-08] contained FFFFFFFF. We will reset the flag in a later patch. Note that after the Loop above has processed the record, ESI is pointing to the very end of the record table, so the Path entry is referenced 3 DWORDs back by [ESI-0C] and the entry containing "CHECK" is referenced by [ESI-08].
Patch 2----------------------------------------------
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004030A0(U)
|
:0040D22D 60 pushad ; save all registers
:0040D22E A100D44000 mov eax, dword ptr [0040D400] ; anti-crash Flag = 1 if SaveAs Dialog box is open
:0040D233 85C0 test eax, eax ; if it is,
:0040D235 7530 jne 0040D261 ; ignore our patch, return to program code
:0040D237 8B4EF8 mov ecx, dword ptr [esi-08] ; mov the address containing 'CHECK' into ecx
:0040D23A 813943484543 cmp dword ptr [ecx], 43454843 ; cmp with hex for "CEHC"
:0040D240 7527 jne 0040D261 ; if no match return to program code
:0040D242 BF00D34000 mov edi, 0040D300 ; move our hiding place for the Path into edi
:0040D247 57 push edi ; save edi for later
:0040D248 51 push ecx ; save ecx for later
This part clears 256 bytes starting at our buffer address of 40D300, which we've MOVed into edi. STOSD stores eax at the address specified by es:di (edi). di is then incremented or decremented according to the size of the instruction and the value of the direction flag. Repeat until cx is 0. (40h x Store a DWORD (8) = 256 bytes). 256 characters for just the directory path is overkill anyway because the OPENFILENAME nMaxFile value is limited to 256 including the filename string (though this could always be reversed ;).
:0040D249 FC cld ; clear direction flag for positive incrementation of edi
:0040D24A B940000000 mov ecx, 00000040 ; initialize cx
:0040D24F 33C0 xor eax, eax ; fill eax with 00000000
:0040D251 F3 repz ; repeat next instruction until cx=0, decrement cx by 1
:0040D252 AB stosd ; store eax in edi, increment di by 4
:0040D253 59 pop ecx ; restore ecx
:0040D254 5F pop edi ; restore edi (40D300)
Now that we've cleared the buffer of any previous data, we'll fill it up byte by byte with the Path string which is addressed at [ESI-0C], (or [65F658] in this case). If we put that address in esi, LODSB will load ds:si into the al register and increment the si register by 1 byte. STOSB then stores al in edi and the LOOP repeats things until cx=0. To initialize cx we need to know the length of the Path string. Since we've already stored the starting address [65F65C] of the subsequent string "CHECK" in ecx (method to madness? ;-), we can subtract the starting address of the Path from it to get the length+1.
:0040D255 8B76F4 mov esi, dword ptr [esi-0C] ; address Path = 412E1E
:0040D258 2BCE sub ecx, esi ; address "CHECK" (412E29) - 412E1E = 0B
:0040D25A 49 dec ecx ; length of Path string = 0A
:0040D25B FC cld ; clear direction flag
:0040D25C AC lodsb ; load each character of Path into al
:0040D25D AA stosb ; store al in es:di (40D300...)
:0040D25E E2FC loop 0040D25C ; repeat until cx=0, decrement cx
:0040D260 90 nop ;just a nop
This part just restores everything we've messed up and returns to program code.
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0040D22F(C), :0040D23D(C)
|
:0040D261 61 popad ; restore all original registers
:0040D262 5F pop edi ; replace original code
:0040D263 8BC3 mov eax, ebx
:0040D265 5E pop esi
:0040D266 5B pop ebx
:0040D267 E9345EFFFF jmp 004030A5 ; return to program code
End Patch 2------------------------------------------
You still here? Good stuff. Now if we go back to that line in the first patch
:0040D286 C744244400D34000
mov [esp+44], 0040D300 ;
our new lpstrInitialDir
you
can see how we got the directory path into 40D300.
Almost done. We need to reset the anti-crash Flag, which tested to see if the SaveAs Dialog box was open while monitoring was still going on. This must be done (in case the user selects Cancel) immediately after the call to GetSaveFileNameA, which opens the Dialog box and waits for user input.
Jump to Patch 3------------------------------------
* Reference To: comdlg32.GetSaveFileNameA, Ord:000Bh
|
:00404032 E8A5220000 Call 004062DC
:00404037 85C0 test eax, eax
:00404039 E968920000 jmp 0040D2A6 ; jump to inline patch #2
:0040403E 90 nop ; nop for byte adjustment* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00403FB3(U), :0040D2B6(U)
|
:0040403F 8D8C248C000000 lea ecx, dword ptr [esp+0000008C] ; original code
---------------------------------------------------------Patch 3------------------------------------------------
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00404039(U)
|
:0040D2A6 C70500D4400000000000 mov dword ptr [0040D400], 00000000 ; reset Flag
:0040D2B0 0F842C6FFFFF je 004041E2 ; restore original code
:0040D2B6 E9846DFFFF jmp 0040403F ;return to program code
End Patch 3------------------------------------------------
The very last thing to be done
(halelujah!) is to get rid of the original code line which pushed NULL into
lpstrInitialDir. :00404008 896C2448 mov
dword ptr [esp+48], ebp
You can either nop it out or just repeat the previous line of 4 bytes:
:00404008 896C2444 mov dword ptr [esp+44], ebp
Adding an Accelerator Key:
Why not? It's pretty easy. Don't even need an inline patch, just a resource editor. Save has Ctrl-S as an accelerator, how about Ctrl-D as one for SaveAs? Oh wait a minute, that wouldn't be too wise ;) How about Ctrl-V?
Using an accelerator is the same as choosing a menu item: Both actions cause Windows to send a WM_COMMAND or WM_SYSCOMMAND message to the corresponding window procedure. An application processes an accelerator WM_COMMAND message in exactly the same way as the corresponding menu item WM_COMMAND message. However, the WM_COMMAND message contains a flag that specifies whether the message originated from an accelerator or a menu item, in case accelerators must be processed differently from their corresponding menu items.
I won't go into too much detail on working with WM_COMMAND, it's been covered elsewhere, but if you set a BMSG Hwnd WM_COMMAND on the Hwnd of the Menu bar and select the Save accelerator Ctrl-S, you see:
Break due to BMSG 044C WM_COMMAND (ET=2.43 seconds) hWnd=044C wParam=9C47 lParam=00010000 msg=0111 WM_COMMAND
wParam is the Resource Command in hex of the Save function and lParam is the Accelerator identifier Flag spoken of in the above definition. If you select Save from the Menu, lParam is 0. These two are summed to give the value in eax in the code below, which as you see doesn't matter after being ANDed with FFFF. Now trace back into program code (which is the address you see PUSHed (4044B0) as soon as you break in Kernel.Alloc , or use the K32Thk1632Prolog trick). Trace a bit and you'll see:
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00404FE6(C)
|
:00405142 8BB424A0050000 mov esi, dword ptr [esp+000005A0]
:00405149 8BC6 mov eax, esi ;19C47 for Ctrl-S, 9C47 for Save
:0040514B 25FFFF0000 and eax, 0000FFFF ; now becomes 9C47 in either case
.
.
* Possible Ref to Menu: LISTMENU, Item: "Save Ctrl+S"
|
:00405172 3D479C0000 cmp eax, 00009C47
:00405177 0F8F8D000000 jg 0040520A
:0040517D 7463 je 004051E2 ; go to Save routine
.
.
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00405177(C)
|
* Possible Ref to Menu: LISTMENU, Item: "Save As..."
|
:0040520A 3D4C9C0000 cmp eax, 00009C4C ; Resource Command in hex of SaveAs
:0040520F 0F8581060000 jne 00405896
This is all we need to know. We can add a new Accelerator resource with BRW (Exescope couldn't handle it for me). For the Save Ctrl-S the parameters are ["S" 40007 Virtkey, NonInvert, Control] (40007 is dec for 9C47). We can add a new one with the parameters ["V" 40012 Virtkey, NonInvert, Control] (40012 is dec for 9C4C). Et Voila.
Postamble:
Well, not much to say. I'm pretty well ambled out at this point. I know that I certainly learned a heckuva lot doing a pure reversing job on a beloved Tool. I repeat that I've only tested this fully on my Win98SE system and would appreciate comments on how it performs on other systems, or if you find any errors. It's all based on a (hopefully not) flimsy premise that the word "CHECK" will be in the record that defines the directory pathname. As I mentioned, you may have to use the word "QUERY" as a marker with Explorer not filtered from the output for Win95, or find some other way to extract the directory pathname.
As usual, Greetz to +Sandman's Newbies Forum ;-)
Cheers for now,
Kayaker (ww_paddler(at)hotmail.com)