0%

键盘钩子

钩子

百度百科的解释:


钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。
钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。


其实也是比较好理解的,就是Windows在处理一些东西的时候,例如发送数据包,删除文件夹,打字,点击鼠标之类的一些事件的时候都会根据钩子链这种东西一级一级的往下执行,比如我们的程序就有一个钩子在链中(我记得是后加入的钩子在钩子链的顶端),我们可以截获这个操作,然后对这个操作做一些手脚。

下面通过一个实例说明一下键盘钩子

键盘钩子

键盘钩子其实就是对键盘按下是的截获,之前一下垃圾盗号木马之类的应该就是截获这些鼠标钩子,如果我们截获的钩子不是当前我们的软件,而是想对某个进程或者系统进行截获,那就要用到DLL,因为DLL比较容易映射到别的进程中。

首先看一下程序的样子:
效果图

就是我们输入的内容都会被打印出来(但是我没有明确的进行大小写的区分,而是只根据lParam的扫描码直接输出的)。

DLL部分编写

首先是我们要编写我们的DLL文件,新建一个之后我们在dllmain.cpp中进行编写。

头文件的定义我们需要先看一下:

1
2
3
4
5
6
7
8
9
#ifdef KEYHOOKLIB_EXPORTS
#define KEYHOOKLIB_API __declspec(dllexport)
#else
#define KEYHOOKLIB_API __declspec(dllimport)
#endif

#define HM_KEY WM_USER+101

BOOL KEYHOOKLIB_API WINAPI SetHookkey(BOOL bInstall,DWORD dwThread = 0 ,HWND hWndCaller = NULL);

首先先是判断是不是定义了那个导出宏,如果定义了的话呢我们就将其设置为导出,如果不是的话呢我们就将其设置为导入,这样写方便我们在主程序中进行编写。
然后定义一个消息,用来接收我们发送接收键盘的信息。
在定义唯一一个导出函数,就是设置钩子。

然后我们在cpp文件引入如下内容:

1
2
3
4
5
6
7
8
9
10
#include "stdafx.h"
#include <Windows.h>

#define KEYHOOKLIB_EXPORTS
#include "KeyMonitor.h"

#pragma data_seg("Wker")//这里的必须初始化,否无效
HWND g_hWndCaller = NULL;
HHOOK g_hHook = NULL;
#pragma data_seg()

显示定义导出的宏,为了告诉编译器我们这个是要导出的函数,然后我们在设置一个区段,区段的内容必须初始化。这里设置区段是因为我们要讲我们的钩子放入别的程序中的时候我们的这个全局的变量如果不在一个我们自己的区段的话呢,那么每次被映射过去,我们的值都是没有了的。

首先是我们需要编写一个函数,使用来获取DLL实例句柄的:

1
2
3
4
5
6
7
8
9
10
11
12
HMODULE WINAPI ModuleFromAddress(PVOID pv)
{
MEMORY_BASIC_INFORMATION mib;
if (VirtualQuery(pv,&mib,sizeof(mib)))
{
return (HMODULE)mib.AllocationBase;

}else
{
return NULL;
}
}

通过VirtualQuery这个函数我们查询出要查询的内存地址各种信息,然后我们返回相对应的一个基地址。

然后我们就编写钩子函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
LRESULT CALLBACK KeyHookProc(
int nCode, // hook code passed to hook procedure
WPARAM wParam, // value passed to hook procedure
LPARAM lParam // value passed to hook procedure
)
{
if (nCode < 0 || nCode == HC_NOREMOVE)
{
return CallNextHookEx(g_hHook,nCode,wParam,lParam);
}
if (lParam & 0X40000000)//如果在发送消息之前键关闭
{
return CallNextHookEx(g_hHook,nCode,wParam,lParam);
}
PostMessage(g_hWndCaller,HM_KEY,wParam,lParam);
return CallNextHookEx(g_hHook,nCode,wParam,lParam);
}

