Windows Hook的简单实现

 

源代码:

FirstHook.exe的源代码(起到加载KeyHook.dll——安装钩子的作用)

FirstHook.cpp:

#include 
#include 
#include #define DEF_DLL_NAME "KeyHook.dll"
#define DEF_HOOKSTART "HookStart"
#define DEF_HOOKSTOP "HookStop"// 定义了两个返回值为void的函数类型
typedef void(*PFN_HOOKSTART)();
typedef void(*PFN_HOOKSTOP)();using namespace std;
int main()
{HMODULE hDll = NULL;PFN_HOOKSTART HookStart = NULL;PFN_HOOKSTOP HookStop = NULL;char ch = 0;// 加载KeyHook.DLLhDll = LoadLibraryA(DEF_DLL_NAME);// 获取导出函数地址HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART);HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP);// 开始勾取HookStart();// 等待,直到用户输入"q"cout << "press 'q' to quit." << endl;while(_getch() != 'q');// 终止勾取HookStop();// 卸载KeyHook.DLLFreeLibrary(hDll);
}

我们来分析FirstHook.cpp中不清楚的点

SetWindowsHookEx:

WinUser.h中对SetWindowsHookEx的定义 :

SetWindowsHookExW(int idHook, // Hook响应的事件HOOKPROC lpfn, // Hook后系统请求的回调函数HINSTANCE hmod, // 当前所处的DLL句柄DWORD dwThreadId // 想要挂钩的线程ID(若为0则是全局钩子,影响所有进程)
);// 以下是针对UNICODE和ANSI不同版本的SetWindowsHookEx的宏定义
// 其中SetWindowsHookExW是针对UNICODE编码的,SetWindowsHookExA是针对ANSI的
#ifdef UNICODE
#define SetWindowsHookEx  SetWindowsHookExW
#else
#define SetWindowsHookEx  SetWindowsHookExA
#endif // !UNICODE

KeyHook.dll的源代码

