c++ - SetWindowsHookEx + WH_CBT 不起作用?或者至少不是我认为应该的方式?

标签 c++ windows dll keyboard-hook setwindowshookex

我有一个诊断程序,它使用 SetWindowsHookExWH_KEYBOARD_LL 在系统范围内扫描代码 我想扩展它来监 Windows 口焦点的变化,这是一种可能使用SetWindowsHookEx 和基于计算机的训练 CBT 钩子(Hook) WH_CBT

对于 WH_KEYBOARD_LL Hook ,我能够将 Hook 函数放入我的进程中并且它起作用,在我桌面上的几乎每个应用程序窗口中捕获按键。我的理解是WH_CBT其实需要放在一个单独的dll中,这样才能注入(inject)到其他进程中。所以我已经这样做了。

我也知道这会带来位要求 - 如果我的 dll 是 64 位,我不能将它注入(inject) 32 位进程,反之亦然。

无论如何,我在 VS2008 调试器中进行了尝试,果然,我看到了 OutputDebugString 输出(我的处理程序调用了 OutputDebugString)。但仅在 Visual Studio 和 DebugView 中——当我将焦点切换到 DebugView 时,DebugView 将显示焦点更改字符串输出。当我切换回 VS 调试器时,VS 输出窗口将显示焦点更改字符串输出。

我认为这可能是 VS 和 DebugView 之间的丑陋交互,所以我尝试在没有调试器的情况下自行运行我的程序。同样,它会在 DebugView 中显示输出,但仅在切换到 DebugView 时才会显示。当我将焦点切换到 Notepad++、SourceTree 和其他六个应用程序时,在 DebugView 中没有注册任何内容。

我有点怀疑,所以我启动了进程资源管理器并搜索了我的注入(inject) dll。果然,只有一小部分进程似乎获得了 dll。当我构建 32 位 dll 时,Visual Studio、DebugView、procexp.exe 似乎都获得了 dll,但没有任何其他在我的机器上运行的 32 位进程。当我构建 64 位 dll 时,explorer.exeprocexp64.exe 获取 dll,但我机器上的任何其他 64 位进程都没有。

任何人都可以提出任何建议吗?任何可能的解释?是否有可能在某处获取日志记录事件,这可以解释为什么我的 dll 进入一个特定进程而不是另一个? SetWindowsHookEx 报告 ERROR_SUCCESSGetLastError。接下来我可以去哪里看?

更新:

我已经上传了演示这一点的 visual studio 项目。

https://dl.dropboxusercontent.com/u/7059499/keylog.zip

我使用 cmake,不幸的是 cmake 不会将 32 位和 64 位目标放在同一个 sln 中 - 所以 64 位 .sln 在 _build64 中,而 32 位 .sln 在中。 sln 在 _build32 中。需要明确的是,您不需要 cmake 来尝试这个 - 只是我最初使用 cmake 来生成这些项目文件。

这是我的 main.cpp

#include <iostream>
#include <iomanip>
#include <sstream>
#include "stdafx.h"
#include "km_inject.h"

using namespace std;

typedef pair<DWORD, string> LastErrorMessage;

LastErrorMessage GetLastErrorMessage()
{
    DWORD code = GetLastError();
    _com_error error(code);
    LPCTSTR errorText = error.ErrorMessage();
    return LastErrorMessage( code, string(errorText) );
}

static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
  switch (message)
  {
  case WM_DESTROY:
      PostQuitMessage(0);
      break;
  default:
      return DefWindowProc(hWnd, message, wParam, lParam);
  }
  return 0;
}


