简体   繁体   中英

Global Keyhook on 64-Bit Windows

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 eg 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

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! :)

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...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM