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

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

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

我也意识到,这强加了一点点的要求 – 如果我的DLL是64位,我不能注入到32位进程,反之亦然。

无论如何,我在VS2008debugging器中试过了,果然,我看到了OutputDebugString输出(我的处理程序调用了OutputDebugString )。 但只有在Visual Studio和DebugView中 – 当我将焦点切换到DebugView时,DebugView将显示焦点更改string输出。 当我切换回VSdebugging器时,VS输出窗口将显示焦点更改string输出。

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

我有点可疑,所以我启动了进程资源pipe理器,并search了我的注入DLL。 果然,只有less量的进程似乎得到的DLL。 当我build立的32位DLL,Visual Studio,DebugView, procexp.exe似乎都得到的DLL,但没有任何其他在我的机器上运行的32位进程。 当我build立的64位DLL, explorer.exeprocexp64.exe得到的DLL,而不是我的机器上的任何其他64位进程。

任何人都可以build议吗? 任何可能的解释? 是否有可能在某处logging事件,这可能解释为什么我的dll进入一个特定的进程,而不是另一个? SetWindowsHookEx报告ERROR_SUCCESSGetLastError 。 我在哪里可以看下一个?

更新:

我已经上传了演示这个的Visual Studio项目。

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

我使用cmake,不幸的是,cmake不会把32位和64位目标放在同一个sln中 – 所以64位.sln在_build64 ,而32位.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; } 

这是我为此创build的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_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter引入了调试输出过滤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筛选Debug Print Filter ,这只是我的猜测,Windows不会使OutputDebugString符号可用于链接和注入该DLL会失败(默默?)。 已经链接到OutputDebugString应用程序 – 比如DebugView本身,Visual Studio,进程管理器,显然是explorer.exe,都可以 – 我的注入DLL会正确链接。 无论如何,这是我的猜测。

谢谢大家的建议。

更新:

好吧,我不再那么确定了。 我回去并删除了DEFAULT筛选器,我仍然可以看到我的钩子DLL得到加载到新的进程。 那么这里发生了什么? 我真的不知道。 滥用进程资源管理器? 如果您不以管理员权限启动进程管理器,则无法搜索特定dll的所有进程。 但是,即使如此,我发现十几个或标准的非管理员流程也没有问题。

我再也不能重现这个问题了。 如果有人有兴趣,示例代码仍然可以在上面的链接。 很显然,我不打算把这个问题看成是一个答案,因为问题只是“消失”了。 如果问题恢复,我会更新。