LRESULT __stdcall CALLBACK LowLevelKeyboardProc(
  _In_  int nCode,
  _In_  WPARAM wParam,
  _In_  LPARAM lParam
)
{
    KBDLLHOOKSTRUCT * hookobj = (KBDLLHOOKSTRUCT *)lParam;
    DWORD vkCode = hookobj->vkCode;
    DWORD scanCode = hookobj->scanCode;
    DWORD flags = hookobj->flags;
    DWORD messageTime = hookobj->time;

    UINT vkCodeChar = MapVirtualKey( vkCode, MAPVK_VK_TO_CHAR );

#define BITFIELD(m) string m##_str = (flags & m)? #m : "NOT " #m
    BITFIELD(LLKHF_EXTENDED);
    BITFIELD(LLKHF_INJECTED);
    BITFIELD(LLKHF_ALTDOWN);
    BITFIELD(LLKHF_UP);
#undef BITFIELD

    string windowMessageType;

#define KEYSTRING(m) case m: windowMessageType = #m; break

    switch ( wParam )
    {
        KEYSTRING( WM_KEYDOWN );
        KEYSTRING( WM_KEYUP );
        KEYSTRING( WM_SYSKEYDOWN );
        KEYSTRING( WM_SYSKEYUP );
    default: windowMessageType = "UNKNOWN"; break;
    };
#undef KEYSTRING

    stringstream ss;
    ss << left 
       << setw(10) << messageTime << " "
       << setw(15) << windowMessageType << ": "
       << right
       << "VK=" << setw(3) << vkCode << " (0x" << hex << setw(3) << vkCode << dec << ") " << setw(2) << vkCodeChar << ", " 
       << "SC=" << setw(3) << scanCode << " (0x" << hex << setw(3) << scanCode << dec << "), " 
       << setw(20) << LLKHF_EXTENDED_str << ", " 
       << setw(20) << LLKHF_INJECTED_str << ", " 
       << setw(20) << LLKHF_ALTDOWN_str << ", " 
       << setw(15) << LLKHF_UP_str << endl;
    OutputDebugString( ss.str().c_str() );

    return CallNextHookEx( 0, nCode, wParam, lParam );
}


