复现KeyHook

复现《逆向工程核心原理》Hook

简介

利用Hook,我们可以查看,修改,截断 ‘用户-os-应用程序’ 之间所有的信息
示意图

接下来以keyBoard为例,实际操作一下,目的是向notepad.exe中注入KeyHook.dll实现键盘输入的钩取,并且改写成全局钩子,显示获取到的key

函数总览

main

HookMain.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
#include"stdio.h"
#include"Windows.h"
#include"conio.h"

#define DLL_NAME "KeyHook.dll"
#define HOOKSTART "HookStart"
#define HOOKSTOP "HookStop"

typedef void(*FN_HOOKSTART)();
typedef void(*FN_HOOKSTOP)();

int main()
{
HMODULE hDll = NULL;
FN_HOOKSTART HookStart = NULL;
FN_HOOKSTOP HookStop = NULL;

hDll = LoadLibraryA(DLL_NAME);

HookStart = (FN_HOOKSTART)GetProcAddress(hDll, HOOKSTART);
HookStop = (FN_HOOKSTOP)GetProcAddress(hDll, HOOKSTOP);

HookStart();

printf("press 'q' to quit this hook procdure");
while (_getch() != 'q');

HookStop();

FreeLibrary(hDll);

return 1;
}

dll

KeyHook.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
66
67
68
#include "stdio.h"
#include "windows.h"
#include <iostream>
#include <fstream>

#define PROCESS_NAME "notepad.exe"

HINSTANCE g_hInstance = NULL;
HHOOK g_Hook = NULL;
HWND g_hWnd = NULL;

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
g_hInstance = hinstDLL;

break;
case DLL_PROCESS_DETACH:

break;
default:
break;
}
return TRUE;
}

LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
char szPath[MAX_PATH] = { 0, };
char* p = NULL;
if (nCode >= 0)
{
if (!(lParam & 0x80000000))
{
GetModuleFileNameA(NULL, szPath, MAX_PATH);
p = strrchr(szPath, '\\');

if (!_stricmp(p + 1, PROCESS_NAME))
{

return 1;
}


}
}
return CallNextHookEx(g_Hook, nCode, wParam, lParam);
}
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
__declspec(dllexport) void HookStart()
{
g_Hook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);
}
__declspec(dllexport) void HookStop()
{
if (g_Hook)
{
UnhookWindowsHookEx(g_Hook);
g_Hook = NULL;
}
}
#ifdef __cplusplus
}
#endif // __cplusplus

逐块解释

dll

下面逐块解释一下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
HINSTANCE g_hInstance = NULL;  //实例化对象指针
HHOOK g_Hook = NULL; //钩子指针
HWND g_hWnd = NULL; //窗口指针

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
// Perform actions based on the reason for calling.
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
g_hInstance = hinstDLL; //初始化

break;
case DLL_PROCESS_DETACH:

break;
default:
break;
}
return TRUE;
}

这是DLL的程序入口,是一种框架代码,一般的Dll都会这样写。
程序会根据fdwReason的值执行不同的函数,fdwReason有四种取值

1
2
3
4
fdwReason 0 DLL_PROCESS_ATTACH  //DLL被加载
fdwReason 1 DLL_PROCESS_DETACH //DLL被卸载
fdwReason 2 DLL_THREAD_ATTACH //创建进程时
fdwReason 3 DLL_THREAD_DETACH //卸载进程时

