C/C++ inline-hook(x86)高级函数内联钩子

🧵 C/C++ inline-hook(x86)高级函数内联钩子

引用

  1. fetch-x86-64-asm-il-size
  2. C++ i386/AMD64平台汇编指令对齐长度获取实现

🧠 一、Inline Hook技术体系架构

Inline Hook是一种二进制指令劫持技术,通过修改目标函数的机器码,将执行流重定向到自定义函数。其核心价值在于无需源码即可监控或修改程序行为,广泛应用于调试器(如x64dbg)、安全软件(如杀毒引擎)和性能分析工具(如VTune)。

1.1 技术实现全流程

定位目标函数 修改内存权限 备份原始指令 写入跳转指令 构建跳板Trampoline 劫持执行流至Hook函数 通过Trampoline调用原函数

  • 关键步骤详解
    • 指令覆盖 :x86覆盖5字节(E9+4字节偏移),x64覆盖12-14字节(FF25+8字节绝对地址)

    • 偏移计算

      c 复制代码
      // x86示例:跳转偏移 = Hook函数地址 - (目标函数地址 + 5)
      DWORD offset = (DWORD)HookedFunc - (DWORD)TargetFunc - 5;
      BYTE jmp[5] = {0xE9, *(BYTE*)&offset}; 

⚙️ 二、跳板(Trampoline)机制深度解构

直接调用原函数会导致 ​​递归死循环 ​​(因原函数入口已被 JMP Hook 覆盖)。跳板通过 ​​分离指令备份与执行流恢复​​ 解决此问题。

2.1 跳板结构设计​

入口指令被覆盖 目标函数 JMP Hook函数 Hook函数 调用跳板 执行备份指令 JMP 原函数 + N

