Jazzing up Regmon

Adding a DLL to reopen logs with Drag and Drop support

by Kayaker

published by +Tsehp march 2001

Target: Regmon v4.32
              http://www.sysinternals.com

Tools Used: SoftIce, W32Dasm, Win32 Programmer's Reference, BRW, APISpy32, MASM, oh yeah, and Regmon.

Included files : regmon.zip

Preamble: Greetings Fravias! Our beloved Regmon, window on the Registry, what would we do without it? And that wonderful feature that allows you to double-click on an entry and navigate directly to the Registry Key, nice touch. But what about after you've saved the log as a text file? That double-click convenience is gone. Why don't we get it back by reopening those old logs into the Regmon Listview window? A little bit of reversing, a little bit of coding, and it just might work. I find the result particularly handy for cleaning up those "hidden" registry entries you may have forgotten about in long-dead projects. What I'm really hoping to do is just give an example of how writing your own dll can expand the possibilities of reversing. Harlequin nicely demonstrated this in his 'W32Dasm Disassembled' series of tuts, which I highly recommend reading.

Of course if you only want to test the results, just run the patcher, which should be included with this tutorial, on Regmon v4.32 and add the dll to the program directory. Make sure you've got the most recent version dated Nov 20/00, file size 86,016 bytes because there have been changes without the version number being updated and I didn't implement any version checking! Even better, patch Regmon yourself and modify the dll source code I've included as you wish!! Let's get to it.

Modifying the exe file

1. Adding a Resource:

OK, what we'd like to do is to be able to open a saved Regmon log by the usual Open common dialog box via the menu and also add the convenience of dragging and dropping a file directly onto the main window. The first step is to add a new menu item. I used Borland Resource Workshop, but Exescope, Resource Hacker or the like should work as well. Under the Menu/Listmenu resource I added a new Menu item as
MENUITEM "&Open\tCtrl+O", 40000

40000 is our new MenuItemID I chose relatively arbitrarily and converts to 9C40 in hex. Then, to allow us to use the shortcut key Ctrl-O, under Accelerators I added
VK_O, 40000, NOINVERT, CONTROL, VIRTKEY

That's it for resource changes. Most of the real work will be done in the dll, but we do need to call that dll so we're going to have to find some empty bytes to put in the calling code. If you look at Regmon.exe in a PE editor you see that the Raw size of the .text section is D000 bytes but it's Virtual size is only CA4A bytes. Checking this out in a hex editor confirms that there's a nice big bunch of 0's in that section that we can use as a "cave". Two things we should do with the PE editor first is to change the Virtual Size to be equal to the Raw Size of D000, avoiding an Invalid Page Fault error, and then change the Code characteristics of the section from 60000020 to E0000020, which allows the memory to be 'writeable'.

2. A painful, but necessary look at the WM_COMMAND Message:

System-defined Windows messages such as WM_COMMAND are used to communicate with a program and control the operation of its various windows, controls, etc. We need to intercept this command in the code normally called whenever a Menu item is selected and ask if its 2nd parameter, wID, is the MenuItemID of our newly created File/Open resource, in which case would you please call the dll we have yet to write. Now there's a simple way to find the code which processes the WM_COMMAND message by searching in the W32Dasm disassembly, and a more involved way by using SoftIce to break on the message directly. Since we're going to have to break on a Windows message command directly anyway when we try to implement the drag and drop feature, I'll show both methods.

The WM_COMMAND message is sent when the user selects a command item from a menu, when a control sends a notification message to its parent window, or when an accelerator keystroke is translated.

WM_COMMAND
wNotifyCode = HIWORD(wParam); // notification code
wID = LOWORD(wParam); // item, control, or accelerator identifier
hwndCtl = (HWND) lParam; // handle of control

Parameters
wNotifyCode
Value of the high-order word of wParam. Specifies the notification code if the message is from a control. If the message is from an accelerator, this parameter is 1. If the message is from a menu, this parameter is 0.
wID
Value of the low-order word of wParam. Specifies the identifier of the menu item, control, or accelerator.
hwndCtl
Value of lParam. Identifies the control sending the message if the message is from a control. Otherwise, this parameter is NULL.