KeyHook.dll:

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include 
#include #define DEF_PROCESS_NAME "notepad.exe" // 宏定义我们需要拦截输入的进程HMODULE g_hInstance = NULL;
HHOOK g_hHook = NULL;
HWND g_hWnd = NULL;BOOL WINAPI DllMain(HINSTANCE hinstDLL,DWORD  ul_reason_for_call,LPVOID lpReserved
)
{switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:g_hInstance = hinstDLL;break;case DLL_PROCESS_DETACH:break;}return TRUE;
}LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{char szPath[MAX_PATH] = { 0, };char *p = NULL;if (nCode >= 0){// lParam第三十一位若为0,则表示KeyDown,反之为KeyUpif (!(lParam & 0x80000000)){GetModuleFileNameA(NULL, szPath, MAX_PATH);p = strrchr(szPath, '\\');// 比较当前进程名称,若为notepad.exe,则消息不会传递给应用程序(或下一个“钩子”)if (!_stricmp(p + 1, DEF_PROCESS_NAME))return 1;}}// 若非notepad.exe,则调用CallNextHookEx()函数,将消息传递给应用程序(或下一个“钩子”)return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}#ifdef __cplusplus
extern "C" {
#endif__declspec(dllexport) void HookStart(){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

这个DLL中有些不清楚的点,我们分析一下。

KeyboardProc:

以下是MSDN上对KeyboardProc的解释:

An application-defined or library-defined callback function used with the SetWindowsHookEx function. The system calls this function whenever an application calls the GetMessage or PeekMessage function and there is a keyboard message (WM_KEYUP or WM_KEYDOWN) to be processed.

The HOOKPROC type defines a pointer to this callback function. KeyboardProc is a placeholder for the application-defined or library-defined function name.

来自:https://docs.microsoft.com/zh-cn/previous-versions/windows/desktop/legacy/ms644984(v=vs.85)

 简单的说,KeyboardProc是一个与SetWindowsHookEx一起使用的回调函数。当GetMessagePeekMessage获取到了有关键盘的消息(如:WM_KEYUP或WM_KEYDOWN)时,系统会调用它。

那么回调函数是什么呢?我当前基于以下的博客内容理解:

凡是由你设计却由windows系统呼叫的函数,统称为callback函数。某些API函数要求以callback作为你参数之一。如SetTimer,LineDDA,EnumObjects。用某个函数(通常是API函数)时,将自己的一个函数(这个函数为回调函数)的地址作为参数传递给那个函数。而那个函数在需要的时候,利用传递的地址调用回调函数,这时你可以利用这个机会在回调函数中处理消息或完成一定的操作。至于如何定义回调函数,跟具体使用的API函数有关,一般在帮助中有说明回调函数的参数和返回值等。C++中一般要求在回调函数前加CALLBACK,这主要是说明该函数的调用方式。

    回调函数是由开发者按照一定的原形进行定义的函数(每个回调函数都必须遵循这个原则来设计)

说明:
    1  回调函数必须有关键词 CALLBACK;回调函数本身必须是全局函数或者静态函数。不要使用类的成员函数(也就是说 要使用全局函数) 作为callback函数,在成员函数前使用static,也就是在函数前加上static修饰词,可以声明回调函数。

    2  回调函数并不由开发者直接调用执行(只是使用系统接口API函数作为起点)
    3  回调函数通常作为参数传递给系统API,由该API来调用
    4  回调函数可能被系统API调用一次,也可能被循环调用多次

来自:https://blog.csdn.net/julius819/article/details/6882095

 系统在收到相应 消息 时,会调用回调函数。那么 消息 又是什么呢?

于是我们又谈到了windows的消息机制

这里我先用《逆向工程核心原理》的话简单理解:

以记事本键盘敲击事件为例:

  1. 发生键盘输入事件时,WM_KEYDOWN消息被添加到系统消息队列
  2. 系统判断哪个应用程序中发生了事件,然后从系统消息队列取出消息,添加到相应应用程序的线程消息队列(应用程序消息队列)中。
  3. 应用程序(如记事本)监视自身的应用程序消息队列,发现新添加的WM_KEYDOWN消息后,调用相应的事件处理程序(回调函数)处理。

 如图:

3

现在对消息机制的理解还比较浅显,后续会进一步理解:

https://blog.csdn.net/dandycheung/article/details/7304151

https://blog.csdn.net/Jacoob1024/article/details/80849972?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.control

https://www.cnblogs.com/zhoug2020/p/6239018.html

https://www.cnblogs.com/weekbo/p/10442165.html

CALLBACK与WINAPI:

刚开始并不清楚函数名前的CALLBACK和WINAPI是什么意思,于是转到定义便豁然开朗

在WinUser.h中是这么定义的:

#define CALLBACK    __stdcall
#define WINAPI      __stdcall
#define WINAPIV     __cdecl
#define APIENTRY    WINAPI
#define APIPRIVATE  __stdcall
#define PASCAL      __stdcall

CALLBACK与WINAPI都是函数调用方式,被定义为__stdcall。

有关函数调用方式,可以看这篇文章: https://blog.csdn.net/qinrenzhi/article/details/94997119

有空(并不 想自己再写一篇有关函数调用方式的探索,也算对CSAPP的复习。

编写dll时为什么有extern "C":

原因:因为C和C++的重命名规则是不一样的。这种重命名称为“Name-Mangling”,extern "C" 是为了确保函数名一致。

具体可以看这篇文章:https://www.cnblogs.com/cswuyg/archive/2011/09/30/dll.html

__declspec(dllexport)的作用:

_declspec(dllexport)用在dll上,用于说明这是导出的函数。

_declspec(dllimport)用在调用dll的程序中,用于说明这是从dll中导入的函数,但不是必须的。

同样看:https://www.cnblogs.com/cswuyg/archive/2011/09/30/dll.html

DllMain函数:

每一个动态链接库都会有一个DllMain函数。如果在编程时没有定义,编译器会给你加上。

格式如下:

BOOL APIENTRY DllMain( HANDLE hModule,DWORD  ul_reason_for_call,LPVOID lpReserved)
{switch(ul_reason_for_call){case DLL_PROCESS_ATTACH:printf("\nprocess attach of dll");break;case DLL_THREAD_ATTACH:printf("\nthread attach of dll");break;case DLL_THREAD_DETACH:printf("\nthread detach of dll");break;case DLL_PROCESS_DETACH:printf("\nprocess detach of dll");break;}return TRUE;
}

 


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部