"One of the best ways to get yourself a reputation as a dangerous citizen these days is to go about repeating the very phrases which our founding fathers used in the great struggle for independence." --Charles Austin Beard
OllyDbg with .chm help - how to add functionality to a closed-source application
I wrote this article a while ago in Hungarian (my native language), and now I have decided to translate it, and share it on HTS. I hope you will enjoy this stuff.
OllyDbg - the best ring3 debugger ever :) - has a very handy feature: if you give Olly the path of the Win32 API documentation, you can get info on an API function by highlighting it, and pressing CTRL+F1. Well, this works fine on XP, but it doesn't work on Vista. Why? As you may already know, Microsoft made .hlp help files deprecated since Vista. Instead, the .chm format is recommended. Olly uses the WinHelp API (this function is used to open .hlp helps) to open the API documentation. When a program uses WinHelp on Vista (or you just simply try to open a .hlp file), a window pops up that tells you, that .hlp is dead and buried. Vista is good, CTRL+F1 is good too, so something had to be done.. Yeah, well, I could just simply download WinHlp32.exe for Vista, but hey, what's the fun in that? :)
.hlp to .chm
First, I did the boring part of the job: I converted the Win32 API .hlp file to .chm format. This can be done with two free tools: HelpDeco and HTML Help Workshop. Uncompress the .hlp file...
helpdeco.exe win32.hlp /y
... and generate a content file:
helpdeco.exe win32.hlp /y /c
When this is done, start a new project in HTML Help Workshop, and check the Convert WinHelp project checkbox. From now on, just follow the wizard, and your reward will be a brand new, shiny .chm file!
There is an API function similar to WinHelp which deals with .chm help files: HtmlHelp. So, my plan was simple:
1. write a function, that wraps HtmlHelp, so it can be called with the same parameters as WinHelp
2. find a code cave (a bunch of 0x00 bytes) in Olly, and insert the wrapper there
3. replace all WinHelp calls with a call to my wrapper.
Indeed, I could write the wrapper function inside Olly into the code cave, but I choose a more comfortable way. I fired up WinASM, and wrote the function and a little framework to test it. Here is the source code of the function:
The WinHelp function's uCommand parameter can have a lot of values; I implemented only the ones, that occur in Olly (HELP_PARTIALKEY, HELP_CONTEXT and HELP_INDEX).
OK, I got the wrapper, I had to insert it into Olly. I opened the above mentioned framework in Olly, highlighted the wrapper, and copied the opcodes to the clipboard with the Asm2Clipboard plugin. Then, I opened Olly in - guess what - another instance of Olly, did a little digging to find a code cave, and binary pasted the content of the clipboard. NOTE: I have cheated a bit with the code below. It doesn't look like this right after inserting it; a few adjustments have to be made. I'll explain these changes later.
The wrapper uses HtmlHelp which is exported by HHCtrl.ocx. This file is not loaded by Olly, so I had to load it, and get the address of the HtmlHelp API function. This piece of code is quite simple, so I didn't use WinASM this time.
A little explanation: instructions through 0x004AF6C3 to 0x004AF6D3 load HHCtrl.ocx and get the address of the HtmlHelp function. Two APIs are used to achieve this task: LoadLibrary and GetProcAddress. If I assemble the call of these functions in Olly as CALL LoadLibrary and CALL GetProcAddress, Olly will use their actual address. This can be a problem, because the actual address of API's can vary (e.g.: ASLR, different version of OS). The problem can be eliminated by calling the APIs through the jump thunk table. So, I had to find the functions in the jump thunk table, and CALL those addresses (e.g. instead of CALL LoadLibrary, I had to assemble CALL 4AF138):
There are some instructions, I haven't explained yet, so let's move on! The instruction at 0x004AF6BC was originally located near the entry point of Olly. I've replaced it with the JMP, which jumps to this piece of code, so I had to insert it here. Here is the JMP at near EP (at 0x00401012):
The PUSHAD stores the values of the general purpose registers before my code runs, and the POPAD restores these values after my code. This is important, because a messed up register value could crash the program. The MOV at 0x004AF6D8 stores the address of HtmlHelp at address 0x004AF695 (this address is also located in the code cave). And now is the time to tell you more about the above mentioned adjustments to the wrapper code. All CALL HtmlHelp instructions had to be changed to CALL DWORD PTR[4AF695], because the address of HtmlHelp is stored at 0x004AF695. The last instruction, the JMP at 0x004AF6DE jumps back to near the entry point, from where we have jumped here earlier.
One more thing: we need the strings "HHCtrl.ocx" and "HtmlHelpA" to be able to load the library, and get the address of the function. Of course this is not a big deal, there are plenty of space in the code cave, so we can put them there:
If we save the modifications to the .exe, and try to run it, it will crash. Uh-oh, now what? Never fear, I is here (yeah, well.. who can tell me, which movie is this quote from? :) ). The code cave is in the .text section, which is a non-writable section, yet we try to write the address of HtmlHelp to 0x004AF695, which is indeed in the .text section. We can easily make .text section writable, there are a lot of tools capable of doing the job. Or it can be done manually, with a hex editor, but we are not masochists. At least, I am not, so I have used PEditor. Here is how to do it with that tool: open the .exe with PEditor, click on the sections button, right click on the .text section, choose edit section, than char. wizard. Now check the writeable checkbox, click on take it!, and you're ready.
Now the program runs normally. All we have to do is replace all WinHelp calls with a call to the wrapper. We could search for all WinHelps, and replace them with a CALL 4AF6EE (I have the wrapper at that address), but we are lazy, so we take another path. That path leads us to the jump thunk table. We search for WinHelp in the table, and replace the jump with a JMP 4AF6EE. Here I have done the change (at 0x004AF5FA):