int WINAPI WinMain(
  __in  HINSTANCE hInstance,
  __in_opt  HINSTANCE hPrevInstance,
  __in_opt  LPSTR lpCmdLine,
  __in  int nCmdShow )
{
    OutputDebugString( "Beginning test...\n" );

    // Set up main event loop for our application.
    WNDCLASS windowClass = {};
    windowClass.lpfnWndProc = WndProc;
    char * windowClassName = "StainedGlassWindow";
    windowClass.lpszClassName = windowClassName;
    windowClass.hbrBackground = (HBRUSH)GetStockObject( WHITE_BRUSH );
    if (!RegisterClass(&windowClass)) 
    {
        LastErrorMessage fullMessage = GetLastErrorMessage();
        stringstream ss;
        ss << "Failed to register window class: " << fullMessage.first << " \"" << fullMessage.second << "\"\n";
        OutputDebugString( ss.str().c_str() );
        return -1;
    }
    HWND mainWindow = CreateWindow(windowClassName, // class
        "keylogger", // title
        WS_OVERLAPPEDWINDOW | WS_VISIBLE , // 'style'
        CW_USEDEFAULT, // x
        CW_USEDEFAULT, // y
        CW_USEDEFAULT, // width
        CW_USEDEFAULT, // height
        NULL, // parent hwnd - can be HWND_MESSAGE
        NULL, // menu - use class menu
        hInstance, // module handle
        NULL); // extra param for WM_CREATE

    if (!mainWindow) 
    {
        LastErrorMessage fullMessage = GetLastErrorMessage();
        stringstream ss;
        ss << "Failed to create main window: " << fullMessage.first << " \"" << fullMessage.second << "\"\n";
        OutputDebugString( ss.str().c_str() );
        return -1;
    }

    // Get the name of the executable
    char injectFileName[ MAX_PATH + 1 ];
    {
        int ret = GetModuleFileName( hInstance, injectFileName, MAX_PATH );
        if ( ret == 0 || ret == MAX_PATH )
        {
            LastErrorMessage fullMessage = GetLastErrorMessage();
            stringstream ss;
            ss << "GetModuleFileName failed: " << fullMessage.first << " \"" << fullMessage.second << "\"\n";
            OutputDebugString( ss.str().c_str() );
            return -1;
        }
        char * sep = strrchr( injectFileName, '\\' );
        if ( sep == NULL )
        {
            stringstream ss;
            ss << "Couldn't find path separator in " << injectFileName << endl;
            OutputDebugString( ss.str().c_str() );
            return -1;
        }
        *sep = 0;
        strcat_s( injectFileName, "\\km_inject.dll" );
    }

    // Get the module handle
    HINSTANCE inject = LoadLibrary( injectFileName );
    if ( NULL == inject )
    {
        LastErrorMessage fullMessage = GetLastErrorMessage();
        stringstream ss;
        ss << "Failed to load injector with LoadLibrary: " << fullMessage.first << " \"" << fullMessage.second << "\"\n";
        OutputDebugString( ss.str().c_str() );
        return -1;
    }

#ifdef _WIN64
    HOOKPROC LowLevelCBTProc = (HOOKPROC)GetProcAddress( inject, "LowLevelCBTProc" );
#else
    HOOKPROC LowLevelCBTProc = (HOOKPROC)GetProcAddress( inject, "_LowLevelCBTProc@12" );
#endif

    if ( !LowLevelCBTProc )
    {
        LastErrorMessage fullMessage = GetLastErrorMessage();
        stringstream ss;
        ss << "Failed to find LowLevelCBTProc function: " << fullMessage.first << " \"" << fullMessage.second << "\"\n";
        OutputDebugString( ss.str().c_str() );
        return -1;
    }

    // Install the keyboard and CBT handlers
    if ( NULL == SetWindowsHookEx( WH_KEYBOARD_LL, LowLevelKeyboardProc, NULL, 0 ) )
    {
        LastErrorMessage fullMessage = GetLastErrorMessage();
        stringstream ss;
        ss << "Failed to set llkey hook: " << fullMessage.first << " \"" << fullMessage.second << "\"\n";
        OutputDebugString( ss.str().c_str() );
        return -1;
    }

    if ( NULL == SetWindowsHookEx( WH_CBT, LowLevelCBTProc, inject, 0 ) )
    {
        LastErrorMessage fullMessage = GetLastErrorMessage();
        stringstream ss;
        ss << "Failed to set cbt hook: " << fullMessage.first << " \"" << fullMessage.second << "\"\n";
        OutputDebugString( ss.str().c_str() );
        return -1;
    }


    BOOL bRet;
    MSG msg;

    while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
    { 
        if (bRet == -1)
        {
            LastErrorMessage fullMessage = GetLastErrorMessage();
            stringstream ss;
            ss << "What on earth happened? errcode=" << fullMessage.first << ", \"" << fullMessage.second << "\"\n";
            OutputDebugString( ss.str().c_str() );
            break;
        }
        else
        {
            TranslateMessage(&msg); 
            DispatchMessage(&msg); 
        }
    } 



    OutputDebugString( "Bye, bye!\n" );

    return 0;
}

这是我为此创建的dll,km_inject.cpp/.h

km_inject.h:

#ifndef INCLUDED_keyloggermini_km_inject_h
#define INCLUDED_keyloggermini_km_inject_h

#if defined(__cplusplus__)
extern "C" {
#endif

LRESULT __declspec(dllimport)__stdcall CALLBACK LowLevelCBTProc(
  _In_  int nCode,
  _In_  WPARAM wParam,
  _In_  LPARAM lParam
);

#if defined(__cplusplus__)
};
#endif


#endif

km_inject.cpp:

#include <windows.h>
#include <utility>
#include <string>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <ctime>

using namespace std;

