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

时间:2014-05-07 04:28:17

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

我的诊断程序使用SetWindowsHookExWH_KEYBOARD_LL扫描系统范围内的代码我想将其扩展为监视窗口焦点更改,这可能是使用SetWindowsHookEx和基于计算机的培训CBT挂钩WH_CBT

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

我也知道这有点需要 - 如果我的dll是64位,我不能将它注入32位进程,反之亦然。

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

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

我有点怀疑所以我启动了进程资源管理器并搜索了我的注入dll。果然,只有少数几个进程似乎得到了dll。当我构建32位DLL时,Visual Studio,DebugView,OutputDebugString似乎都得到了dll,但不是我机器中任何其他正在运行的32位进程。当我构建64位DLL时,procexp.exeexplorer.exe获取dll,但不是我机器上的任何其他64位进程。

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

更新:

我已经上传了展示这一点的视觉工作室项目。

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

我使用cmake,不幸的是cmake不会将32位和64位目标放在同一个sln中 - 所以64位.sln位于GetLastError,而32位.sln位于_build64。为了清楚起见,你不需要cmake来试试这个 - 只是我用cmake来生成这些项目文件。

这是我的main.cpp

_build32

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

km_inject.h:

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

km_inject.cpp:

#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

1 个答案:

答案 0 :(得分:0)

我很确定我知道这里发生了什么。 @ 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在DEFAULT中看不到Debug Print Filter过滤器时,只是我的猜测,Windows并没有使OutputDebugString符号可用于链接,因此注入dll会失败(默默地?)。已经链接到OutputDebugString的应用程序 - 比如DebugView本身,Visual Studio,进程资源管理器,以及显然是explorer.exe,都可以 - 我的注入dll会正确链接。无论如何,这是我的猜测。

谢谢大家的建议。

更新:

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

我无法重现这个问题。如果有人感兴趣,示例代码仍可在上面的链接中找到。显然,我不打算将此作为答案,因为这个问题已经消失了#39;如果问题再次出现,我会更新。