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

SetWindowsHookEx + WH_CBT doesn't work? Or at least not the way I think it should?

本文关键字:方式 WH CBT 不起作用 或者 SetWindowsHookEx      更新时间:2023-10-16

我有一个诊断程序,它使用SetWindowsHookExWH_KEYBOARD_LL在系统范围内扫描代码。我想将其扩展到监控窗口焦点的变化,这是使用SetWindowsHookEx和基于计算机的训练CBT挂钩WH_CBT的一种可能性。

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

我也知道这会带来一个bit ness要求——如果我的dll是64位的,我就不能将它注入32位进程,反之亦然。

无论如何,我在VS2008调试器中进行了尝试,果然,我看到了OutputDebugString输出(我的处理程序调用OutputDebugString)。但只有在VisualStudio和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进入一个特定的进程,而不是另一个进程?SetWindowsHookExGetLastError报告ERROR_SUCCESS。我下一步可以去哪里看?

更新:

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

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

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

这是我的主.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提到,当他在注入的dll中有OutputDebugString()时,它似乎挂起了,没有安装。我想这也是发生在我身上的事情。

OutputDebugString()在Vista上表现得非常糟糕。特别是,Vista在HKEY_LOCAL_MACHINESYSTEMCurrentControlSetControlSession ManagerDebug 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。但即使在那时,它也不应该有问题发现我启动的十几个或标准的非管理流程。

我再也无法重现这个问题了。如果有人感兴趣,示例代码仍然可以在上面的链接中找到。很明显,我不会把这个作为一个答案,因为问题只是"消失了"。如果问题再次出现,我会更新此信息。