extern "C" LRESULT __declspec(dllexport)__stdcall CALLBACK LowLevelCBTProc(
  _In_  int nCode,
  _In_  WPARAM wParam,
  _In_  LPARAM lParam
)
{
#define HCBTCODE(m) case m: OutputDebugString( #m "\n" ); break;
    switch ( nCode )
    {
        HCBTCODE( HCBT_ACTIVATE );
        HCBTCODE( HCBT_CLICKSKIPPED );
        HCBTCODE( HCBT_CREATEWND );
        HCBTCODE( HCBT_DESTROYWND );
        HCBTCODE( HCBT_KEYSKIPPED );
        HCBTCODE( HCBT_MINMAX );
        HCBTCODE( HCBT_MOVESIZE );
        HCBTCODE( HCBT_QS );
        HCBTCODE( HCBT_SETFOCUS );
        HCBTCODE( HCBT_SYSCOMMAND );
    default:
        OutputDebugString( "HCBT_?\n" );
        break;
    }
    return CallNextHookEx( 0, nCode, wParam, lParam );
}

extern "C" BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        //
        break;

    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;

    case DLL_PROCESS_DETACH:
        //
        break;
    }
    return TRUE;
}

最佳答案

我很确定我知道这里发生了什么。 @500-InternalServerError 提到当他在注入(inject)的 dll 中有 OutputDebugString() 时,它似乎挂起并且没有安装。我认为这也是发生在我身上的事情。

OutputDebugString() 对 Vista 产生了非常刻薄的倾向。特别是,Vista 在 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter 中引入了调试输出过滤器。我以前偶然发现过这个,但是在内核调试的上下文中,这可能会导致你的 DbgPrint/OutputDebugString/printk 输出完全静音.

按照此处的说明 (http://blogs.msdn.com/b/doronh/archive/2006/11/14/where-did-my-debug-output-go-in-vista.aspx),我在调试输出中添加了一个完全允许的 DEFAULT 过滤器,然后重新启动。现在,当我运行我的键盘记录器时,在我的键盘记录器之后启动的每个应用程序似乎都获得了注入(inject)的 dll。有用! DebugView 现在几乎可以看到我在键盘记录器之后启动的每个应用程序的调试输出。

我认为根据@500-InternalServerError 的经验,也许当 Windows 在 Debug Print Filter 中没有看到 DEFAULT 过滤器时,这只是我的猜测,Windows 不会使 OutputDebugString 符号可用于链接,因此注入(inject) dll 会失败(无提示?)。已经链接到 OutputDebugString 的应用程序 - 如 DebugView 本身、Visual Studio、进程资源管理器和显然是 explorer.exe,都可以 - 我的注入(inject) dll 可以正确链接。无论如何,这是我的猜测。

谢谢大家的建议。

更新:

好吧,我对此不太确定了。我返回并删除了 DEFAULT 过滤器,我仍然可以看到我的 hook dll 被加载到新进程中。那么这里发生了什么?我真的不知道。滥用进程资源管理器?如果您不以管理员权限启动进程资源管理器,它将无法在所有进程中搜索特定的 dll。但即便如此,发现我启动的十几个或标准的非管理进程应该没有问题。

我无法再重现该问题。如果有人感兴趣,示例代码仍然可以在上面的链接中找到。显然我不会将此标记为答案,因为问题只是“消失了”。如果问题再次出现,我会进行更新。

关于c++ - SetWindowsHookEx + WH_CBT 不起作用?或者至少不是我认为应该的方式?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23508747/

相关文章:

c++ - 声明一个十六进制数的数组

c++ - 可变参数模板错误: 'In instantiation of'(gcc 9.2)

android - 为什么编译器不能从 sp<android::MetaData> 隐式转换为 bool?

c++ - CMD.EXE 以上述路径作为当前目录启动。不支持 UNC 路径。默认为 Windows 目录

c# - 在 .net4 中尝试 Dll rebase 似乎不起作用

windows - WindowsPE-如何运行 Electron 应用程序,缺少dll

c++ - 奇怪的 Unresolved external 编译 DLL

c++ - 如何使用指针从不同的函数访问局部变量?

ruby-on-rails - 无法在 Windows 上安装 ruby​​-debug gem

C++ 控制台应用程序总是在最前面?