2

I currently have some trouble to get a global keyhook working on a Windows 7 64-Bit O/S. Now I know that there are a lot of threads on this topic already here on Stackoverflow, but none of them quite give me an answer that I can work with or I don't quite understand how the issue is solved in these threads.

So I will try to explain what I am struggling with and hope that anyone can help me out or point me into the right direction.

Basically my goal is to intercept the CTRL+C and CTRL+V keyboard inputs for a sort of clipboard manager. For this reason my current attempt is to register a syste-wide WH_KEYBOARD hook which deals with the intercepted keystrokes to my needs.

I am running the hook on a 64-Bit Windows 7 O/S, and that is where the problems start. It is obvious to me that a 32-Bit Hook DLL causes problems for 64-Bit processes, vice versa. For this reason I have generated a x86 and x64 version of my library containing the hook, as well as for the invoker of the hook (the one who calls SetWindowsHookEx()), both with different filenames as the documentation suggests.

But what now? If I set a my 64-Bit DLL as a system-wide hook, all the 32-Bit applications start hanging as soon as I press a key when they are focused. Same thing when I apply the 32-Bit hook, my Windows is practically unusable because the explorer.exe is 64-Bit. If I set both hooks, my system is effectivley at a standstill, having a global "bitness" fight in place.

Now I assume that the issue arises from e.g. the 64-Bit hooking DLL trying to be injected into a 32-Bit process and so forth, which is of course nonesense. But for this case the documentation of SetWindowsHookEx() says the following:

Because hooks run in the context of an application, they must match the "bitness" of the application. If a 32-bit application installs a global hook on 64-bit Windows, the 32-bit hook is injected into each 32-bit process (the usual security boundaries apply). In a 64-bit process, the threads are still marked as "hooked." However, because a 32-bit application must run the hook code, the system executes the hook in the hooking app's context; specifically, on the thread that called SetWindowsHookEx. This means that the hooking application must continue to pump messages or it might block the normal functioning of the 64-bit processes.

I don't fully understand the bold part of the text, but I interpret it as in that if the "bitness" of a hooking target differs from the one of the hook, it is executed on the thread that has actually set the hook so it can be executed at all. Furthermore, this means that this thread must still be active and probably running a sort of message loop. Is this correct? Or am I totally off on this one? The documentation also seems to give exact instructions on what to do for my scenario:

To hook all applications on the desktop of a 64-bit Windows installation, install a 32-bit global hook and a 64-bit global hook, each from appropriate processes, and be sure to keep pumping messages in the hooking application to avoid blocking normal functioning.

But yet I fail to grasp what has to be done in my implementation. To finally show some code, let's take this basic example of trying to set a system-wide keyhook. I guess the creating code for the thread should be irrelevant:

volatile static bool runThread = false;

DWORD WINAPI threadStart(LPVOID lpThreadParameter) {
    HMODULE hMod = LoadLibraryA(is64Bit() ? "keyhook.x64.dll" : "keyhook.x86.dll");
    HHOOK hHook = SetWindowsHookExA(WH_KEYBOARD, (HOOKPROC)GetProcAddress(hMod, "hookProc"), hMod, 0)));

    runThread = true;
    while(runThread) {
        // Message pump? Yes? No? How?
        Sleep(10);
    }

    UnhookWindowsHookEx(hHook);
    FreeLibrary(hMod);
    return 0;
}

The hook itself is kept quite trivial - it is enough to cause the hangup issues when crossing the bitness:

extern "C" LRESULT hookProc(int code, WPARAM wParam, LPARAM lParam) {
    if(code == HC_ACTION) {
    }

    return CallNextHookEx(nullptr, code, wParam, lParam);
}

I assume that some people might throw there hands over their heads right now, and that you can tell that I seldom have worked with hooks ;)

But that's exactly why I am asking :)

To keep things short: I would be grateful if someone could tell me how to change the above example to get a system-wide keyhook working on 64-Bit Windows. My problem is that certain applications with other "bitness" than the hook start hanging and I don't know how to solve the issue.

Any help is very much appreciated!

Thanks and regards

PuerNoctis

PuerNoctis
  • 1,364
  • 1
  • 15
  • 34
  • Why dont you just use the [Keyboard Input Functions (msdn)](http://msdn.microsoft.com/en-us/library/windows/desktop/ff468859%28v=vs.85%29.aspx) like GetAsyncKeyState to check if keys are pressed. – Jona Mar 21 '13 at 10:00
  • 1
    That's what I would do inside the hook itself (I left the hook empty intentionally for the above example). My issue is that I can't get a system-wide hook working for all processes in a 64-Bit environment, even when creating a separate DLL for x86 and x64. If you suggest to just use GetAsyncKeyState() and drop hooks overall, that is not possible unfortunately since I have to intercept the CTRL+C / CTRL+V commands as they happen, and maybe, prevent further conveying. Periodic polling wouldn't do the trick. – PuerNoctis Mar 21 '13 at 10:07
  • Instead of trying to catch those keys why not install a clipboard viewer and just watch for changes in the clipboard data? – Jonathan Potter Mar 21 '13 at 11:31
  • I had that idea too at first (with SetClipboardViewer()), and it works perfectly for any copy operation. But I need to handle pastes (especially CTRL+V) too, as I need to modify the data of the clipboard in certain situations prior the message is being forwarded to the original message target. Are there any global OnPaste messages or things the like that can be hooked? – PuerNoctis Mar 21 '13 at 11:37
  • 2
    I don't think so - maybe you could just clone the data object that's put onto the clibboard and replace the clipboard contents with your own version whenever it changes? Then you'll know when it's pasted (as your `GetData` method will be called). – Jonathan Potter Mar 21 '13 at 11:47
  • I already found a solution for my exact problem by now (see below). But I also like the idea you were proposing where I replace the clipboard's content immediately after a copy operation with an own "intelligent object" that uses `GetData` to determine the point it is pasted somewhere. But I am struggling finding such a `GetData` that can be implemented with own code or callbacks (I found a .NET equivalent of a `Clipboard.GetData()`, but it is a static method that can not be altered). Could you give a quick example, if possible? Thanks! – PuerNoctis Mar 21 '13 at 18:38
  • Relying on a keyboard hook to try to intercept copy and paste operations seems problematic. You'll miss copy and paste operations that happen via menus, and you'll interfere with things like a CMD prompt where Ctrl+C doesn't mean copy. And there are some apps that pop up a menu on a paste operations because they allow for several different types of pasting. Keyboard hooks are not the answer. – Adrian McCarthy Mar 21 '13 at 19:59
  • @AdrianMcCarthy That's fortunately not a concern in my case. I explicity only want to handle the keyboard shortcuts and don't mind the menu commands. I really only need special care for CTRL+C and CTRL+V :) – PuerNoctis Mar 21 '13 at 20:20

