【发布时间】:2014-06-23 21:30:18
【问题描述】:
我有一个诊断程序,它使用SetWindowsHookEx 和WH_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.exe 和 procexp64.exe 获取 dll,但没有获取我机器上的任何其他 64 位进程。
任何人都可以提出任何建议吗?有什么可能的解释吗?是否有可能在某处获取日志事件,这可以解释为什么我的 dll 进入一个特定进程而不是另一个? SetWindowsHookEx 报告 ERROR_SUCCESS 和 GetLastError。接下来我可以在哪里看?
更新:
我已经上传了演示这一点的 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