Hotkey hijackers
Kapil Kapre | Wednesday, March 10, 2010

A while back, I consciously decided to reduce or stop all forms of instant messaging on the interwebs. It wasn't that I was getting interrupted every other minute, but more that I hated having to perform expensive context switches from whatever I was doing to responding to messages. The way I look at it, if you're online you either respond to messages within some time-upper-limit or just stay offline. Ofcource there were other options like blocking a few people, or logging-on in 'invisible mode', etc, but they didn't feel right.

This itself wasn't much of an issue, but I was using my IM client to get mail notifications so I needed to find a reasonable alternative for that. Being a programming nerd, I wrote a minimal imap client in python that would connect with gmail and throw up a little popup informing me of unread messages. I set it to execute every 4 hours. This combined with wincal.exe that I was using for event notifications worked fine until recently when I moved up to Win7 and realized that the wincal client app was replaced with some stupid windows live calendar bullshit. (Yes, you can copy the old binaries from Vista and get wincal working, but there are some other problems)

So, I decided to give google calendar a try, and use their client to get notifications on events. As it happens google's client on startup registers Win + <KEY> hotkeys with User32!RegisterHotKey that I was using elsewhere. Also, in classic Google (and Apple?) fashion they remove all forms of control from the user and leave them no way to get rid of the annoyances. I know few people give a shit about it, but even Microsofts own "Guidelines for Keyboard User Interface Design" specify that Win + <KEY> shortcuts are reserved for operating system use. Heck, Raymond Chen even wrote up an entire article about not using the WIN key as the base key for your apps hotkeys. I'm sure some of Microsoft's own apps also fantastically violate those rules, so I suppose it just seems pointless to people observing externally, the value in that guideline.

One could argue that nothing is going to stop a determined nerd, but time afterall isn't free. I could have easily patched the executable and removed the offending call, but it just seemed annoying that I'd have to keep doing that for every update of the app. Alternatively, I could possibly also stick a patched copy of USER32.DLL in the local directory but both these solutions just seemed like A Bad Idea™. A while back Microsoft research had released a cool library called Detours that I knew could be put to good use here. All I would have to do is attach a Detour to RegisterHotKey and UnRegisterHotKey and route it to my own stub functions and I'd be all set. This was the extent of all the code I had to write:


#include <windows.h>
#include <detours.h>


static BOOL (WINAPI *ActualRegisterHotKey) (HWND,int,UINT,UINT) = RegisterHotKey;
static BOOL (WINAPI *ActualUnregisterHotKey) (HWND,int) = UnregisterHotKey;

BOOL WINAPI FakeRegisterHotKey(HWND a, int b, UINT c, UINT d)
{
    return TRUE;
}

BOOL WINAPI FakeUnregisterHotKey(HWND a, int b)
{
    return TRUE;
}

BOOL WINAPI DllMain(HINSTANCE hInst, DWORD dwReason, LPVOID reserved)
{
    if (dwReason == DLL_PROCESS_ATTACH)
    {
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourAttach(&(PVOID&)ActualRegisterHotKey, FakeRegisterHotKey);
        DetourAttach(&(PVOID&)ActualUnregisterHotKey, FakeUnregisterHotKey);
        DetourTransactionCommit();
    }
    else if (dwReason == DLL_PROCESS_DETACH)
    {
        DetourTransactionBegin();
        DetourUpdateThread(GetCurrentThread());
        DetourDetach(&(PVOID&)ActualRegisterHotKey, FakeRegisterHotKey);
        DetourDetach(&(PVOID&)ActualUnregisterHotKey, FakeUnregisterHotKey);
        DetourTransactionCommit();
    }
    return TRUE;
}

I'm not sure how Detours works but one other way that I know of to hook functions is to modify the executable's import table and rewrite the jump to the actual function to an address of your own. Ofcource there are other subtleties to it, and interested readers can google for "BugSlayer0298.exe" and look up the ancient column by John Robbins.

Anyway, Detours seems to have worked just fine here. I created a tiny dll with my stub function and used the 'withdll' sample from the detours library to help me get it up and running for the google talk executable. Unfortunately I can't really post my complete solution here as detours ships with a restrictive license. You could download the express version for personal use though. If you're a programmer this is fairly trivial to get working else shoot me an email if you can't for some reason.