First you need to find the Window Handle (hWnd) of the main Regmon window, which includes the Menu bar. You can use a utility such as WinShow or WinDowse or simply type 'HWND Regmon' in the SoftIce command window and retrieve the hWnd of the 'RegmonClass' Class Name. Then you can set a 'BMSG hWnd WM_COMMAND' breakpoint, select the new Open menu item, and SoftIce will break in Kernel.Alloc with something like:

Break due to BMSG 0D08 WM_COMMAND (ET=3.25 seconds) 
hWnd=0D08 wParam=9C40 lParam=00000000 msg=0111 WM_COMMAND

If you used the Ctrl-O Accelerator, lParam would be 00010000. Now we need to get back into program code. If you check out the address you broke at in SoftIce, you should be in Kernel.Alloc code and should see:

xxxx:xxxx 6668C04E4000 PUSH 00404EC0
xxxx:xxxx 666800000300 PUSH 00030000

The address 404EC0 in the first PUSH statement will be where you'll get back into program code. Now you can set a breakpoint on this address directly by specifying the Code Segment (CS) selector your programs usually use. If you don't remember it offhand you can type 'MAP32 Regmon' in SoftIce and note the CS selector for the CODE section of Regmon under "Address". For my system this is 0167, so I set a
BPX 167:404EC0 and press F5.

Sidenote: The other way to return to program code after selecting a menu item that some people may be more familiar with is the method of setting a BPX K32Thk1632Prolog. This is outlined in several other 'Menu Enabling' tuts, but briefly you can access the Prolog/Epilog routines in Kernel32.dll which call and restore system message calls within a program by setting the above breakpoint, pressing F5, then F11 and you reach the middle call here:

:BFF943FD CALL KERNEL32!K32Thk1632Prolog
:BFF94402 CALL BFF735FD
:BFF94407 CALL KERNEL32!K32Thk1632Epilog

F8 into the Call BFF735FD, trace and step into the Call GS:[ESI+08] you see a few lines down, and you'll be at 404EC0, the beginning of what is processing code for "WM_" messages.

:BFF73637 65FF5608 CALL GS:[ESI+08] ;Call 404EC0
:BFF7363B 8BE7 MOV ESP,EDI ;return address after the call

Now Windoze begins this code section by looking at the Msg parameter and asking - "is this 111, the WM_COMMAND message?" If yes, jump somewhere and continue processing the rest of the parameters, if it's some other system message, jump somewhere else. So start single step tracing, keeping track of what is happening, and you should very quickly find where all the MenuItemID's are being compared and jumping to whatever code will process the particular menu intructions.

:00405BBD 25FFFF0000 and eax, 0000FFFF ; this line masks out the lParam=10000 Accelerator key modifier
.
.
* Possible Ref to Menu: LISTMENU, Item: "Save Ctrl+S"
|
:00405BE4 3D479C0000 cmp eax, 00009C47 ;MenuItemID of "Save"
:00405BE9 7F69 jg 00405C54
:00405BEB 7463 je 00405C50
:00405BED 83F868 cmp eax, 00000068 ;MenuItemID of "Exit"
:00405BF0 743B je 00405C2D

* Possible Ref to Menu: LISTMENU, Item: "About"
|
:00405BF2 3D2C010000 cmp eax, 0000012C ;WE CAN JUMP TO OUR INLINE PATCH HERE
:00405BF7 0F8504080000 jne 00406401

From the disassembly it's easy to guess that the other way to get here would be to note one of the MenuItem ID's you see in W32Dasm, such as the Save 9C47h ID, and do a text search for it.

Great, finally we find a place to jump to our inline patch. The "cave" address I chose in the file was at hex DC00, so the statement

:00405BF2 3D2C010000 cmp eax, 0000012C
can be changed to
:00405BF2 E909800000 jmp 0040DC00

and the RET address we need to remember is at
:00405BF7 0F8504080000 jne 00406401

3. Calling the dll:

Calling the dll is pretty straightforward, you really only need 3 API calls; LoadLibraryA to map the named dll into memory, GetProcAddress to retrieve the address of the exported dll function, and FreeLibrary upon return from calling the dll to free the memory used. We need to write the name of the dll and its function, which I called OpenLog, in the cave as well, which I chose to insert at hex offset DB00. I also added 2 MessageBox strings.

