You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

x86架构下如何Hook运行时未知数量的函数

问题

运行时获取同一进程内的一组函数地址,需要在这些函数被调用时记录对应的函数地址。

已尝试方案的局限

单个函数Hook可以用subhook这类库实现,伪代码如下:

create_hook(function_to_be_hooked, intermediate);

intermediate(args...) {
  log("function with address %p got called", &function_to_be_hooked);
  remove_hook(function_to_be_hooked);
  ret = function_to_be_hooked(args...);
  create_hook(function_to_be_hooked, intermediate);
  return ret;
}

但这个方案无法直接扩展到运行时数量未知的多函数场景:如果用同一个中间函数Hook多个目标函数,无法区分到底是哪个函数被触发调用。当前环境为Linux下的C/C++,可选Hook库为subhook和funchook,二者均不支持直接关联Hook上下文。

解决方案

1. 为每个目标函数生成独立中间包装函数

通过C/C++宏为每个目标函数创建专属的中间函数,每个中间函数硬编码对应的目标函数地址,调用时直接记录该地址。

示例代码(基于subhook):

#include <subhook.h>
#include <stdio.h>

// 宏定义:为指定函数地址生成专属Hook逻辑
#define CREATE_TARGET_HOOK(func_addr) \
static void* intermediate_##func_addr(void* args) { \
  printf("Called function at address: %p\n", (void*)func_addr); \
  subhook_remove((subhook_t)hook_handle_##func_addr); \
  void* ret = ((void*(*)(void*))func_addr)(args); \
  subhook_install((subhook_t)hook_handle_##func_addr); \
  return ret; \
} \
subhook_t hook_handle_##func_addr = subhook_create((void*)func_addr, intermediate_##func_addr, 0);

// 编译时已知的目标函数地址示例
void* known_target_funcs[] = {0x12345678, 0x87654321};

int main() {
  // 为每个编译时已知的函数创建Hook
  CREATE_TARGET_HOOK(0x12345678);
  CREATE_TARGET_HOOK(0x87654321);
  
  subhook_install(hook_handle_0x12345678);
  subhook_install(hook_handle_0x87654321);
  
  // 后续逻辑...
  
  return 0;
}

局限:仅适用于编译时已知目标函数地址的场景,运行时动态获取的地址无法用此方法。

2. 汇编跳转桩+上下文存储(适配动态地址)

对于运行时动态获取的函数地址,需要手动构造跳转桩,把目标函数地址作为上下文传递给统一的中间处理函数:

  1. 为每个目标函数分配可执行内存作为跳转桩,在跳转桩中把目标函数地址存入线程局部存储(避免多线程冲突)。
  2. 跳转桩跳转到统一的中间处理函数,处理函数从线程局部存储中取出目标地址记录日志,再调用原始函数并恢复Hook。

示例代码(Linux x86_64平台):

#include <subhook.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <stdint.h>

// 线程局部存储:保存当前触发Hook的目标函数地址
__thread void* current_target = NULL;
// 线程局部存储:保存当前Hook的句柄,用于临时移除Hook
__thread subhook_t current_hook_handle = NULL;

// 统一的Hook处理函数
void* hook_handler(void* args) {
  printf("Called function at address: %p\n", current_target);
  
  // 临时移除Hook,调用原始函数
  subhook_remove(current_hook_handle);
  void* ret = ((void*(*)(void*))current_target)(args);
  subhook_install(current_hook_handle);
  
  return ret;
}

// 为目标函数创建跳转桩并关联Hook
void* setup_dynamic_hook(void* target_func, subhook_t* out_hook_handle) {
  // 分配可执行内存页
  void* trampoline = mmap(NULL, 4096, PROT_READ | PROT_WRITE | PROT_EXEC, 
                          MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (trampoline == MAP_FAILED) return NULL;

  // x86_64汇编逻辑:将target_func存入current_target,然后跳转到hook_handler
  unsigned char* asm_ptr = (unsigned char*)trampoline;
  
  // mov QWORD PTR [rip + offset], target_func
  *asm_ptr++ = 0x48; *asm_ptr++ = 0xC7; *asm_ptr++ = 0x05;
  *(uintptr_t*)asm_ptr = (uintptr_t)&current_target - (uintptr_t)(asm_ptr + 4);
  asm_ptr += 4;
  *(uintptr_t*)asm_ptr = (uintptr_t)target_func;
  asm_ptr += 8;
  
  // mov QWORD PTR [rip + offset], hook_handle
  *asm_ptr++ = 0x48; *asm_ptr++ = 0xC7; *asm_ptr++ = 0x05;
  *(uintptr_t*)asm_ptr = (uintptr_t)&current_hook_handle - (uintptr_t)(asm_ptr + 4);
  asm_ptr += 4;
  *(uintptr_t*)asm_ptr = (uintptr_t)*out_hook_handle;
  asm_ptr += 8;
  
  // jmp hook_handler
  *asm_ptr++ = 0xE9;
  *(uintptr_t*)asm_ptr = (uintptr_t)hook_handler - (uintptr_t)(asm_ptr + 4);
  asm_ptr += 4;

  // 创建Hook:将目标函数重定向到跳转桩
  *out_hook_handle = subhook_create(target_func, trampoline, 0);
  return trampoline;
}

int main() {
  // 运行时动态获取的函数地址示例
  void* dynamic_targets[] = {0x11223344, 0x55667788};
  subhook_t hook_handles[2];
  void* trampolines[2];

  // 为每个动态地址设置Hook
  for (size_t i = 0; i < 2; i++) {
    trampolines[i] = setup_dynamic_hook(dynamic_targets[i], &hook_handles[i]);
    if (trampolines[i]) {
      subhook_install(hook_handles[i]);
    }
  }

  // 后续逻辑...

  // 清理资源
  for (size_t i = 0; i < 2; i++) {
    if (hook_handles[i]) subhook_remove(hook_handles[i]);
    if (trampolines[i]) munmap(trampolines[i], 4096);
  }

  return 0;
}

注意事项:

  • 汇编代码需适配目标平台(x86_64与x86差异较大)。
  • 必须确保跳转桩内存页拥有可执行权限(通过mmap的PROT_EXEC参数设置)。
  • 多线程场景下必须用线程局部存储(__thread)保存上下文,避免不同线程的Hook上下文混乱。

3. funchook适配方案

funchook的回调签名固定,但可以用线程局部存储传递上下文,逻辑和方法2一致:为每个目标函数创建跳转桩,传递目标地址到线程局部变量,再进入统一处理函数。

总结

  • 编译时已知函数地址:用宏生成独立中间函数,实现简单且高效。
  • 运行时动态获取地址:通过汇编跳转桩+线程局部存储传递上下文,实现多Hook的区分。

内容的提问来源于stack exchange,提问作者Phastasm

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 14:40:51