源代码:
FirstHook.exe的源代码(起到加载KeyHook.dll——安装钩子的作用)
FirstHook.cpp:
#include <iostream>
#include <Windows.h>
#include <conio.h>
#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.DLL
hDll = 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.DLL
FreeLibrary(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 <iostream>
#include <Windows.h>
#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,反之为KeyUp
if (!(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一起使用的回调函数。当GetMessage或PeekMessage获取到了有关键盘的消息(如: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.youkuaiyun.com/julius819/article/details/6882095
系统在收到相应 消息 时,会调用回调函数。那么 消息 又是什么呢?
于是我们又谈到了windows的消息机制
这里我先用《逆向工程核心原理》的话简单理解:
以记事本键盘敲击事件为例:
- 发生键盘输入事件时,WM_KEYDOWN消息被添加到系统消息队列。
- 系统判断哪个应用程序中发生了事件,然后从系统消息队列取出消息,添加到相应应用程序的线程消息队列(应用程序消息队列)中。
- 应用程序(如记事本)监视自身的应用程序消息队列,发现新添加的WM_KEYDOWN消息后,调用相应的事件处理程序(回调函数)处理。
如图:

现在对消息机制的理解还比较浅显,后续会进一步理解:
https://blog.youkuaiyun.com/dandycheung/article/details/7304151
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.youkuaiyun.com/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;
}
这篇博客详细解析了Windows下的钩子机制和DLL注入,通过FirstHook.exe和KeyHook.dll的例子展示了如何实现键盘钩子。FirstHook负责加载KeyHook.dll并调用其导出的HookStart和HookStop函数,KeyHook.dll则通过SetWindowsHookEx设置键盘钩子,拦截特定进程(如notepad.exe)的键盘输入。KeyboardProc作为回调函数,处理键盘消息。文章还探讨了CALLBACK和WINAPI的含义,以及extern C在DLL中的作用。
2637

被折叠的 条评论
为什么被折叠?



