API Hooking

news/2025/1/27 13:54:17/文章来源:https://www.cnblogs.com/websecyw/p/18692572

一、介绍

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 函数:

  • DetourTransactionBeginopen - 开始一个新的事务用于附加或分离钩子。钩住和解钩时应首先调用此函数。
  • DetourUpdateThreadopen - 更新当前事务。Detours 库使用它将一个线程“登记”到当前事务。
  • DetourAttachopen - 在当前事务中,将钩子安装到目标函数。在调用 DetourTransactionCommit 之前不会提交此钩子。
  • DetourDetachopen- 在当前事务中,从目标函数中移除钩子。在调用 DetourTransactionCommit 之前不会提交此钩子。
  • DetourTransactionCommitopen - 提交当前附加或分离钩子的事务。

上述函数返回一个 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 和 MessageBoxWVirtualAlloc 和 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;
}

  

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/876228.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

杨立昆谈 Deepseek:开源正在超越私有;SpeechGPT 2.0-preview:情景智能拟人化实时交互系统

开发者朋友们大家好:这里是 「RTE 开发者日报」 ,每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE(Real-Time Engagement) 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文章 」、「有看点的 会议 」,但内容仅代表编辑…

【nginx】界面化管理Nginx站点的两种方式

前言 之前在文章🧲NginxUI:界面化管理Nginx的工具中简单介绍了NginxUI工具和搭建,本文则探讨怎么使用NginxUI来做界面化的站点管理。 使用docker启一个nginx-ui用于测试,启动命令: docker run -dit \--name=nginx-ui \--restart=always \-e TZ=Asia/Shanghai \-v /mnt/us…

20250127_C++高级编程

对象初始化1 类的4个常见的构造函数1.1 类对象初始化的3个函数1.1.1 默认构造函数 FunctionClass(int data = 10) :m_data(data) {cout << "FunctionClass(int)" << endl; }1.1.2 拷贝构造函数 FunctionClass::FunctionClass(const FunctionClass&…

vsCreator笔记_I/O控制

1, I/O接口端子说明 2, I/O接口功能分配 3, 以上为默认设置, 也可通过vsCreator环境设置\I/O端子设置 4, 输入信号接线示意图5, 输出信号接线示意图

Windows bat批处理读取文件增加行号

前言全局说明Windows bat批处理读取文件增加行号一、说明 1.1 环境: Windows 11 家庭版 23H2 22631.3737 Microsoft Windows [版本 10.0.22631.4751]二、文件内容 2.1 来源文件内容 文件名:source.ini a bCdE2.2 批处理内容 文件名:line_num.bat @echo offset "INI_FILE…

Linux 中 如何将time命令输出的时间信息保存在文件中

001、 通常做法[root@localhost test]# time seq 10 > a.txt 2> xxx ## 完全追加至xxxreal 0m0.002s user 0m0.000s sys 0m0.002s [root@localhost test]# ls a.txt xxx [root@localhost test]# cat a.txt 1 2 3 4 5 6 7 8 9 10 [root@localhost test]# c…

ABCDE:一个使用Kotlin编写的OpenHarmony逆向工具包

ABCDE是一个使用Kotlin编写的OpenHarmony逆向工具包,目前已经实现的功能为解析方舟字节码文件中 的类信息、方法信息、字面量数组信息以及对方法进行反汇编,解析资源索引文件等功能。 该工具核心功能由纯kotlin(jvm)实现,因此可以提供平台无关的jar包供java工程引用并二次…

【Linux性能】Linux系统中进程运行时间的五大精准检测法

在Linux操作系统的复杂而精细的运行环境中,准确了解各个进程的运行时间对于系统管理员和开发人员而言至关重要。这不仅有助于监控系统性能,还能为调试问题提供关键线索,进而实现对系统资源的高效管理。本文将深入探讨五种在Linux中检查进程运行时间的有效方法,每种方法都具…

VS Code 报错 __float128 is not supported on this target 解决方法

最近在使用 VS Code 时,每一个 cpp 文件都会有如下报错:在中文互联网上并没有搜索到很好的解决方案,但是在 stack overflow 上找到了一个比较好的回答:This problem may be caused by your VSCode using clang-tidy as the C/C++ extension. clang-tidy does not support _…

A Critique of ANSI SQL Isolation Levels.18687395

原文:A critique of ANSI SQL isolation levels摘要:ANSI SQL-92[MS, ANSI]使用脏读、不可重复读以及幻读现象(phenomena)定义了隔离级,本论文展示了这些现象,以及ANSI SQL定义并无法合适的描述众多流行的隔离级别,包括(ANSI标准)所涵盖的级别的标准锁实现。我们还介绍…