首先如果小于0或者等于HC_NOREMOVE的时候是不行的。
MSDN说明:如果代码小于0,则钩子子程必须将消息传递给CallNextHookEx函数,而无需进一步处理,并且应该返回由CallNextHookEx返回的值。
HC_NOREMOVE:这个好像就是说这个消息没有被移除,具体为什么不要这个消息,这个我现在没怎么去深究。

然后看下lParam:

Value Description
0–15 Specifies the repeat count. The value is the number of times the keystroke is repeated as a result of the user’s holding down the key.
16–23 Specifies the scan code. The value depends on the original equipment manufacturer (OEM).
24 Specifies whether the key is an extended key, such as a function key or a key on the numeric keypad. The value is 1 if the key is an extended key; otherwise, it is 0.
25–28 Reserved.
29 Specifies the context code. The value is 1 if the alt key is down; otherwise, it is 0.
30 Specifies the previous key state. The value is 1 if the key is down before the message is sent; it is 0 if the key is up.
31 Specifies the transition state. The value is 0 if the key is being pressed and 1 if it is being released.

我们用0X40000000和他进行与操作,就是为了判断第三十位是不是0,如果在发送消息之前进行了关闭,那么我们就不接受这个东西了。
然后我们将我们接受到的消息发送给我们的窗口句柄。
最后返回值需要注意,返回0的话呢好像是截断,不继续传递下去了,如果要继续传递下去,那么就要return CallNextHookEx(g_hHook,nCode,wParam,lParam);

设置钩子函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
BOOL WINAPI SetHookkey(BOOL bInstall,DWORD dwThread /* = 0  */,HWND hWndCaller /* = NULL */)
{
BOOL bOK;
g_hWndCaller = hWndCaller;
if (bInstall)
{
g_hHook = SetWindowsHookEx(WH_KEYBOARD,KeyHookProc,ModuleFromAddress(KeyHookProc),dwThread);
bOK = (g_hHook != NULL);
}
else
{
bOK = UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
}

return bOK;
}

这里的话呢用到我们之前写的那个函数,这里有一点需要注意的就是,dwThread 如果是0的话呢就是系统。

然后我们写一下我们的配置文件:

1
2
3
4
EXPORTS
SetHookkey
SECTIONS
Wker Read Write Shared

编写好之后我们,我们就生成一下解决方案。
我们这里先用静态的方式打开DLL,不要忘记lib文件。

主程序

我们在主程序中进行导入:

1
2
3
#include "../KeyMonitor/KeyMonitor.h"

#pragma comment (lib,"KeyMonitor.lib")

导入我哦们的动态链接库和头文件,因为我们没有定义那个导出宏,所以我们这里的话呢就是导入。

然后加入一个Edit control,用来记录我们的信息。

首先在我们的初始化函数中加载钩子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
BOOL CMainKeyMonitorDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();

// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标

// TODO: 在此添加额外的初始化代码

if(!SetHookkey(TRUE,0,m_hWnd))
MessageBox("钩子加载失败");
retur

加载完毕钩子之后,在我们窗口销毁的时候销毁钩子:

1
2
3
4
5
void CMainKeyMonitorDlg::OnDestroy()
{
CDialogEx::OnDestroy();
SetHookkey(FALSE);
}

然后我们将我们的消息进行映射:

1
2
3
4
5
6
7
8
9
10
afx_msg LRESULT CMainKeyMonitorDlg::OnHmKey(WPARAM wParam, LPARAM lParam)
{
char szKey[80];
GetKeyNameText(lParam,szKey,80);
CString str,edit;
str.Format("用户按键:%s\r\n",szKey);
GetDlgItemText(IDC_RECEDIT,edit);
SetDlgItemText(IDC_RECEDIT,str+edit);
return 0;
}

通过GetKeyNameText这个函数获得我们的输入内容。


钩子类型更有很多,这里先就记录一下这个吧。