1

我目前无法在 Windows 7 64 位操作系统上使用全局键挂钩。现在我知道 Stackoverflow 上已经有很多关于这个主题的主题了,但是没有一个能给我一个我可以使用的答案,或者我不太明白这些主题是如何解决这个问题的。

因此,我将尝试解释我正在努力解决的问题,并希望任何人都可以帮助我或为我指明正确的方向。

基本上,我的目标是拦截 CTRL+C 和 CTRL+V 键盘输入,以实现一种剪贴板管理器。出于这个原因,我目前的尝试是注册一个系统范围的WH_KEYBOARD钩子,它可以根据我的需要处理截获的击键。

我在 64 位 Windows 7 O/S 上运行挂钩,这就是问题开始的地方。对我来说很明显 32 位 Hook DLL 会导致 64 位进程出现问题,反之亦然。出于这个原因,我生成了包含钩子的库的 x86 和 x64 版本,以及钩子的调用者(调用者SetWindowsHookEx()),两者都具有不同的文件名,如文档所示。

但是现在呢?如果我将我的 64 位 DLL 设置为系统范围的钩子,则所有 32 位应用程序只要我在聚焦时按下一个键就会开始挂起。当我应用 32 位挂钩时,我的 Windows 实际上无法使用,因为它explorer.exe是 64 位的。如果我设置了两个钩子,我的系统就会陷入停顿状态,从而引发一场全球性的“比特”斗争。

现在我假设问题出在例如试图注入 32 位进程的 64 位挂钩 DLL 等等,这当然是毫无意义的。但是对于这种情况,文档SetWindowsHookEx()说明如下:

因为钩子在应用程序的上下文中运行,所以它们必须与应用程序的“位数”相匹配。如果 32 位应用程序在 64 位 Windows 上安装全局挂钩,则 32 位挂钩将被注入每个 32 位进程(通常的安全边界适用)。在 64 位进程中,线程仍被标记为“已挂钩”。但是,由于 32 位应用程序必须运行钩子代码,系统会在钩子应用程序的上下文中执行钩子;具体来说,在调用 SetWindowsHookEx 的线程上。这意味着挂钩应用程序必须继续发送消息,否则可能会阻止 64 位进程的正常运行。

我不完全理解文本的粗体部分,但我将其解释为如果钩子目标的“位数”与钩子不同,它将在实际设置钩子的线程上执行所以它完全可以执行。此外,这意味着该线程必须仍然处于活动状态并且可能正在运行某种消息循环。它是否正确?还是我完全不喜欢这个?该文档似乎还提供了有关如何处理我的场景的确切说明:

要在 64 位 Windows 安装的桌面上钩住所有应用程序,请安装一个 32 位全局钩子和一个 64 位全局钩子,每个钩子都来自相应的进程,并确保在钩子应用程序中保持泵送消息,以避免阻塞正常运作。

但是我无法掌握在我的实施中必须做的事情。为了最终展示一些代码,让我们以这个尝试设置系统范围的 keyhook 的基本示例为例。我猜线程的创建代码应该是无关紧要的:

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;
}

钩子本身保持相当简单 - 在跨越位数时足以引起挂断问题:

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

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

我想有些人现在可能会把手举过头顶,你可以看出我很少使用钩子;)

但这正是我要问的原因:)

简而言之:如果有人能告诉我如何更改上述示例以使系统范围的 keyhook 在 64 位 Windows 上工作,我将不胜感激。我的问题是某些具有除钩子之外的其他“位数”的应用程序开始挂起,我不知道如何解决这个问题。

很感谢任何形式的帮助!

谢谢并恭祝安康

葡萄干

4

2 回答 2

4

好的,我知道问题出在哪里了。也许我的解决方案可以帮助其他遇到同样问题的人:如上所述,文档明确指出

[...] 系统在(32 位)挂钩应用程序的上下文中执行(32 位)挂钩;具体来说,在调用 SetWindowsHookEx 的线程上。这意味着挂钩应用程序必须继续发送消息,否则可能会阻止 64 位进程的正常运行。

我遇到的是上面提到的阻塞行为,应该通过消息泵来克服。在我上面的代码示例中,正是缺少这种机制,因为我不知道这应该是一个简单的 Windows 消息循环(我以前不知道“泵”这个词)。我的初始代码中的最终代码片段必须如下所示:

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;
}

在此示例中,我使用非阻塞PeekMessage()而不是GetMessage(),因为我希望我的线程不断检查它的终止标志。

有了这个实现,我的钩子在所有本机 64 位或 WOW64 进程中都按预期工作,没有应用程序在钩子后立即挂起。消息泵是唯一缺少的部分。

经过所有这些实验后,我得出以下结论 - 如果我错了,请在评论中纠正我:

当安装了系统范围的钩子时,它会尝试将给定的 Hook-DLL 注入到每个正在运行的进程中。如果进程的“位数”与 Hook-DLL 中的“位数”匹配,则挂钩过程将作为目标进程中的远程线程执行(就像正常的 Injectors 工作一样)。如果“位数”不同,Windows 会回退到最初调用的进程和线程SetWindowsHookEx()(在我的示例中是代码片段中的线程),并充当不匹配的进程的执行代理“比特”。出于这个原因,这个线程需要不断地处理传入的消息,否则将不会为原始目标进程处理任何事件,这反过来又开始挂起。

同样,如果这不完全正确,不完整,或者如果有任何进一步的注释要添加,请在下面发表评论。谢谢!:)

于 2013-03-21T18:35:50.400 回答
0

我的解决方案是将钩子应用程序(在其中SetWindowsHookEx()被调用)和 DLL(回调钩子函数所在的位置)编译为 x86 和 x64 版本。因此,我们有两个 EXE(xxx-x86.exe 和 xxx-x64.exe)和两个 DLL(xxx-x86.dll 和 xxx-x64.dll)。

然后实现一个复杂的 IPC 协议来同步 x86 应用和 x64 应用之间的数据。它看起来很有效,不会阻止另一个“位数”无与伦比的进程。但是很难处理精确的事件序列,它只能作为事件的粗略指标。

这个解决方案很丑陋,但我没有更好的方法......

于 2018-12-02T11:29:46.493 回答