API hook - 自定义代码

news/2025/3/12 1:21:35/文章来源:https://www.cnblogs.com/websecyw/p/18692834

一、介绍

开源hook库已被用于实现 API 挂钩。然而,这种方法的一个主要问题是这些库的源代码是公开可用的,使得安全研究人员和安全产品供应商可以很直接地构建 IoC。因此,本文将手动实现 API 挂钩,虽然不如前面演示的库复杂,但足以在没有 IoC 的情况下实现预期结果,如果只想挂钩单个函数,自定义挂钩代码会是一个更好的选择。这样可以避免链接其他库的额外工作,以及避免这些库给二进制文件大小带来的额外负担。

二、创建跳转代码

一种实现函数钩子的方法是改写其前几条指令,用我们想执行的新指令覆盖。新指令通常称为跳转,负责将函数的执行流转到替代函数。该跳转壳码通常是一个小的 jmp ,它执行一个 jmp 指令,跳到要执行的函数的地址上。为了执行 jmp 指令,必须将要跳转到的地址保存在一个寄存器中。在给出的示例中,在 32 位处理器上使用的寄存器是 eax,在 64 位处理器上使用的寄存器是 r10。将地址保存在这些寄存器中时,会用到 mov 指令。

64 位跳转 Shellcode

64 位跳转 Shellcode 如下:

mov r10, pAddress  
jmp r10 

其中 pAddress 是要跳转到的函数地址(例如 0x0000FFFEC32A300)。在代码中使用这些指令之前,必须先将其转换为 opcode

0x49, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, pAddress
0x41, 0xFF, 0xE2                                            // jmp r10

32 位 跳转 Shellcode如下:

32 位版本:

mov eax, pAddress  
jmp eax

同样,将指令转换为操作码。 

0xB8, 0x00, 0x00, 0x00, 0x00,     // mov eax, pAddress
0xFF, 0xE0                        // jmp eax

请注意,pAddress 表示为 NULL,这就解释了 0x00 序列。这些 0x00 操作码是占位符,在运行时将被覆盖。

 检索 pAddress

Hook 是在运行时安装的,因此必须在运行时检索并向 shellcode 添加 pAddress 值。可以使用 GetProcAddress 检索地址,一经完成,memcpy 用于将地址复制到 shellcode 中的正确位置。

64位补丁

uint8_t	uTrampoline[] = {0x49, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 将 r10 寄存器设置为 pFunctionToRun 的值0x41, 0xFF, 0xE2                                            // 跳转到 r10 寄存器的值
};uint64_t uPatch = (uint64_t)pAddress;
memcpy(&uTrampoline[2], &uPatch, sizeof(uPatch)); // 将地址复制到 uTrampoline 中偏移量为 '2' 的位置
 为什么要从uTrampoline[2]地址开始呢因为索引[2]是0x00也就是我们的地址占位符pFunctionToRun然后进行覆盖
32位补丁
uint8_t		uTrampoline[] = {0xB8, 0x00, 0x00, 0x00, 0x00,     // mov eax, pFunctionToRun0xFF, 0xE0                        // jmp eax
};uint32_t uPatch = (uint32_t)pAddress;
memcpy(&uTrampoline[1], &uPatch, sizeof(uPatch)); // 将地址复制到 uTrampoline 中的偏移量“1”处

如前所述,pAddress是目标函数的地址。uint32_tuint64_t数据类型用于确保地址为正确数量的字节,即32位机器为4字节,64位机器为8字节。uint32_t的大小为4字节,uint64_t的大小为8字节。memcpy将通过覆盖0x00占位字节,将地址放入跳转代码中。

 编写跳转代码

在使用准备好的 shellcode 覆盖目标函数的前几个指令之前,将跳转代码要写入的内存空间标记为可写非常重要。在大多数情况下,内存区域不可写,需要使用 VirtualProtect WinAPI 将内存权限更改为 PAGE_EXECUTE_READWRITE。值得注意的是,该内存区域必须可写且可执行,因为当程序调用该函数时,它需要执行在只写内存中不允许的指令。

考虑到这一点,跳转代码应首先修改目标函数的权限,然后再复制 shellcode。 