0000DB00 5265 676D 506C 7573 2E64 6C6C 0000 0000 RegmPlus.dll....
0000DB10 4F70 656E 4C6F 6700 0000 0000 0000 0000 OpenLog.........
0000DB20 4361 6E27 7420 4C6F 6164 2052 6567 6D50 Can't Load RegmP
0000DB30 6C75 732E 646C 6C00 0000 0000 0000 0000 lus.dll.........
0000DB40 446C 6C20 4675 6E63 7469 6F6E 204E 6F74 Dll Function Not
0000DB50 2046 6F75 6E64 0000 0000 0000 0000 0000  Found..........
0000DB60 0000 0000 0000 0000 0000 0000 0000 0000 ................

And here's the patch code which I wrote with HIEW:

:0040DC00 3D409C0000 CMP EAX,00009C40 ;is MenuItemID for File/Open?
:0040DC05 740A JZ 0040DC11 ;if yes, continue
:0040DC07 3D2C010000 CMP EAX,0000012C ;restore original program code
:0040DC0C E9E67FFFFF JMP 00405BF7 ;return to program
:0040DC11 60 PUSHAD ;save all registers
:0040DC12 6800DB4000 PUSH 0040DB00 ;push name of dll
:0040DC17 FF15E4E04000 CALL [KERNEL32!LoadLibraryA] ;load dll
:0040DC1D 0BC0 OR EAX,EAX ;returned a successful handle?
:0040DC1F 7513 JNZ 0040DC34 ;if yes, continue
:0040DC21 6A00 PUSH 00 
:0040DC23 6A00 PUSH 00 
:0040DC25 6820DB4000 PUSH 0040DB20 ;push error message
:0040DC2A 6A00 PUSH 00 
:0040DC2C FF153CE24000 CALL [USER32!MessageBoxA] 
:0040DC32 EB36 JMP 0040DC6A ;exit from patch
:0040DC34 A360DB4000 MOV [0040DB60],EAX ;save LoadLibraryA dll handle
:0040DC39 6810DB4000 PUSH 0040DB10 ;push dll exported function name
:0040DC3E 50 PUSH EAX ;push LoadLibraryA dll handle
:0040DC3F FF15E8E04000 CALL [KERNEL32!GetProcAddress] 
:0040DC45 0BC0 OR EAX,EAX ;returned a successful address?
:0040DC47 7513 JNZ 0040DC5C ;if yes, continue
:0040DC49 6A00 PUSH 00 
:0040DC4B 6A00 PUSH 00 
:0040DC4D 6840DB4000 PUSH 0040DB40 ;push error message
:0040DC52 6A00 PUSH 00 
:0040DC54 FF153CE24000 CALL [USER32!MessageBoxA] 
:0040DC5A EB02 JMP 0040DC5E ;free memory, exit patch
:0040DC5C FFD0 CALL EAX ;CALL DLL
:0040DC5E FF3560DB4000 PUSH [0040DB60] ;push LoadLibraryA dll handle
:0040DC64 FF15E0E04000 CALL [KERNEL32!FreeLibrary] ;free memory
:0040DC6A 61 POPAD ;restore all registers
:0040DC6B 8BE5 MOV ESP,EBP ;replace ESP with Frame Pointer
:0040DC6D 83EC1C SUB ESP,1C ;reference return address
:0040DC70 C3 RET ;return to Kernel code

That's it for the dll calling code. It doesn't matter that the dll is still a black box at this point, the code can be used to call any dll.

Now there's one thing that needs explaining in the above code that will also be pertinent when we implement the drag and drop feature. You may have wondered, especially if you're familiar with inline patching, is "Why the hell did he use those last 3 lines in the patch code?" Why not just jump back into the program code at 405BF7 from whence we came? The simple answer is that I found it was the easiest way to deal with a little "glitch" which called the About box if the dll wasn't actually present. This had to do with the state of the Zero flag on returning to program code after the LoadLibraryA call failed. I could have used PUSHF and POPF statements to preserve the Flags, but because of the 2nd drag and drop patch to come, this didn't work. All that plus I just thought it was a cool way to end the patch once the dll's work was done ;-) That said, we now need to take:

4. Another painful, but eminently useful look at Local Variable referencing using EBP:

