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, _In_ DWORD dwThreadId);
|
UnhookWindowsHookEx()
卸载钩子
1 2 3 4 5 6
| WINUSERAPI BOOL WINAPI UnhookWindowsHookEx( _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);
}
#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)) { 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" { _declspec(dllexport) NativeManager* _stdcall CreateNativeManager(); _declspec(dllexport) void _stdcall ReleaseNativeManager(); _declspec(dllexport) void(_stdcall ExSetLogHandler)(LogHandler handler); _declspec(dllexport) void(_cdecl ReleaseNativeManager2)(); }
_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
| #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