« Using If-Modified-Since in HTTP Requests | Main | A Few Tips For Taking a SQL Server Database Offline »
June 25, 2009
Patching a Crash in Kensington MouseWorks
A co-worker had trouble installing the latest version of an application I've been working on. The log file showed that the problem was an access violation when our setup program called MsiInstallProduct. This seemed very unusual, since Windows APIs return error codes instead of crashing. I decided to see where the crash was happening by running the installer under WinDbg.
I set WinDbg to immediately break on exceptions and launched the app. It stopped almost immediately with the following callstack. The crash location is in kwm_dll.dll, which is a hook DLL installed by Kensington MouseWorks.
kmw_dll!CallWndProcFunc+0xa8 USER32!DispatchHookA+0x101 USER32!fnHkINLPCWPSTRUCTA+0x4f USER32!__fnDWORD+0x24 ntdll!KiUserCallbackDispatcher+0x13 USER32!NtUserSetFocus+0xc USER32!CreateDialogIndirectParamAorW+0x33 USER32!CreateDialogParamW+0x49 msi!CBasicUI::CreateProgressDialog+0x35 msi!CBasicUI::CheckDialog+0x47 msi!CBasicUI::SetProgressData+0x58 msi!CBasicUI::Initialize+0x11c msi!MsiUIMessageContext::Initialize+0x230 msi!MsiUIMessageContext::RunInstall+0x22 msi!RunEngine+0xe0 msi!MsiInstallProductW+0xa1 BatchUpd!Application::RunCommandInstall+0x1f5 BatchUpd!Application::Run+0x11e3 BatchUpd!wWinMain+0x102
Disassembling the crash location showed the following (crashing code in bold; ebp does not contain a valid address).
mov eax,dword ptr [ebp-4] mov ecx,dword ptr [eax] push ecx mov edx,dword ptr [ebp-4] mov eax,dword ptr [edx+4] push eax mov ecx,dword ptr [ebp-4] mov edx,dword ptr [ecx+0Ch] push edx call kmw_dll!CallWndProcFunc+0x12f0 (10004210) add esp,0Ch mov eax,dword ptr [ebp+10h] push eax mov ecx,dword ptr [ebp+0Ch] push ecx mov edx,dword ptr [ebp+8] push edx mov eax,dword ptr [kmw_dll!ShowOptsProc+0x6b94 (1000e0a4)] push eax call dword ptr [kmw_dll!ShowOptsProc+0x7c80 (1000f190)] mov esp,ebp pop ebp ret 0Ch
One interesting thing about the code is that it looks like a Debug build (or a Release build with no optimizations): the disassembly is straightforward and seems like it has a one-to-one correspondence with the putative source code. The other thing of note (not shown above) is that the base address of the DLL is set to the default 0x10000000, which is a poor choice for a hook DLL that will be loaded into every process on the system.
ebp should be preserved across the function call, so I looked at the function that was just called (at address 0x10004210). I've added a few explanatory comments based on my understanding of what it's doing.
push ebp ; save caller's value of ebp mov ebp,esp ; standard function prologue sub esp,offset+0x87 (00000088) ; BOOL bLocal0; char szLocal1[132]; cmp dword ptr [ebp+0Ch],0 ; if (param2 == 0) je kmw_dll!CallWndProcFunc+0x130b (1000422b) ; goto label0; mov dword ptr [ebp-88h],0 ; bLocal0 = FALSE; jmp kmw_dll!CallWndProcFunc+0x1315 (10004235) ; goto label1; label0: mov dword ptr [ebp-88h],offset (00000001) ; bLocal0 = TRUE; label1: mov eax,dword ptr [ebp-88h] ; push bLocal0 push eax lea ecx,[ebp-84h] ; push &szLocal1[0] push ecx mov edx,dword ptr [ebp+10h] ; push param3 push edx mov eax,dword ptr [ebp+8] ; push param1 push eax call kmw_dll!ShowOptsProc+0x16e0 (10008bf0) ; fn(param1, param3, szLocal1, bLocal0) add esp,10h ; clean up parameters (C calling convention) mov esp,ebp ; "free" locals pop ebp ; restore caller's value of ebp ret
This function is allocating 0x88 (i.e., 136) bytes for local variable storage: enough for an int (or BOOL) and a 132 byte buffer. If this buffer were overflowed, the stack would be overwritten and ebp would be corrupted upon return. Some internet searching turns up posts that discuss a similar issue, stating that "Kensington MouseWorks ... crashes ... if the executable path is longer than 128 characters"; this seems to match our situation. Indeed, dumping the bytes at the old value of ebp-84h shows the full path of our setup application, which is too long for the buffer.
Since the buffer is stack allocated, it would be trivial to change its size by editing the instructions that create and reference the local variables. At a minimum, the buffer should be capable of storing MAX_PATH characters. Because this function doesn't supply the actual buffer length to the function it calls, we can make it as long as we (reasonably) want. I decided to increase the size for storage of locals in this function to 300 bytes. In version 6.3.2.4 of kmw_dll.dll (which seems like it may be newer than the latest available version, published in February 2006), this can be accomplished by editing the following bytes in the file. These changes simply change the numbers 136, -136, and -132 (which are the three offsets used in the code above) to 300, -300, and -296.
| Offset | New Bytes |
| 0x4215 | 2C 01 |
| 0x4221 | D4 FE |
| 0x422D | D4 FE |
| 0x4237 | D4 FE |
| 0x423E | D8 FE |
The buffer should now be large enough to hold a file name up to MAX_PATH bytes long. With this new DLL installed in the C:\Windows\System32 folder, the setup program is able to launch the MSI and installation completes successfully.
Posted by Bradley Grainger at June 25, 2009 7:45 PM
Trackback Pings
TrackBack URL for this entry:
http://blog.logos.com/mt-cgi/mt-tb.cgi/298
Comments
First of all, WOW! Second, with skills like this (which I assume you also comfortable with the old SoftICE and other such tools), I wager that you have cracked a few games or apps. No need to confess here, but these are identical skills needed to undo what others have done. Still, this is beyond normal. Your co-worker minimally owes you a lunch.
Posted by: Steve Rives at June 25, 2009 10:47 PM
Yes, might be best to leave the confessions for a venue that's not my employer’s official blog... :-)
Fortunately, this application was not designed to resist reverse engineering. It didn't even appear to have the standard optimizations you'd expect to see as part of a Release build that make the code harder to understand.
For a similar story to this one (and perhaps a little more information on how it's done), you may like: http://www.codexon.com/posts/patching-a-program-without-source-code-how-to-be-like-the-skype-hacker-for-newbies
Posted by: Bradley Grainger
at June 25, 2009 10:57 PM