Let's go back and take a look at the very start of this WM_ processing code section again at 404EC0. I'm not going to go into any detail about the stack, but it is important to know how to access certain parameters. Normally a stack frame is set up when you enter a call and contains the parameters to the function, its local variables, and the data necessary to recover the previous stack frame, including the value of the instruction pointer at the time of the function call. While the stack pointer ESP changes during the call as items are PUSHed and POPed on and off the stack, EBP always remains the same (unless deliberately changed), therefore it can be used as a fixed reference address, known as the Frame Pointer. Local variables can be accessed as negative offsets from this address, and passed function parameters can be accessed as positive offsets from it. The first code lines in a call are what is known as the procedure prologue and is usually shown as something like:

push ebp ;save previous ebp
mov ebp, esp ;set ebp for the frame pointer
sub esp, 10h ;allocate space on stack for local variables (example here is 10h for 4 DWORDS)

To get the values of the local variables or of the passed parameters, you simply need to reference EBP, i.e.:

mov eax, [ebp-4] ;copy local variable 1 into eax
mov ebx, [ebp+8] ;copy passed parameter 1 into ebx

While the real procedure prologue in the Regmon code doesn't look exactly like this, it does serve the same purpose in setting up the stack frame.

:00404EC0 sub esp, 000003B4 ;allocate space on stack for local variables
:00404EC6 push ebx
:00404EC7 mov ebx, [esp+000003C0] ;copy Msg parameter to ebx
:00404ECE push ebp ;save previous ebp
:00404ECF push esi
:00404ED0 cmp ebx, 0000002B ;1st Msg comparison (2B = WM_DRAWITEM)

If you display the stack as soon as you enter the call at 404EC0 with 'dd esp' you see:

EBP=0065FC58 ESP=0065FC3C
:0065FC3C BFF7363B 00000D08 00000111 00009C40
:0065FC4C 00000000

The 1st DWORD is the RET address which is within Kernel code from where the call was made. You saw this if you used the K32Thk1632Prolog route to get here. The other DWORDS you can recognize as the parameters listed by the BMSG hWnd WM_COMMAND break. As I mentioned, the Regmon procedure prologue isn't quite "textbook", but we can easily reference the local variables we might want to access ourselves:

[EBP - 1C] = Return address of Call = BFF7363B
[EBP - 18] = hWnd = D08
[EBP - 14] = Msg = 111
[EBP- 10] = wParam = 9C40
[EBP- C] = lParam = 0

Now if we go back to the final lines in the patch:

:0040DC6A 61 POPAD ;restore all registers
:0040DC6B 8BE5 MOV ESP,EBP ;replace ESP with Frame Pointer
:0040DC6D 83EC1C SUB ESP,1C ;reference return address (EBP-1C)
:0040DC70 C3 RET  ;return to Kernel code

you can see where I dug up the return address from, by referencing EBP. Essentially we've set up the corresponding procedure epilogue that normally finishes a call. "Fine, fine", I hear you say, "But why is he going into all this crap?" Because we'll need it later when adding drag and drop support. Really, I promise.

5. Adding Drag and Drop support:

The ability for a control to recognize when files are dragged and dropped on it is specified during its creation in its CreateWindowExA call, or can be modified runtime with the DragAcceptFiles call. If you look at the partial APISpy32 output below you see that the ListView window, where the Regmon log entries normally reside, is a child subclass of the main window class. The nested nature of the output shows the parent/child relationship, which you can also see when you type 'hWnd Regmon' in SoftIce. What this means is that if we add drag and drop capability to the main window, the child window will also be affected. We could just add it to the child SysListView32 window alone, but I decided to add it to the full window.