2.2 跳板结构与生成算法
c 复制代码
LPVOID CreateTrampoline(uint8_t* target, size_t len) {
    LPVOID tramp = VirtualAlloc(NULL, len+5, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    // 1. 复制原始指令
    memcpy(tramp, target, len); 
    // 2. 追加跳回指令(JMP回原函数+len)
    uint8_t* jmp_pos = (uint8_t*)tramp + len;
    *jmp_pos = 0xE9; 
    *(DWORD*)(jmp_pos+1) = (DWORD)(target + len) - (DWORD)(jmp_pos + 5);
    return tramp;
}
  • 指令级还原原理
    • 备份指令必须完整覆盖被破坏的原始指令(如x86的5字节)
    • 跳回地址需精确计算至目标函数+备份长度,避开被篡改区域
2.3 执行流恢复的线程安全挑战

当多线程并发调用被Hook函数时:

  1. 寄存器一致性:跳板执行时需保持所有寄存器状态与原函数入口一致
  2. 栈平衡机制 :x86通过push ebp; mov ebp, esp建立栈帧,跳板需模拟此过程
  3. 调用约定兼容 :确保stdcall/fastcall等约定不被破坏

🔒 三、多线程环境下的原子性与安全性保障

3.1 指令修改的竞态风险

当线程A正在写入跳转指令时,若线程B执行到该区域:

  • 撕裂读取:可能读取到半写入状态的无效指令(如仅写入3字节)
  • CPU缓存失效:旧指令残留在L1 Cache导致执行错误
3.2 工业级解决方案
方案 原理 优缺点
线程挂起 通过SuspendThread暂停所有线程,确保无并发执行 安全但导致进程卡顿
原子写入 使用InterlockedExchange64单指令完成8字节写入 仅限x64,且需指令长度对齐
热补丁(Hot Patch) 利用函数头部的MOV EDI,EDI(2字节)构造短跳转,避免覆盖执行中的指令 需编译器支持(/hotpatch)
c 复制代码
// 热补丁实现示例(覆盖7字节)
void HotPatchHook() {
    // 1. 在函数头部上方5字节处写入长跳转(E9 xxxxxxxx)
    WriteJump((PVOID)((DWORD)TargetFunc - 5), HookFunc); 
    // 2. 覆盖头2字节为短跳转(EB F9)
    BYTE shortJump[2] = {0xEB, 0xF9}; 
    WriteMemory(TargetFunc, shortJump, 2);
}

🧩 四、跨平台实现差异与技术挑战

4.1 架构差异与应对策略
问题 x86方案 x64方案 ARM方案
跳转范围 ±2GB(近跳转) 全64位地址(远跳转) ±32MB(B指令)
指令长度 5字节(JMP rel32) 14字节(MOVABS + JMP) 4-8字节(LDR+BR)
寄存器保护 依赖栈保存 需手动保存XMM0-XMM5 保护AAPCS定义的易失寄存器
c 复制代码
// x64远跳转实现(14字节)
void WriteX64Jump(PVOID target, PVOID hook) {
    BYTE code[14] = {
        0xFF, 0x25, 0x00, 0x00, 0x00, 0x00, // FF25 00000000: JMP [RIP+0]
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00  // 绝对地址
    };
    *(ULONG_PTR*)(code + 6) = (ULONG_PTR)hook;
    WriteMemory(target, code, 14);
}
4.2 变长指令处理的可靠性设计
  • 问题本质 :x86指令长度不定(1-15字节),覆盖5字节可能截断指令

    assembly 复制代码
    ; 危险案例:覆盖5字节破坏完整指令
    MOV [EAX+ECX*4], 12345678h ; 完整指令占10字节
  • 解决方案

    1. 反汇编引擎:使用Zydis/Capstone动态计算最小完整指令边界
    2. 跳板扩展:备份跨越指令所需全部字节,追加修复逻辑

🛠️ 五、生产环境最佳实践与演进方向

5.1 现代安全机制的规避策略
安全机制 影响 破解方案
DEP 阻止数据区执行跳板 申请PAGE_EXECUTE_READWRITE权限
ASLR 函数地址随机化 动态解析API地址(GetProcAddress)
PatchGuard Windows内核代码签名校验 挂钩非校验区域(如KiFilterFiberContext)
5.2 性能优化与稳定增强
  1. 跳板池复用:预生成常用函数跳板,减少运行时分配开销
  2. 延迟挂钩:首次调用时再安装Hook,避免启动卡顿
  3. 栈帧探测:通过RBP链校验调用路径,防止递归崩溃

🚀 六、内联钩子及跳板的实现

1.1 演示效果
1.2 工程实现
cpp 复制代码
#include <windows.h>
#include <cstdint>
#include <cstring>

#ifdef _WIN64
#include <intrin.h>
#pragma intrinsic(_mm_sfence)
#endif

// 函数指针类型定义
using message_box_ptr = int(WINAPI*)(HWND, LPCSTR, LPCSTR, UINT);

// 全局变量
static message_box_ptr original_message_box = NULL;
static LPVOID trampoline_shellcode = NULL;
static size_t backup_length = 0;

// 内存屏障
void memory_barrier() {
#ifdef _WIN64
    _mm_sfence();
#endif
    _ReadWriteBarrier();
}

// 计算备份长度 (固定长度简化版)
size_t calculate_backup_length() {
    return 5;  // x86需要5字节覆盖
}

// 创建跳板shellcode
LPVOID create_trampoline(uint8_t* target, size_t length) {
    // 计算跳回地址
    uintptr_t return_address = reinterpret_cast<uintptr_t>(target) + length;

    // 分配可执行内存
    LPVOID exec_mem = VirtualAlloc(NULL, length + 5,
        MEM_COMMIT | MEM_RESERVE,
        PAGE_EXECUTE_READWRITE);
    if (!exec_mem) return NULL;

    uint8_t* shellcode_ptr = static_cast<uint8_t*>(exec_mem);

    // 1. 复制原始指令
    memcpy(shellcode_ptr, target, length);

    // 2. 添加跳回指令

    // x86: JMP rel32
    shellcode_ptr += length;
    *shellcode_ptr++ = 0xE9; // JMP
    DWORD jmp_offset = (DWORD)return_address - (DWORD)shellcode_ptr;
    *reinterpret_cast<DWORD*>(shellcode_ptr) = jmp_offset;

    // 刷新内存
    memory_barrier();
    FlushInstructionCache(GetCurrentProcess(), exec_mem, length + 5);

    return exec_mem;
}

// Hook 函数实现
int WINAPI hooked_message_box(HWND hwnd, LPCSTR lp_text, LPCSTR lp_caption, UINT u_type) {
    char hooked_text[256] = { 0 };
    const char* prefix = "[HOOKED] ";

    // 安全组合新消息
    strcpy_s(hooked_text, sizeof(hooked_text), prefix);

    if (lp_text) {
        // 防止缓冲区溢出
        size_t prefix_len = strlen(prefix);
        size_t max_copy = sizeof(hooked_text) - prefix_len - 1;
        strncat_s(hooked_text, sizeof(hooked_text), lp_text, max_copy);
    }

    // 调用原始功能
    using trampoline_func = int(WINAPI*)(HWND, LPCSTR, LPCSTR, UINT);
    trampoline_func trampoline = reinterpret_cast<trampoline_func>(trampoline_shellcode);

    // 调试输出
    OutputDebugStringA("Hooked function called");
    OutputDebugStringA(hooked_text);

    return trampoline(hwnd, hooked_text, lp_caption ? lp_caption : "Hooked MessageBox", u_type);
}

// 安装 Hook
bool install_hook() {
    // 1. 使用自定义函数作为源
    original_message_box = &MessageBoxA;

    // 2. 计算备份长度
    backup_length = calculate_backup_length();

    // 3. 创建跳板 Shellcode
    trampoline_shellcode = create_trampoline(
        reinterpret_cast<uint8_t*>(original_message_box), backup_length);

    if (!trampoline_shellcode) {
        OutputDebugStringA("Failed to create trampoline shellcode");
        return false;
    }

    // 4. 构造跳转指令到 Hook 函数
    uint8_t jump_code[16] = { 0 };
    uintptr_t hook_address = reinterpret_cast<uintptr_t>(&hooked_message_box);
    size_t jump_size = 0;

    // x86: JMP rel32 (5字节)
    jump_code[0] = 0xE9; // JMP
    DWORD jmp_offset = static_cast<DWORD>(hook_address) -
        (reinterpret_cast<DWORD>(original_message_box) + 5);
    *reinterpret_cast<DWORD*>(jump_code + 1) = jmp_offset;

    jump_size = 5;

    // 5. 写入跳转指令
    DWORD old_protect;
    if (!VirtualProtect(original_message_box, jump_size, PAGE_EXECUTE_READWRITE, &old_protect)) {
        OutputDebugStringA("VirtualProtect failed");
        return false;
    }

    // 使用内存屏障保证顺序
    memory_barrier();

    // 写入跳转代码
    memcpy(original_message_box, jump_code, jump_size);

    memory_barrier();
    FlushInstructionCache(GetCurrentProcess(), original_message_box, jump_size);

    DWORD temp;
    VirtualProtect(original_message_box, jump_size, old_protect, &temp);

    return true;
}

// 示例用法
int main() {
    // 安装Hook前测试
    MessageBoxA(NULL, "Pre-Hook Test", "Original", MB_OK);

    // 安装Hook
    if (!install_hook()) {
        MessageBoxA(NULL, "Hook installation failed", "Error", MB_OK);
        return 1;
    }

    // 使用Hook
    MessageBoxA(NULL, "Hello World", "Test", MB_OK);

    return 0;
}

💎 结论:跳板钩子的技术本质与价值

跳板(Trampoline)是Inline Hook的安全执行引擎,通过三阶协作实现无损劫持:

  1. 劫持层 :通过JMP指令重定向执行流(原子化写入保障线程安全)
  2. 过滤层:Hook函数实现参数过滤/日志记录(上下文一致性是关键)
  3. 还原层:跳板执行备份指令并跳回原函数(精确计算跳回地址)

在多线程场景下,需结合热补丁机制指令缓存刷新_mm_sfence() + FlushInstructionCache)确保原子可见性。