// 将 pFunctionToHook 处的内存权限更改为 PAGE_EXECUTE_READWRITE
if (!VirtualProtect(pFunctionToHook, sizeof(uTrampoline), PAGE_EXECUTE_READWRITE, &dwOldProtection)) {return FALSE;
}// 将跳转 shellcode 复制到 pFunctionToHook
memcpy(pFunctionToHook, uTrampoline, sizeof(uTrampoline));

其中 pFunctionToHook 是要挂钩的函数地址,uTrampoline 是跳转 shellcode。

 取消钩子

当被钩取的函数被调用时,跳转外壳代码应该同时适用于 64 位和 32 位架构。然而,我们还没有讨论如何取消钩子。要做到这一点,需要使用在安装跳转外壳代码之前创建的包含这些字节的缓冲区,还原被跳转外壳覆盖的原始字节。然后,取消钩子时应将此缓冲区用作 memcpy 函数中的源缓冲区。

memcpy(pFunctionToHook, pOriginalBytes, sizeof(pOriginalBytes));

其中,pFunctionToHook 是被钩取的函数的地址,pOriginalBytes 是保存函数原始字节的缓冲区,这些字节应该在钩取前保存,可以通过 memcpy 调用来完成。pOriginalBytes 缓冲区的大小应与跳转外壳代码大小相同,这样只能覆盖外壳代码。最后,建议还原内存权限,可以通过以下代码段完成。

if (!VirtualProtect(pFunctionToHook, sizeof(uTrampoline), dwOldProtection, &dwOldProtection)) {return FALSE;
}

其中,dwOldProtection 是第一个 VirtualProtect 调用返回的旧内存权限。

 HookSt 结构体

为了方便实现,创建了 HookSt 结构体。此结构体将包含用来对特定函数进行挂接和取消挂接所需的信息。 对于设置为编译为 64 位应用程序的程序,将 TRAMPOLINE_SIZE 值设置为 13;而对于设置为在 32 位模式下编译的程序,则将其设置为 7。值 13 和 7 是 trampoline(跳转代码)shellcode 的大小,分别在前面显示的 uTrampoline 变量中表示 64 位和 32 位系统。

typedef struct _HookSt {PVOID	pFunctionToHook; // 要挂接的函数的地址PVOID	pFunctionToRun; // 要改为运行的函数的地址BYTE	pOriginalBytes[TRAMPOLINE_SIZE]; // 缓冲区,用于存储一些原始字节(清理时需要)DWORD	dwOldProtection; // 保存“要挂接的函数”地址的旧内存保护(清理时需要)
} HookSt, *PHookSt;

通过以下预处理程序代码设置 TRAMPOLINE_SIZE 值:

// 如果编译为 64 位
#ifdef _M_X64
#define TRAMPOLINE_SIZE		13
#endif // _M_X64// 如果编译为 32 位
#ifdef _M_IX86
#define TRAMPOLINE_SIZE		7
#endif // _M_IX86

安装钩子 

以下函数使用 HookSt 来安装钩子。

BOOL InstallHook (IN PHookSt Hook) {#ifdef _M_X64// 64 位跳转代码uint8_t	uTrampoline [] = {0x49, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, pFunctionToRun0x41, 0xFF, 0xE2                                            // jmp r10};// 将调用地址 (pFunctionToRun) 补丁到 shellcode 中uint64_t uPatch = (uint64_t)(Hook->pFunctionToRun);// 将调用地址复制到 uTrampoline 中的偏移量 '2'memcpy(&uTrampoline[2], &uPatch, sizeof(uPatch));
#endif // _M_X64#ifdef _M_IX86// 32 位跳转代码uint8_t	uTrampoline[] = {0xB8, 0x00, 0x00, 0x00, 0x00,     // mov eax, pFunctionToRun0xFF, 0xE0                        // jmp eax};// 将调用地址 (pFunctionToRun) 补丁到 shellcode 中uint32_t uPatch = (uint32_t)(Hook->pFunctionToRun);// 将调用地址复制到 uTrampoline 中的偏移量 '1'memcpy(&uTrampoline[1], &uPatch, sizeof(uPatch));
#endif // _M_IX86// 放置跳转代码函数 - 安装钩子memcpy(Hook->pFunctionToHook, uTrampoline, sizeof(uTrampoline));return TRUE;
}

