【问题标题】:SetWindowsHookEx + WH_CBT doesn't work? Or at least not the way I think it should?SetWindowsHookEx + WH_CBT 不起作用?或者至少不是我认为应该的方式?
【发布时间】:2014-06-23 21:30:18
【问题描述】:

我有一个诊断程序,它使用SetWindowsHookExWH_KEYBOARD_LL 在系统范围内扫描代码我想扩展它以监视窗口焦点变化,这可以使用SetWindowsHookEx 和基于计算机的培训 CBT钩WH_CBT.

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

我也知道这会带来位要求 - 如果我的 dll 是 64 位,我无法将其注入 32 位进程,反之亦然。

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

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

我有点怀疑,所以我启动了进程资源管理器并搜索了我的注入 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 位于 @987654337 @。需要说明的是,你不需要 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;
}

【问题讨论】:

  • 如果 DLL 本身正在生成输出(而不是向您的进程发送消息),那么自然输出将由接收到事件的任何进程写入,因此您为 OutputDebugString 描述的行为正如预期的那样。另外,我相信钩子的工作方式意味着只有实际接收到相关事件的进程(即,自安装钩子后获得焦点的进程)才会加载 DLL,这可以解释您在 Process Explorer 中看到的内容.
  • OutputDebugString 映射并写入 Windows 中的共享调试页面 - 任何调用 OutputDebugString 的进程都将显示在 DebugView 中。将 DLL 注入进程并从中调用 OutputDebugString 仍应显示在 DebugView 中。
  • 仅供参考,如果最后一个函数调用失败,您只能相信GetLastError 的返回值。在这种情况下,这意味着SetWindowsHookEx 返回了NULL。如果函数成功,并且您获得了挂钩过程的有效句柄,那么您不能调用GetLastError。无论如何,您可能希望发布用于安装挂钩的代码。
  • 只是一个简短的说明:在我写这篇文章时,我碰巧正在开发一个同时使用 32 位和 64 位 Windows 挂钩的应用程序,我不得不删除所有对 OutputDebugString 的调用我的钩子触发,因为它们导致我的钩子挂起(大概)然后被 Windows(8.1.)卸载。我还没有完全研究确切的根本原因(如果我做的事情真的很愚蠢)所以这只是一个提示。
  • 别这样。请改用 SetWinEventHook()。

标签: c++ windows dll keyboard-hook setwindowshookex


【解决方案1】:

我很确定我知道这里发生了什么。 @500-InternalServerError 提到当他在注入的 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 过滤器,然后重新启动。现在,当我运行我的键盘记录器时,在我的键盘记录器之后启动的每个应用程序似乎都获得了注入的 dll。有用! DebugView 现在可以看到我在键盘记录器之后启动的几乎每个应用程序的调试输出。

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

谢谢大家的建议。

更新:

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

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

【讨论】:

  • 链接器符号不能像您建议的那样动态隐藏。它们要么存在,要么不存在。更有可能的是,OutputDebugString() 本身,或者它在内部与之通信的任何东西,如果缺少过滤器,就不会生成可捕获的调试事件,仅此而已。
  • 原则上,你可以这样做——如果 dll1 链接到 dll2,并且 LoadLibrary 在加载 dll1 时找不到 dll2,它肯定会失败。但这里的情况并非如此——OutputDebugString 在 kernel32.dll 中,而底层 DbgPrint 在 ntdll.dll 中,这两者都已经在任何 Windows 进程中了。
  • 这不是“静态”链接,也不是基于 IMPORTS 部分的隐式共享链接,这就是我认为您的意思。目标进程的 IMPORTS 表中没有我的钩子 dll。 Windows 用户模式库将使用 LoadLibrary 或类似的东西将我的钩子 dll 拉入其他进程。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-09-06
  • 2019-06-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-05
  • 2020-10-05
相关资源
最近更新 更多