API Hooking
一、介绍
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); // 更改参数值 // 调用未挂钩的 MessageBoxA return 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 函数,必须创建一个事务、提交一个操作(钩取/解钩)到事务,然后提交事务。下面的代码片段执行了这些步骤。
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 | // 在 `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 就是 MessageBoxA if ((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; //------------------------------------------------------------------ // 不会直接运行 - 将运行 MyMessageBoxA MessageBoxA(NULL, "恶意软件开发是错误的" , "原始 MsgBox" , MB_OK | MB_ICONWARNING); //------------------------------------------------------------------ // 取消挂钩 if (!Unhook()) return -1; //------------------------------------------------------------------ // 直接运行,已取消挂钩 MessageBoxA(NULL, "正常 MsgBox 已恢复" , "原始 MsgBox" , MB_OK | MB_ICONINFORMATION); return 0; } |
完整代码
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 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | #include<stdio.h> #include<windows.h> #include "detours.h" // 如果是64位系统,链接 detours 库 #ifdef _M_X64 #pragma comment (lib, "detours.lib") #endif // 定义一个函数指针类型 fnMessageBox,用于指向 MessageBoxA 函数 typedef INT(WINAPI* fnMessageBox)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType); // 定义一个全局的函数指针变量 g_pMessageBoxA,用于保存原始的 MessageBoxA 函数地址 fnMessageBox g_pMessageBoxA = (fnMessageBox)MessageBoxA; // 这是当钩子安装后替换 MessageBoxA 的函数 INT WINAPI MyMessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) { // 打印原始的 MessageBoxA 参数 printf( "[+] 原始参数 : \n" ); printf( "\t - lpText : %s\n" , lpText); printf( "\t - lpCaption : %s\n" , lpCaption); // 使用不同的文本和标题显示 MessageBox,而不是原来的文本 return g_pMessageBoxA(hWnd, "different lpText" , "different lpCaption" , uType); } // 安装钩子函数,将 MessageBoxA 替换为 MyMessageBoxA 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; } // 将原始的 MessageBoxA 替换为 MyMessageBoxA if ((dwDetoursErr = DetourAttach(&(PVOID&)g_pMessageBoxA, MyMessageBoxA)) != NO_ERROR) { printf( "[!] DetourAttach 失败,错误码:%d \n" , dwDetoursErr); return FALSE; } // 提交钩子事务,使钩子生效 if ((dwDetoursErr = DetourTransactionCommit()) != NO_ERROR) { printf( "[!] DetourTransactionCommit 失败,错误码:%d \n" , dwDetoursErr); return FALSE; } return TRUE; // 钩子安装成功 } // 卸载钩子函数,恢复 MessageBoxA 为原始版本 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; } // 移除钩子,将 MyMessageBoxA 恢复为原始的 MessageBoxA if ((dwDetoursErr = DetourDetach(&(PVOID&)g_pMessageBoxA, MyMessageBoxA)) != NO_ERROR) { printf( "[!] DetourDetach 失败,错误码:%d \n" , dwDetoursErr); return FALSE; } // 提交钩子事务,使钩子移除生效 if ((dwDetoursErr = DetourTransactionCommit()) != NO_ERROR) { printf( "[!] DetourTransactionCommit 失败,错误码:%d \n" , dwDetoursErr); return FALSE; } return TRUE; // 钩子卸载成功 } int main() { // 调用未被钩住的 MessageBoxA,显示原始的文本 MessageBoxA(NULL, "What Do You Think About Malware Development ?" , "Original MsgBox" , MB_OK | MB_ICONQUESTION); //------------------------------------------------------------------ // 安装钩子:替换 MessageBoxA 为 MyMessageBoxA if (!InstallHook()) { return -1; // 如果安装钩子失败,退出程序 } //------------------------------------------------------------------ // 调用 MessageBoxA 时,会触发 MyMessageBoxA,而不是原始的 MessageBoxA MessageBoxA(NULL, "Malware Development Is Bad" , "Original MsgBox" , MB_OK | MB_ICONWARNING); MessageBoxA(NULL, "Is Bad" , " MsgBox" , MB_OK | MB_ICONWARNING); //------------------------------------------------------------------ // 卸载钩子:恢复 MessageBoxA 为原始版本 if (!Unhook()) { return -1; // 如果卸载钩子失败,退出程序 } //------------------------------------------------------------------ // 卸载钩子后,MessageBoxA 会恢复为原始版本 MessageBoxA(NULL, "Normal MsgBox Again" , "Original MsgBox" , MB_OK | MB_ICONINFORMATION); return 0; // 程序正常退出 } |
本文来自博客园,作者:aoaoaoao,转载请注明原文链接:https://www.cnblogs.com/websecyw/p/18692572
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通