卸载钩子  

下面的函数使用 HookSt 移除钩子。

BOOL RemoveHook (IN PHookSt Hook) {DWORD	dwOldProtection = NULL;// 复制原始字节memcpy(Hook->pFunctionToHook, Hook->pOriginalBytes, TRAMPOLINE_SIZE);// 清理我们的缓冲区memset(Hook->pOriginalBytes, '\0', TRAMPOLINE_SIZE);// 将旧内存保护设置回钩入前的状态if (!VirtualProtect(Hook->pFunctionToHook, TRAMPOLINE_SIZE, Hook->dwOldProtection, &dwOldProtection)) {printf("[!] VirtualProtect 失败,错误代码:%d \n", GetLastError());return FALSE;}// 全部设为 nullHook->pFunctionToHook   = NULL;Hook->pFunctionToRun    = NULL;Hook->dwOldProtection   = NULL;return TRUE;
}

填充 HookSt 结构

InitializeHookStruct 函数用于用执行挂钩所需的信息填充 HookSt 结构。

 
BOOL InitializeHookStruct(IN PVOID pFunctionToHook, IN PVOID pFunctionToRun, OUT PHookSt Hook) {// 填充结构Hook->pFunctionToHook   = pFunctionToHook;Hook->pFunctionToRun    = pFunctionToRun;// 保存我们将覆盖的相同大小的原始字节(即 TRAMPOLINE_SIZE)// 这是为了在完成时能够进行清理memcpy(Hook->pOriginalBytes, pFunctionToHook, TRAMPOLINE_SIZE);// 将保护更改为 RWX 以便我们可以修改字节// 我们将旧保护保存到结构中(以便在清理时重新放置它)if (!VirtualProtect(pFunctionToHook, TRAMPOLINE_SIZE, PAGE_EXECUTE_READWRITE, &Hook->dwOldProtection)) {printf("[!] VirtualProtect 失败,错误代码:%d \n", GetLastError());return FALSE;}return TRUE;
}

 完整代码

