32 位程序无法捕获在 32 位进程上进行的击键,但能够捕获在 64 位进程上进行的击键

Posted

技术标签:

【中文标题】32 位程序无法捕获在 32 位进程上进行的击键,但能够捕获在 64 位进程上进行的击键【英文标题】:32-bit program is unable to capture keystrokes made on a 32-bit process, but able to capture those made on a 64-bit process 【发布时间】:2014-01-03 19:01:15 【问题描述】:

我的环境详情:

操作系统:Windows 7 Enterprise Service Pack 1(64 位操作系统) 编译器:Microsoft Visual Studio 2005(Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.762 for 80x86) 我的程序 main.exe 和 hook.dll 是 32 位的 Internet Explorer (iexplore.exe) 是 64 位 Chrome (chrome.exe) 是 32 位

我编写了一个名为 main.exe 的 C++ 程序,它进行以下调用:

HOOKPROC callback = (HOOKPROC) GetProcAddress(dll, "keyboardHook");
HHOOK hook = SetWindowsHookEx(WH_KEYBOARD, callback, dll, NULL);

keyboardHook 函数在名为 hook.dll 的 DLL 中定义。此函数记录控制台上的每次击键以及名为 out.txt 的文件。

我进行以下实验。

    在命令提示符下执行 main.exe。 将 Internet Explorer(64 位)设为我的活动窗口,然后按 ABC。 将 Chrome(32 位)设为我的活动窗口,然后按 XYZ。 使命令提示符成为我的活动窗口并按 Ctrl + C 杀死 main.exe。

我在控制台上只看到以下输出。

C:\lab\keyhook>main,exe
hinstDLL: 10000000; fdwReason: 1; lpvReserved: 00000000
); scan code: 28; transition state: 1
nCode: 0; wParam: 65 (A); scan code: 30; transition state: 0
nCode: 0; wParam: 65 (A); scan code: 30; transition state: 1
nCode: 0; wParam: 66 (B); scan code: 48; transition state: 0
nCode: 0; wParam: 66 (B); scan code: 48; transition state: 1
nCode: 0; wParam: 67 (C); scan code: 46; transition state: 0
nCode: 0; wParam: 67 (C); scan code: 46; transition state: 1
nCode: 0; wParam: 17 (◄); scan code: 29; transition state: 0
nCode: 0; wParam: 67 (C); scan code: 46; transition state: 0
hinstDLL: 10000000; fdwReason: 2; lpvReserved: 00000000
hinstDLL: 10000000; fdwReason: 0; lpvReserved: 00000001

而且我在要登录的文件中只看到以下输出。

C:\lab\keyhook>type out.txt
); scan code: 28; transition state: 1
nCode: 0; wParam: 65 (A); scan code: 30; transition state: 0
nCode: 0; wParam: 65 (A); scan code: 30; transition state: 1
nCode: 0; wParam: 66 (B); scan code: 48; transition state: 0
nCode: 0; wParam: 66 (B); scan code: 48; transition state: 1
nCode: 0; wParam: 67 (C); scan code: 46; transition state: 0
nCode: 0; wParam: 67 (C); scan code: 46; transition state: 1
nCode: 0; wParam: 17 (◄); scan code: 29; transition state: 0
nCode: 0; wParam: 67 (C); scan code: 46; transition state: 0

您可以看到在 32 位 Chrome 上进行的击键在控制台或日志文件中都没有被捕获。

为什么我用 32 位 C++ 编译器编译的 32 位程序可以成功捕获 64 位 iexplore.exe 上的击键,但无法捕获 32 位 chrome.exe 上的击键?

MSDN 上的SetWindowsHookEx documentation 似乎表明我的 32 位程序应该能够捕获仅在其他 32 位程序上进行的击键。

SetWindowsHookEx 可用于将 DLL 注入另一个进程。 32位DLL不能注入64位进程,64位DLL不能注入32位进程。如果应用程序需要在其他进程中使用钩子,则需要 32 位应用程序调用 SetWindowsHookEx 将 32 位 DLL 注入 32 位进程,64 位应用程序调用 SetWindowsHookEx 将 64 位DLL 转换为 64 位进程。 32 位和 64 位 DLL 必须具有不同的名称。

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

这是调用SetWindowsHookEx的主程序的完整代码。

// Filename: main.cc
#include <iostream>
#include <windows.h>

int main(int argc, char **argv)

    HMODULE dll = LoadLibrary("hook.dll");
    if (dll == NULL) 
        std::cerr << "LoadLibrary error " << GetLastError() << std::endl;
        return 1;
    

    HOOKPROC callback = (HOOKPROC) GetProcAddress(dll, "keyboardHook");
    if (callback == NULL) 
        std::cerr << "GetProcAddress error " << GetLastError() << std::endl;
        return 1;
    

    HHOOK hook = SetWindowsHookEx(WH_KEYBOARD, callback, dll, NULL);
    if (hook == NULL) 
        std::cerr << "SetWindowsHookEx error " << GetLastError() << std::endl;
        return 1;
    

    MSG messages;
    while (GetMessage (&messages, NULL, 0, 0))
    
        TranslateMessage(&messages);
        DispatchMessage(&messages);
    
    UnhookWindowsHookEx(hook);