当fdwReason为DLL_PROCESS_ATTACH时, lpvReserved为NULL表示动态加载,不为NULL表示静态加载。当fdwReason为DLL_PROCESS_DETACH时, lpvReserved为NULL表示FreeLibrary被调用或DLL加载失败,不为NULL表示进程正在终止。

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
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
//KeyboardProc 是一个回调函数,会作为SetWindowsHookEx的参数,具体处理Hook到的信息
//ncode参数决定如何处理这个消息
//wParam 以这个程序为例,存放虚拟键码
//lParam 存在一些标志,如按下和取消等等
char szPath[MAX_PATH] = { 0, };
char* p = NULL;
if (nCode >= 0)
{
if (!(lParam & 0x80000000)) //lParam的31bit代表是否按下
{
GetModuleFileNameA(NULL, szPath, MAX_PATH);
p = strrchr(szPath, '\\');
//拿到进程名字,这个函数是定位到字符出现的最后一个位置
if (!_stricmp(p + 1, PROCESS_NAME))
{

return 1;
}


}
}
return CallNextHookEx(g_Hook, nCode, wParam, lParam);
}

这个函数将作为SetWindowsHookEx函数的一个参数,负责处理Hook到的消息,通俗的来讲,你要对截获的信息进行处理,可以是舍弃,变更,修改等等,所以要写一个函数来实现,对于这个程序,功能为判断这个输入是否是在notepad.exe进程中,如果是就不向下传递,如果不是就调用CallNextHookEx(),将信息传递下去。

ncode参数

1
2
3
小于0:必须调用CallNextHookEx函数传递消息
0:表示参数wParam和lParam 包含关于虚拟键值相关信息
3:在值为0的基础上,表示这个消息被某个进程用PeekMessage查看过

也就是当ncode>0的时候,可以对进行操作,否则只能传递钩子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
__declspec(dllexport) void HookStart()
{
g_Hook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);
}
__declspec(dllexport) void HookStop()
{
if (g_Hook)
{
UnhookWindowsHookEx(g_Hook);
g_Hook = NULL;
}
}
#ifdef __cplusplus
}
#endif // __cplusplus
1
2
3
4
5
6
7
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus
...
#ifdef __cplusplus
}
#endif // __cplusplus

有些函数只能在c语言下解析,但是dll源文件是cpp,这个就是告诉编译器什么时候再c语言下解析,什么时候再cpp下解析。

__declspec(dllexport) 标志导出函数

1
2
3
4
5
6
HHOOK SetWindowsHookExA(
[in] int idHook, //标识钩子的种类 WH_KEYBOARD为键盘钩子
[in] HOOKPROC lpfn, //函数指针,这个参数填之前写好的回调函数
[in] HINSTANCE hmod, //实例
[in] DWORD dwThreadId //如果当前的钩子进程与现存的线程相关,那么它的值就是0
);

main

主函数较为简单
LoadLibraryA 加载dll
GetProcAddress 获取dll中的函数地址

测试

测试环境 : win10,xp

看到网上说win10环境下会出现问题,但是我自己试的的时候好像没什么问题。两种测试环境大同小异,win10要编译成64位可执行程序,xp编译成32位可执行程序即可。我这里使用g++编译器

1
2
3
4
g++ -shared ./KeyHook.cpp -o KeyHook.dll // -shared 表示要生成dll文件,-o 后跟dll的名字

g++ ./KeyHook.dll hookMain.cpp -o KeyHook.exe //生成exe文件

效果

生成这两个文件,说明成功

先运行exe文件,后打开notepad

打开notepad
此时键盘输入无效,打开进程管理工具发现KeyHook.dll成功注入

注入成功

xp 平台测试与win10类似,gcc要用32位版本,而且似乎xp并不支持在当前路劲打开cmd,要用绝对路径。

全局钩子和显示key

1
2
3
4
5
6
7
8
if (!_stricmp(p + 1, "conhost.exe"))
{
return CallNextHookEx(g_Hook, nCode, wParam, lParam);
return 1;
}
else{
GetKeyNameTextA(lParam,str,50); //定义一个str存放key
}
1
2
3
4
5
int GetKeyNameTextA(
[in] LONG lParam,
[out] LPSTR lpString,
[in] int cchSize
);

开始以为参数是存放在wParam中,一直没有成功。

在任何位置输入都会被拦截


复现KeyHook
https://lafdrew.github.io/2024/05/16/复现KeyHook/
Author
John Doe
Posted on
May 16, 2024
Licensed under