#include <stdio.h>
#include <Windows.h>
#include <stdint.h>// 如果是x64编译
#ifdef _M_X64
#define TRAMPOLINE_SIZE 13 // _M_X64
#endif// 如果是x32编译
#ifdef _M_IX86
#define TRAMPOLINE_SIZE 7 // _M_X32
#endif// 自定义的 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);// 由于基于跳板的钩子方法,无法调用全局的原始函数指针来恢复执行。因此,调用 MessageBoxW 函数来替代。return MessageBoxW(hWnd, L"不同的 lpText", L"不同的 lpCaption", uType);
}// 存储钩子安装和卸载所需的信息的结构体
typedef struct _HookSt {PVOID pFunctionToHook; // 要钩取的函数的地址PVOID pFunctionToRun;  // 替代运行的函数的地址BYTE pOriginalBytes[TRAMPOLINE_SIZE]; // 用于保存被覆盖的原始字节DWORD dwOldProtection; // 保存“要钩取函数”的原始内存保护标志}HookSt, * PHookSt;// 初始化钩子结构体
BOOL InitializeHookStruct(IN PVOID pFunctionToHook, IN PVOID pFunctionToRun, OUT PHookSt Hook) {// 填充结构体Hook->pFunctionToHook = pFunctionToHook;Hook->pFunctionToRun = pFunctionToRun;// 保存原始字节,大小为我们将要覆盖的字节数(即 TRAMPOLINE_SIZE)// 这样做是为了在移除钩子时能够恢复原始内容memcpy(Hook->pOriginalBytes, pFunctionToHook, TRAMPOLINE_SIZE);// 更改内存保护为 RWX,这样我们就可以修改字节// 我们将原始保护标志保存到结构体中,以便在清理时恢复if (!VirtualProtect(pFunctionToHook, TRAMPOLINE_SIZE, PAGE_EXECUTE_READWRITE, &Hook->dwOldProtection)) {printf("[!] VirtualProtect 失败,错误码 : %d \n", GetLastError());return FALSE;}return TRUE;
}// 安装钩子
BOOL InstallHook(IN PHookSt Hook) {#ifdef _M_X64 // 64位的跳板代码uint8_t	uTrampoline[] = {0x49, 0xBA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // mov r10, pFunctionToRun // 指针是 8 字节,在 x64 中 // 地址表示为 NULL。0x41, 0xFF, 0xE2                                            // jmp r10};// uPatch 是目标函数地址。我们使用 uint32_t 和 uint64_t 数据类型来确保地址的大小正确。// uint32_t 是 4 字节,uint64_t 是 8 字节。// 将跳转地址填充到跳板中uint64_t uPatch = (uint64_t)(Hook->pFunctionToRun);// 将跳转地址填充到 uTrampoline 数组的第 2 个字节位置memcpy(&uTrampoline[2], &uPatch, sizeof(uPatch));
#endif // _M_X64#ifdef _M_IX86 // 32位的跳板代码uint8_t	uTrampoline[] = {0xB8, 0x00, 0x00, 0x00, 0x00,     // mov eax, pFunctionToRun0xFF, 0xE0                        // jmp eax};// 将目标函数的地址填充到跳板代码中uint32_t uPatch = (uint32_t)(Hook->pFunctionToRun);// 将地址填充到 uTrampoline 数组的第 1 个字节位置memcpy(&uTrampoline[1], &uPatch, sizeof(uPatch));
#endif // _M_IX86// 将跳板代码写入要钩取的函数地址,安装钩子memcpy(Hook->pFunctionToHook, uTrampoline, sizeof(uTrampoline));return TRUE;
}// 卸载钩子
BOOL RemoveHook(IN PHookSt Hook) {DWORD	dwOldProtection = NULL;// 将原始字节恢复到被钩取的函数memcpy(Hook->pFunctionToHook, Hook->pOriginalBytes, TRAMPOLINE_SIZE);// 清空我们的字节缓冲区memset(Hook->pOriginalBytes, '\0', TRAMPOLINE_SIZE);// 恢复原始的内存保护权限if (!VirtualProtect(Hook->pFunctionToHook, TRAMPOLINE_SIZE, Hook->dwOldProtection, &dwOldProtection)) {printf("[!] VirtualProtect 失败,错误码 : %d \n", GetLastError());return FALSE;}// 将钩子结构体中的所有字段重置为 NULLHook->pFunctionToHook = NULL;Hook->pFunctionToRun = NULL;Hook->dwOldProtection = NULL;return TRUE;
}int main() {// 初始化结构体(在安装或移除钩子之前需要)HookSt st = { 0 };// 初始化钩子结构体if (!InitializeHookStruct(&MessageBoxA, &MyMessageBoxA, &st)) {return -1;}// 原始函数将会执行MessageBoxA(NULL, "这是一个恶意软件开发例子吗?", "原始 MsgBox", MB_OK | MB_ICONQUESTION);// 安装钩子if (!InstallHook(&st)) {return -1;}// 安装了钩子后,这行代码将不会执行MessageBoxA(NULL, "恶意软件开发是不好的", "原始 MsgBox", MB_OK | MB_ICONWARNING);// 卸载钩子if (!RemoveHook(&st)) {return -1;}// 卸载钩子后,恢复原始的 MessageBoxA 函数MessageBoxA(NULL, "恢复正常的 MsgBox", "原始 MsgBox", MB_OK | MB_ICONINFORMATION);return 0;
}

 
 
在x64dbg当中对messageboxA下断点

点击->使程序运行至断点处  

 步进到函数内部看到call &MessageBoxA步进

 可以看到汇编指令为我们替换过的

 

 

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

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

相关文章

【持续更新】【专题】初等数论

【持续更新】【专题】初等数论 Designed By:FrankWkd 【100%原创】【禁止搬运】 Updated at 2025.01.26 前言:主要从线性筛开始速通初等数论 尽可能的多证明结论而不是阐述结论。如果你只是想回顾结论,请看其他人的 \(Blog\) .一、基础概念整除:对于两个正整数 \(a,b\), 存…

[2025.1.27 MySQL学习] SQL优化

SQL优化 Insert优化批量插入Insert into emp values(1,tom),(2,mike),(3,john); 手动提交事务start transaction;、commit; 主键顺序插入,1 2 3 4 5 6 7... 大批量插入数据,Insert性能较低,可以使用load指令,使用指令:#客户端连接服务器,加上参数--local-infile mysql --…

DeepSeek-R1:开源Top推理模型的实现细节、使用与复现

核心观点 ● 直接用强化学习就可以让模型获得显著的推理能力,说明并不一定需要SFT才行。 ● 强化学习并不一定需要复杂的奖励模型,使用简单的规则反而取得意想不到的效果。 ● 通过知识蒸馏让小模型一定程度上也有推理能力,甚至在某些场景下的表现超过了Top模型,比直接在小…

高通平台Android源码bootloader分析之sbl1(一)

高通8k平台的boot过程搞得比较复杂, 我也是前段时间遇到一些问题深入研究了一下才搞明白。不过虽然弄得很复杂,我们需要动的东西其实很少,modem侧基本就sbl1(全称:Secondary boot loader)的代码需要动一下,ap侧就APPSBL代码需要动(对此部分不了解,可参照:bootable 源…

读量子霸权17模拟宇宙(下)

黑洞、暗物质、粒子标准模型及超越理论被探讨,弦理论为领先候选,量子计算机模拟宇宙成为可能,平行宇宙理论也被提出,物理学界寻求宇宙终极理论。1. 黑洞 1.1. 模拟黑洞可以很快耗尽普通数字超级计算机的计算能力 1.2. 并没有人真正知道当一颗大质量恒星在引力作用下坍缩时会…

VSCode 接入DeepSeek V3大模型

转载自: VSCode 接入DeepSeek V3大模型,附使用说明 - 唯知笔记 DeepSeek V3 是一个拥有 6710 亿参数的专家混合(MoE)语言模型。最新评估表明,DeepSeek V3 已经超越了其他开源模型。重点是:国内(不需要工具),便宜(10块钱大约500万tokens)。 作为日常开发使用的编辑器 VSC…

06_LaTeX之特色工具和功能

本文介绍一些特色的 $\LaTeX{}$ 辅助功能。前两个功能 $\texttt{BibTeX}$ 和 $\texttt{makeindex}$ 依靠一些辅助程序自动生成参考文献、索引等;之后的使用颜色、超链接等则令我们生成美观易用的电子文档。06_\(\LaTeX{}\) 之特色工具和功能 目录06_\(\LaTeX{}\) 之特色工具和…

程序员常用高效实用工具推荐,办公效率提升利器!

前言 在当今这个技术日新月异的时代,开发者只有持续学习,才能紧跟时代的浪潮。为了助力开发者在高效学习与工作中实现平衡(告别996的束缚),众多卓越且实用的开发工具应运而生,它们如同强大的助力器,极大地提升了我们的工作效率与创造力。🚀Gitee加速访问: https://gi…

Cisco NX-OS System Software - ACI 16.0(8f)M - 适用于 ACI 模式下的 Nexus 9000 系列交换机系统软件

Cisco NX-OS System Software - ACI 16.0(8f)M - 适用于 ACI 模式下的 Nexus 9000 系列交换机系统软件Cisco NX-OS System Software - ACI 16.0(8f)M 适用于 ACI 模式下的 Cisco Nexus 9000 系列交换机系统软件 请访问原文链接:https://sysin.org/blog/cisco-aci-16/ 查看最新…

[译] WinForms:分析一下(我用 Visual Basic 写的)

原文 | Klaus Loeffelmann 翻译 | 郑子铭 如果您从未看过电影《分析这一点》,下面是简短的介绍:假设一个纽约家族的成员有可疑的习惯,他决定认真考虑接受治疗以改善他的精神状态。在比利克里斯托和罗伯特德尼罗的推动下,剧情一定会很有趣。虽然《分析这一点!》讽刺性地处理…

Cisco APIC 6.0(8f)M - 应用策略基础设施控制器

Cisco APIC 6.0(8f)M - 应用策略基础设施控制器Cisco APIC 6.0(8f)M - 应用策略基础设施控制器 Application Policy Infrastructure Controller (APIC) 请访问原文链接:https://sysin.org/blog/cisco-apic-6/ 查看最新版。原创作品,转载请保留出处。 作者主页:sysin.org思科…