这是 DLL 代码。

// Filename: hook.cc
#include <iostream>
#include <fstream>
#include <windows.h>

extern "C" __declspec(dllexport)
LRESULT keyboardHook(int nCode, WPARAM wParam, LPARAM lParam)

    std::ofstream outputTxt("out.txt", std::ofstream::out | std::ofstream::app);
    outputTxt << "nCode: " << nCode << "; wParam: " << wParam
              <<" (" << char(wParam) << "); scan code: "
              << ((lParam & 0xFF0000) >> 16)
              << "; transition state: " << ((lParam & 0x80000000) >> 31)
              << std::endl;
    outputTxt.close();

    std::cout << "nCode: " << nCode << "; wParam: " << wParam
              <<" (" << char(wParam) << "); scan code: "
              << ((lParam & 0xFF0000) >> 16)
              << "; transition state: " << ((lParam & 0x80000000) >> 31)
              << std::endl;
    return CallNextHookEx(NULL, nCode, wParam, lParam);


BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)

    std::cout << "hinstDLL: " << hinstDLL
              << "; fdwReason: " << fdwReason
              << "; lpvReserved: " << lpvReserved << std::endl;

    return TRUE;

这就是我编译这个项目的方式:

vcvars32.bat
cl /LD hook.cc /link user32.lib
cl main.cc /link user32.lib

这就是流程的样子。看到 chrome.exe 和 main.exe 是 32 位进程,而 iexplore.exe 的实例是 64 位进程。

您能解释一下为什么我的观察结果与 MSDN 文档不符吗?

【问题讨论】:

如果你挂接的程序没有控制台,你不能用 32 位 DLL 挂接一个 64 位进程并且你看不到 std::cout 输出。你成功地钩住了你自己的程序。 @HansPassant 是的!这就是我实验中的问题。当我使用"C:\\out.txt" 作为输出文件路径时,我可以看到在 32 位和 64 位进程上的击键。我将使用固定代码添加答案。感谢您的帮助! 【参考方案1】:

Hans Passant 对这个问题和this answer by manuell 的 cmets 帮助我理解和修复了我的代码。

相对路径"out.txt" 是所有混乱的根源。

我的 32 位程序成功地将其 32 位挂钩注入 32 位 Chrome。因此,钩子在 Chrome 的上下文中运行,并且“out.txt”文件被写入 Chrome 的工作目录。因此,在 Chrome 上作为活动窗口进行的击键 XYZ 记录在“C:\Program Files (x86 )\Google\Chrome\Application\31.0.1650.63\out.txt"。

C:\>type "C:\Program Files (x86)\Google\Chrome\Application\31.0.1650.63\out.txt"
nCode: 0; wParam: 88 (X); scan code: 45; transition state: 0
nCode: 0; wParam: 88 (X); scan code: 45; transition state: 1
nCode: 0; wParam: 89 (Y); scan code: 21; transition state: 0
nCode: 0; wParam: 89 (Y); scan code: 21; transition state: 1
nCode: 0; wParam: 90 (Z); scan code: 44; transition state: 0
nCode: 0; wParam: 90 (Z); scan code: 44; transition state: 1

此输出未出现在控制台中,因为没有控制台与 Chrome 关联。

但是,我的 32 位程序无法将其 32 位挂钩注入 64 位 Internet Explorer。结果,钩子在我的程序的上下文中执行,当它记录在 64 位 Internet Explorer 上的击键时,在我的程序的工作目录中创建了“out.txt”文件。此输出也记录在控制台上,因为有一个与我的程序关联的控制台。这就是你在我的问题中提到的。

对我的代码的一个简单修复是将击键记录到绝对路径 "C:\\out.txt" 而不是 hook.cc 中的相对路径“C:\out.txt”,这样我就可以看到在 32 - 位程序和 64 位程序在同一个文件中。

【讨论】:

+1 感谢您不厌其烦地清楚地解释您所学的内容

以上是关于32 位程序无法捕获在 32 位进程上进行的击键,但能够捕获在 64 位进程上进行的击键的主要内容,如果未能解决你的问题,请参考以下文章

在 64 位机器上捕获的 32 位进程的调试转储

SetWindowsHookEx 在快速输入或键盘按钮按住时冻结

如何修复“忽略的击键”

使用 C++ 从 32 位进程访问 64 位 dll

64位进程调用32位dll的解决方法

小5聊Winform窗体遍历进程提示拒绝访问以及32位无法访问64位模块解决方法