一、介绍
API hook(钩取)是一种用来拦截和修改 API 函数行为的技术。它常用于调试、逆向工程和游戏作弊。API 钩取涉及用一个自定义版本替换 API 函数的原始实现,该自定义版本在调用原始函数之前或之后执行一些附加动作。这允许人们在不修改程序源代码的情况下修改其行为 。
1.1 Trampolines(跳板)
实现 API 劫持的传统方法是通过跳转。Trampolines是一种 shellcode,用于通过跳转(jmp指令)到进程地址空间内的另一个特定地址来改变代码执行路径。Trampolines shellcode 被插入函数开头,导致函数被劫持。当被劫持的函数被调用时,将Trampolines shellcode,并且执行流被传递并更改到另一个地址,从而导致执行其他函数。
1.2 Inline Hooking
Inline Hook是一种替代执行 API 挂载的方法,其工作原理类似于基于跳转的hook。不同之处在于hook会将执行完成后返回到合法功能,允许正常执行继续进行。虽然更复杂且更难维护,但Inline Hook更有效率。
二、API Hooking - Detours库
Detours是 Microsoft Research 开发的软件库,可用于拦截和重定向 Windows 中的函数调用。该库将特定函数的调用重定向到用户定义的替换函数,然后该函数可以执行其他任务或修改原始函数的行为。Detours 通常用于 C/C++ 程序,可用于 32 位和 64 位应用程序。Detours 库将目标函数(即要挂钩的函数)的前几条指令替换为无条件跳转到用户提供的 detour 函数(即要执行的函数)。
要使用 Detours 库函数,必须下载并编译 Detours 存储库,以获取编译所需的静态库文件(.lib)。此外,还应包含 detours.h头文件
下载https://github.com/microsoft/Detours/releases
使用x64/x86 Visual Studio 2022 Developer Command Prompt nmake命令分别编译
编译过程中会出现error不影响是编译samples出现的问题,看到bin\lib对应程序集就成功了
添加包含目录修改库目录为对应的程序集
Detours API 函数
在使用任何钩子方法时,第一步总是获取要钩住的 WinAPI 函数的地址。函数的地址是确定将放置跳转指令的位置的必要条件。在 本文MessageBoxA
函数将被用作要钩住的函数。
以下是 Detours 库提供的 API 函数:
- DetourTransactionBegin - 开始一个新的事务用于附加或分离钩子。钩住和解钩时应首先调用此函数。
- DetourUpdateThread - 更新当前事务。Detours 库使用它将一个线程“登记”到当前事务。
- DetourAttach
DetourTransactionCommit
之前不会提交此钩子。
- 在当前事务中,将钩子安装到目标函数。在调用 - DetourDetach
DetourTransactionCommit
之前不会提交此钩子。
- 在当前事务中,从目标函数中移除钩子。在调用 - DetourTransactionCommit - 提交当前附加或分离钩子的事务。
上述函数返回一个 LONG
值,用于理解函数执行的结果。Detours API 将在成功时返回 NO_ERROR
(即 0),在失败时返回一个非零值。非零值可用作调试目的的错误代码。
替换已经挂钩的 API
下一步是创建一个函数来替换已经挂钩的 API。替换函数应该具有相同的数据类型,并且可以选择使用相同参数,这允许检查或修改参数值。例如,以下函数可以用作 MessageBoxA
的钩子函数,允许检查原始参数值。
INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {// 这里可以检查 hWnd - lpText - lpCaption - uType 参数 }
需要注意的是,替换函数可以使用较少参数,但不能使用比原始函数更多的参数,因为这样会访问无效地址,并抛出访问冲突异常。
无限循环问题
当连接到一个函数并触发钩子时,会执行自定义函数。但是,为了继续执行,自定义函数必须返回原始挂钩函数应返回的有效值。一种简单的做法是在挂钩中通过调用原始函数来返回相同的值。这可能会导致问题,因为会调用替换函数,从而导致无限循环。这是一个通用的挂钩问题,而不是 Detours 库中的错误。
为了更好地理解这一点,下面的代码段展示了替换函数 MyMessageBoxA
调用 MessageBoxA
。这样会导致无限循环。程序会陷入运行 MyMessageBoxA
的状态,这是因为 MyMessageBoxA
正在调用 MessageBoxA
,而 MessageBoxA
又会再次指向 MyMessageBoxA
函数。
INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {// 打印原始参数值printf("Original lpText Parameter : %s\n", lpText);printf("Original lpCaption Parameter : %s\n", lpCaption);// 不要这样做// 更改参数值return MessageBoxA(hWnd, "different lpText", "different lpCaption", uType); // 调用 MessageBoxA(已挂钩) }
解决方案一:全局原始函数指针
Detour 库可以通过在挂钩函数之前保存指向原始函数的指针来解决此问题。该指针可以存储在全局变量中,并在 detour 函数中调用,而不是挂钩函数。
// 用作在 `MyMessageBoxA` 中未挂钩的 MessageBoxA fnMessageBoxA g_pMessageBoxA = MessageBoxA;INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {// 打印原始参数值printf("原始 lpText 参数:%s\n", lpText);printf("原始 lpCaption 参数:%s\n", lpCaption);// 更改参数值// 调用未挂钩的 MessageBoxAreturn g_pMessageBoxA(hWnd, "不同的 lpText", "不同的 lpCaption", uType); }
解决方案 2 - 使用不同的 API
另一个值得一提的更通用的解决方法,是调用一个与目标函数具有相同功能的不同“未挂钩”函数。例如 MessageBoxA
和 MessageBoxW
、VirtualAlloc
和 VirtualAllocEx
。
INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {// 打印原始参数值printf("原始 lpText 参数 :%s\n", lpText);printf("原始 lpCaption 参数:%s\n", lpCaption);// 修改参数值return MessageBoxW(hWnd, L"不同的 lpText", L"不同的 lpCaption", uType); }
Detours 钩子函数
如前所述,Detours 库通过事务来工作,因此要钩取一个 API 函数,必须创建一个事务、提交一个操作(钩取/解钩)到事务,然后提交事务。下面的代码片段执行了这些步骤。
// 在 `MyMessageBoxA` 中用作未钩取的 MessageBoxA // 并被 `DetourAttach` 和 `DetourDetach` 使用 fnMessageBoxA g_pMessageBoxA = MessageBoxA;// 钩取后将在 MessageBoxA 代替运行的函数 INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {printf("[+] 原始参数:\n");printf("\t - lpText:%s\n", lpText);printf("\t - lpCaption:%s\n", lpCaption);return g_pMessageBoxA(hWnd, "不同的 lpText", "不同的 lpCaption", uType); }BOOL InstallHook() {DWORD dwDetoursErr = NULL;// 创建事务并更新它if ((dwDetoursErr = DetourTransactionBegin()) != NO_ERROR) {printf("[!] DetourTransactionBegin 失败,错误为:%d\n", dwDetoursErr);return FALSE;}if ((dwDetoursErr = DetourUpdateThread(GetCurrentThread())) != NO_ERROR) {printf("[!] DetourUpdateThread 失败,错误为:%d\n", dwDetoursErr);return FALSE;}// 在 g_pMessageBoxA 代替执行 MyMessageBoxA,g_pMessageBoxA 就是 MessageBoxAif ((dwDetoursErr = DetourAttach((PVOID)&g_pMessageBoxA, MyMessageBoxA)) != NO_ERROR) {printf("[!] DetourAttach 失败,错误为:%d\n", dwDetoursErr);return FALSE;}// 实际的钩子会在 `DetourTransactionCommit` 之后安装——提交事务if ((dwDetoursErr = DetourTransactionCommit()) != NO_ERROR) {printf("[!] DetourTransactionCommit 失败,错误为:%d\n", dwDetoursErr);return FALSE;}return TRUE; }
Detours 取消挂钩
/ 用于作为在 `MyMessageBoxA` 中取消挂钩的 MessageBoxA // 并由 `DetourAttach` 和 `DetourDetach` 使用 fnMessageBoxA g_pMessageBoxA = MessageBoxA;// 在挂钩时将代替 MessageBoxA 运行的函数 INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {printf("[+] 原始参数 : \n");printf("\t - lpText : %s\n", lpText);printf("\t - lpCaption : %s\n", lpCaption);return g_pMessageBoxA(hWnd, "不同的 lpText", "不同的 lpCaption", uType); }BOOL Unhook() {DWORD dwDetoursErr = NULL;// 创建事务并更新它if ((dwDetoursErr = DetourTransactionBegin()) != NO_ERROR) {printf("[!] DetourTransactionBegin 出错,错误码 : %d \n", dwDetoursErr);return FALSE;}if ((dwDetoursErr = DetourUpdateThread(GetCurrentThread())) != NO_ERROR) {printf("[!] DetourUpdateThread 出错,错误码 : %d \n", dwDetoursErr);return FALSE;}// 从 MessageBoxA 中移除挂钩if ((dwDetoursErr = DetourDetach((PVOID)&g_pMessageBoxA, MyMessageBoxA)) != NO_ERROR) {printf("[!] DetourDetach 出错,错误码 : %d \n", dwDetoursErr);return FALSE;}// 实际的挂钩移除发生在 `DetourTransactionCommit` 之后 - 提交事务if ((dwDetoursErr = DetourTransactionCommit()) != NO_ERROR) {printf("[!] DetourTransactionCommit 出错,错误码 : %d \n", dwDetoursErr);return FALSE;}return TRUE; }主函数 前面展示的挂钩和取消挂钩的例程不包括主函数。下面展示了主函数,它仅从挂钩和未挂钩版本调用 MessageBoxA。int main() {// 直接运行,未挂钩MessageBoxA(NULL, "您如何看待恶意软件开发?", "原始 MsgBox", MB_OK | MB_ICONQUESTION);//------------------------------------------------------------------// 挂钩if (!InstallHook())return -1;//------------------------------------------------------------------ // 不会直接运行 - 将运行 MyMessageBoxAMessageBoxA(NULL, "恶意软件开发是错误的", "原始 MsgBox", MB_OK | MB_ICONWARNING);//------------------------------------------------------------------// 取消挂钩if (!Unhook()) return -1;//------------------------------------------------------------------// 直接运行,已取消挂钩MessageBoxA(NULL, "正常 MsgBox 已恢复", "原始 MsgBox", MB_OK | MB_ICONINFORMATION);return 0; }
完整代码
#include<stdio.h> #include<windows.h> #include "detours.h"// If compiling as 64-bit #ifdef _M_X64 #pragma comment (lib, "detours.lib") #endif typedef INT (WINAPI *fnMessageBox)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);// Used as a unhooked MessageBoxA in `MyMessageBoxA`// And used by `DetourAttach` & `DetourDetach` fnMessageBox g_pMessageBoxA = (fnMessageBox)MessageBoxA;// The function that will run instead MessageBoxA when hooked INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {// log Original Parametersprintf("[+] Original Parameters : \n");printf("\t - lpText : %s\n", lpText);printf("\t - lpCaption : %s\n", lpCaption);// show different text and caption.return g_pMessageBoxA(hWnd, "different lpText", "different lpCaption", uType);}BOOL InstallHook() {DWORD dwDetoursErr = NULL;// Creating the transaction & updating itif ((dwDetoursErr = DetourTransactionBegin()) != NO_ERROR) {printf("[!] DetourTransactionBegin Failed With Error : %d \n", dwDetoursErr);return FALSE;}if ((dwDetoursErr = DetourUpdateThread(GetCurrentThread())) != NO_ERROR) {printf("[!] DetourUpdateThread Failed With Error : %d \n", dwDetoursErr);return FALSE;}// Running MyMessageBoxA instead of g_pMessageBoxA that is MessageBoxAif ((dwDetoursErr = DetourAttach(&(PVOID&)g_pMessageBoxA, MyMessageBoxA)) != NO_ERROR) {printf("[!] DetourAttach Failed With Error : %d \n", dwDetoursErr);return FALSE;}// Actual hook installing happen after `DetourTransactionCommit` - commiting the transactionif ((dwDetoursErr = DetourTransactionCommit()) != NO_ERROR) {printf("[!] DetourTransactionCommit Failed With Error : %d \n", dwDetoursErr);return FALSE;}return TRUE; }BOOL Unhook() {DWORD dwDetoursErr = NULL;// Creating the transaction & updating itif ((dwDetoursErr = DetourTransactionBegin()) != NO_ERROR) {printf("[!] DetourTransactionBegin Failed With Error : %d \n", dwDetoursErr);return FALSE;}if ((dwDetoursErr = DetourUpdateThread(GetCurrentThread())) != NO_ERROR) {printf("[!] DetourUpdateThread Failed With Error : %d \n", dwDetoursErr);return FALSE;}// Removing the hook from MessageBoxAif ((dwDetoursErr = DetourDetach(&(PVOID&)g_pMessageBoxA, MyMessageBoxA)) != NO_ERROR) {printf("[!] DetourDetach Failed With Error : %d \n", dwDetoursErr);return FALSE;}// Actual hook removal happen after `DetourTransactionCommit` - commiting the transactionif ((dwDetoursErr = DetourTransactionCommit()) != NO_ERROR) {printf("[!] DetourTransactionCommit Failed With Error : %d \n", dwDetoursErr);return FALSE;}return TRUE; }int main() {// Will run - not hookedMessageBoxA(NULL, "What Do You Think About Malware Development ?", "Original MsgBox", MB_OK | MB_ICONQUESTION);//------------------------------------------------------------------// Hookingif (!InstallHook()) {return -1;}//------------------------------------------------------------------// Won't run - will run MyMessageBoxA insteadMessageBoxA(NULL, "Malware Development Is Bad", "Original MsgBox", MB_OK | MB_ICONWARNING);MessageBoxA(NULL, "Is Bad", " MsgBox", MB_OK | MB_ICONWARNING);//------------------------------------------------------------------// Unhookingif (!Unhook())return -1;//------------------------------------------------------------------// Will run - hook removedMessageBoxA(NULL, "Normal MsgBox Again", "Original MsgBox", MB_OK | MB_ICONINFORMATION);return 0; }