相关推荐
开开心心_Every13 分钟前
便捷的电脑自动关机辅助工具
开发语言·人工智能·pdf·c#·电脑·音视频·sublime text
霖001 小时前
C++学习笔记三
运维·开发语言·c++·笔记·学习·fpga开发
mit6.8242 小时前
[shad-PS4] Vulkan渲染器 | 着色器_重新编译器 | SPIR-V 格式
c++·游戏引擎·ps4
上单带刀不带妹2 小时前
JavaScript中的Request详解:掌握Fetch API与XMLHttpRequest
开发语言·前端·javascript·ecmascript
小白学大数据2 小时前
Python爬取闲鱼价格趋势并可视化分析
开发语言·python
秋说2 小时前
【PTA数据结构 | C语言版】线性表循环右移
c语言·数据结构·算法
ningmengjing_2 小时前
在 PyCharm 中安装并配置 Node.js 的指南
开发语言·javascript·ecmascript
晓13132 小时前
JavaScript基础篇——第五章 对象(最终篇)
开发语言·前端·javascript
tan77º2 小时前
【Linux网络编程】Socket - TCP
linux·网络·c++·tcp/ip
代码改变世界ctw3 小时前
ARM汇编编程(AArch64架构)课程 - 第5章函数调用规范
汇编·arm开发·架构