00406800:CreateWindowExA(DWORD:00000000,LPSTR:00410B4C:"RegmonClass",...
004047EE:           CreateWindowExA(DWORD:00000000,LPSTR:00410300:"SysListView32",...
004047F4:           CreateWindowExA = C70 (REGMON.EXE)
00406806:CreateWindowExA = D08 (REGMON.EXE)

The 1st parameter of CreateWindowExA determines the extended style of the window which can include WS_EX_ACCEPTFILES, which specifies that a window created with this style accepts drag-drop files. We need to look up the API constant for this value and find it is 10h. You should be able to find an API constants listing somewhere on the Net if you don't already have one. What do you see if you drag and drop a file onto the window at this point? Nothing, just some weird cursor. If you look at the CreateWindowExA address shown by APISpy32 in W32Dasm you see:

* Possible StringData Ref from Data Obj ->"RegmonClass"
|
:004067F9 684C0B4100 push 00410B4C
:004067FE 6A00 push 00000000 ;extended style, change to 10h

* Reference To: USER32.CreateWindowExA, Ord:005Ah
|
:00406800 FF15A8E24000 Call dword ptr [0040E2A8]

Now we simply change the 6A00 at 4067FE to 6A10 in a hex editor at byte location 67FE. Drag and drop a file now and we see the familiar "drag 'n drop" cursor. Nothing still happens, but the window is now receiving a WM_DROPFILES message which we can break on and find a 2nd jump point to our inline patch.

The WM_DROPFILES message is sent when the user releases the left mouse button while the cursor is in the window of an application that has registered itself as a recipient of dropped files.

WM_DROPFILES
hDrop = (HANDLE) wParam; // handle of internal drop structure

Parameters
hDrop
Value of wParam. Identifies an internal structure describing the dropped files. This handle is used by the DragFinish, DragQueryFile, and DragQueryPoint functions to retrieve information about the dropped files.


The actual makeup of the internal drop structure doesn't really concern us, just the address pointing to it, but for completeness it's structure is listed below. The actual filename(s) that were dragged and dropped are listed in ASCII after the final parameter.

struct DROPFILES
{
DWORD pFiles; // offset of file list
POINT pt; // drop point (client coords)
BOOL fNC; // is it on NonClient area and pt is in screen coords
BOOL fWide; // wide character flag   }

Now that we've forced Regmon to create its main windows with drag and drop recognition, if you set a BMSG hWnd WM_DROPFILES on the hWnd of the main window and drop a file, SoftIce will break with:

Break due to BMSG 0D08 WM_DROPFILES (ET=2.58 seconds)
hWnd=0D08 wParam=35DE lParam=00000000 msg=0233 WM_DROPFILES 

Again, you can use the address you see in the Kernel.Alloc code as soon as you break to get back to program code by setting a BPX CS:404EC0.

xxxx:xxxx 6668C04E4000 PUSH 00404EC0
xxxx:xxxx 666800000300 PUSH 00030000

You notice we're at the same address as when we did the WM_COMMAND message, obviously a main processing loop for WM_ messages.

If you display the stack immediately on entering this call you see:

EBP=0065FC58 ESP=0065FC3C
dd:esp
:0065FC3C BFF7363B 00000D08 00000233 00530DD8
:0065FC4C 00000000

The 1st DWORD is the return address for the call in Kernel32 code, then come the hWnd, Msg, wParam and lParam for the WM_DROPFILES message. Notice wParam is not the same as that listed by SoftIce at the WM_DROPFILES break, but it IS the address of the internal drop structure our dll will need to know when it invokes the DragQueryFileA API. Now comes the reason for knowing how to reference local variables by using the Frame Pointer EBP that we went through earlier. We can store the wParam address, which is always changing, into our own variable address in our "cave" by referencing [EBP-10]. That way our dll can retrieve it when it needs to.

[EBP - 18] = hWnd = D08
[EBP - 14] = Msg = 233
[EBP- 10] = wParam = 00530DD8 ;this is the value we need to know
[EBP- C] = lParam = 0

The program isn't fully set up for drag and drop yet, so we need to find a spot while it's still deciding what to do with us, and make a jump to our 2nd inline patch so we can process the WM_DROPFILES message ourselves. Trace a bit as before and we find:

:0040642A 81FB13010000 CMP EBX,00000113 ;is the Msg WM_TIMER?
:00406430 0F8421010000 JZ 00406557
:00406436 81FB00020000 CMP EBX,00000200 ;is the Msg WM_MOUSEMOVE?
:0040643C 0F84BDF2FFFF JZ 004056FF

406436 looks like a good spot, so we replace the code with

:00406436 E93A780000 jmp 0040DC75 ;jump to 2nd inline patch
:0040643B 90 nop
:0040643C 0F84BDF2FFFF je 004056FF

And on to the 2nd inline patch:

:0040DC75 81FB33020000 CMP EBX, 00000233 ;is the message WM_DROPFILES?
:0040DC7B 740B JE 0040DC88 ;if yes continue
:0040DC7D 81FB00020000 CMP EBX, 00000200 ;restore original code
:0040DC83 E9B487FFFF JMP 0040643C ;return to program code
:0040DC88 8B45F0 MOV EAX, [EBP-10] ;copy value of wParam to eax
:0040DC8B A368DB4000 MOV [0040DB68], EAX ;store wParam for access by dll
:0040DC90 E97CFFFFFF JMP 0040DC11 ;jump into dll calling patch

6. Final Considerations:

Before opening a log we want to stop any Regmon monitoring and clear the display. We will do this in the dll by using the SendMessageA API to send a WM_COMMAND message to the main window, specifying the MenuItemID values for Capture Events and Clear Display. By doing this we are essentially selecting the menu commands remotely. This is a very powerful way to control a program, so learning to use the SendMessageA command can be very useful. Since the Capture Events button is normally toggled on/off by the user, we need to know in which state it is in beforehand, if already off we don't want to send the message. By tracing the Capture Events routine from the menu with BMSG hWnd WM_COMMAND and monitoring for the MenuItemID of 9C52h, you can find that there is a flag in memory location 410270 which is monitored at:

:00405CB1  MOV BL,[00410270] ;0 for OFF, 1 for ON
:00405CB7  MOV ESI,[ESP+000003C8] ;hWnd
:00405CBE  TEST BL,BL ;clears or sets the Z flag
:00405CC0  SETZ AL ;sets al to 00 or 01 depending on Z flag

All we need to take from this is the memory address 410270 so the dll can monitor if the Capture Events flag is set to ON or OFF.


Writing the DLL

I'm not going to discuss much the details of the dll (Hallelujah!), it's pretty well commented in the source code I included. The dll is really only a "black box" anyway, the important thing is knowing that by making just a few minor changes in an existing program and calling a dll, the reversing possibilities are endless. At the very least it beats writing a huge long inline patch to do what you want!

We pass no parameters to the dll and nothing is returned. There are only 2 memory locations we need to know about, the Capture Events flag at 410270, and the WM_DROPFILES structure address, which we stored in our own address in the 2nd patch. The basic idea behind the dll is to treat the log file as one long Null-terminated string, which we can then use with the windows system messages LVM_INSERTITEM and LVM_SETITEM to populate the rows and columns of the ListView control. If you look at a Regmon logfile in a hex editor you see that while the "rows" are terminated with a regular LF/CR (0D0Ah), the "columns" are terminated with '09', not '00'. So the first step after opening the file is to scan it and replace all the 09's with NULL.

While you can open the file with ReadFile and manipulate it, I chose to map the file into memory. This is really quite a straightforward technique that can be used with program files as well (i.e. for making patchers, loaders), so it's a good one to know. 3 API's in, 3 API's out. The parameters you specify in the 1st three API's determine if you can make any changes to the memory image, and if they are written back to the file or not. Of course the Win32 Programmers Reference is mandatory, but it looks something like:

Map file into memory with CreateFile, CreateFileMapping, MapViewOfFile
Call your code
Unmap file with UnmapViewOfFile, CloseHandle (of CreateFileMapping object), CloseHandle (of file)

Well that's about all, the dll source code, written in MASM, will hopefully explain the rest of it.

Postamble:

This was my first major MASM coding job and I really learned a lot and had fun. The code seems fairly stable, pretty well any file can be opened up in the ListView window (with unpredictable output, remember it's the presence of 09 that dictates what will become a null terminated string). If you start monitoring again, any new entries will be appended to the log file sitting in the ListView window, so you need to clear the display if you want to monitor anything fresh. An added bonus is that it will also properly open up Filemon logs, restoring the double-click feature of Filemon. But then, who the hell really uses that anyway? In any case, I hope this gets some people thinking about new and exciting things they could do with reversing by writing dll's in the language of their choice. My sincere appreciation goes to Hz for beta testing, making several positive suggestions for improving the tut, and showing me where I goofed up. I highly recommend to all tutorial writers to get someone to thoroughly reverse their tut before publishing it. Even if that person is apologetic about "nit-picking", it can only help but improve the final product ;-) As always, comments, critiques, bug reports and coding improvements are welcomed.

Cheers,

Kayaker
Mar, 2001