2 Answers2

5

Okay, I figured out what the problem was. Maybe my solution helps others experiencing the same issue: As mentioned above, the documentation explicitly states that

[...] the system executes the (32-Bit) hook in the (32-Bit) hooking app's context; specifically, on the thread that called SetWindowsHookEx. This means that the hooking application must continue to pump messages or it might block the normal functioning of the 64-bit processes.

What I was experiencing was the mentioned blocking behaviour that is supposed to be overcome with a message pump. In my above code example, exactly this piece of mechanism was missing, as I wasn't aware that this is supposed to be a simple Windows Message Loop (I didn't know of the term 'pump' before). The finalized code snippet from my initial code must look something like this:

volatile static bool runThread = false;

DWORD WINAPI threadStart(LPVOID lpThreadParameter) {
    HMODULE hMod = LoadLibraryA(is64Bit() ? "keyhook.x64.dll" : "keyhook.x86.dll");
    HHOOK hHook = SetWindowsHookExA(WH_KEYBOARD, (HOOKPROC)GetProcAddress(hMod, "hookProc"), hMod, 0)));

    MSG msg;
    runThread = true;
    while(runThread) {
        // Keep pumping...
        PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE);
        TranslateMessage(&msg);
        DispatchMessage(&msg);
        Sleep(10);
    }

    UnhookWindowsHookEx(hHook);
    FreeLibrary(hMod);
    return 0;
}

In this example I am using the non-blocking PeekMessage() instead of GetMessage(), as I want my thread to constantly check it's termination flag.

With this implementation my hook works as expected in all native 64-Bit or WOW64 processes, no applications hang up as soon as they are hooked. The message pump was the only part missing.

After all this experimentation I come to the following conclusion - and if I am wrong, please correct me in the comments:

When a system-wide hook is installed, it is tried to inject the given Hook-DLL into every process running. If the "bitness" of a process matches the one from the Hook-DLL, the hooking procedure is executed as a remote thread in the target process (like normal Injectors work). In case the "bitness" differs, Windows takes a fallback detour back to the process and thread that initially called SetWindowsHookEx() (in my example the thread in the code snippet), and serves as an execution-proxy for the process that doesn't match the "bitness". For this reason, it is required for this thread to constantly handle incoming messages, otherwise no events will be processed for the original target process which in return starts hanging.

Again, if there is this is not quite right, incomplete, or if there are any further notes to add, please put a comment below. Thanks! :)

PuerNoctis
  • 1,364
  • 1
  • 15
  • 34
  • 3
    What happens if PeekMessage returns when there's no message available? You probably reprocess the previous message. And polling is bad. Either (a) use GetMessage and post yourself a private message using PostThreadMessage when the termination flag changes or (b) use PeekMessage in conjunction with [MsgWaitForMultipleObjects](http://msdn.microsoft.com/en-gb/library/windows/desktop/ms684242%28v=vs.85%29.aspx) and an event to signal termination. – arx Mar 21 '13 at 18:49
  • Good point! My message handling has to be improved here definitely. Thanks! – PuerNoctis Mar 21 '13 at 18:53
  • 1
    Instead of polling for your runThread flag, create an Event that's signaled when you're supposed to exit. Then your pump should start with MsgWaitForMultipleObjects. It will return if your event gets signals *or* if new messages need to be handled. This is much gentler on the system than polling (even with the Sleep call). – Adrian McCarthy Mar 21 '13 at 19:53
  • 1
    Your conclusion is exactly right, if I had came across this post a week ago it would've saved me a lot of headache. – Saeb Amini Nov 01 '15 at 16:08
  • Can you post the complete source to github and send me a link? I would like to do exactly what you are doing. – Christopher Pisz Jul 12 '19 at 21:18
  • Gosh, this thread is over six years old.... but I see if I can find the code somewhere ;) No promises though. – PuerNoctis Jul 15 '19 at 10:41
  • PuerNoctis, how did you got such conclusion??? As Saeb Amini it would also saved a lot. You should be writing MS documentation instead of MS – alvaroc Jan 06 '20 at 21:03
0

My solution is to compile the hook app (in which SetWindowsHookEx() was invoked) and the DLL (where the callback hook function located) to both x86 and x64 version. Thus we have two EXEs (xxx-x86.exe & xxx-x64.exe) and two DLLs (xxx-x86.dll & xxx-x64.dll).

Then implement a complicated IPC protocol to sync the data between the x86 app and x64 app. It looks work, won't block another process which "bitness" is unmatched. But it's hard to process precise event sequences, it can only perform as a rough indicator of events.

This solution is quite ugly, but I have no better way...

Felix Sun
  • 1
  • 1