如何在静态链接二进制文件中替换malloc调用?用户态热补丁咨询
静态链接二进制替换malloc的用户态热补丁方案
针对静态链接的C/C++二进制文件,无法用LD_PRELOAD劫持malloc的场景,用户态有多种类似内核ftrace的热补丁/动态插桩方案,无需修改源码或重新编译,直接在运行时或离线修改二进制实现函数替换:
1. 基于ptrace的手动动态插桩
这是最底层的用户态热补丁实现方式,核心是通过ptrace系统调用操控目标进程内存:
- 核心步骤:
- 解析目标二进制的ELF符号表,或通过调试信息/特征码扫描定位
malloc函数的内存地址。 - 用
ptrace附加到目标进程并暂停它,读取malloc入口处的几条指令并保存(用于后续恢复或构造trampoline跳转)。 - 将
malloc入口指令替换为跳转指令(如x86的jmp),指向你自定义的malloc实现——自定义代码可以通过注入共享库到进程内存,或直接写入进程的可执行内存区域。 - 恢复进程运行,此时所有
malloc调用都会被劫持到你的实现。
- 解析目标二进制的ELF符号表,或通过调试信息/特征码扫描定位
- 注意:需处理指令长度对齐问题,避免破坏原有指令逻辑;自定义
malloc必须完全匹配原函数的参数、返回值和调用约定,多线程场景下还要保证线程安全。
2. Frida动态插桩框架
Frida是一款开箱即用的动态插桩工具,底层基于ptrace和代码注入,对静态链接程序的函数替换支持友好:
- 示例脚本(JavaScript):
// 先通过二进制符号表或内存扫描获取malloc的地址 const mallocAddr = ptr("0x123456"); // 保存原malloc函数的引用 const originalMalloc = new NativeFunction(mallocAddr, 'pointer', ['size_t']); // 替换malloc调用 Interceptor.replace(mallocAddr, new NativeCallback(function(size) { // 自定义逻辑:比如记录分配大小 console.log(`malloc called with size: ${size}`); // 调用原malloc完成实际分配 return originalMalloc(size); }, 'pointer', ['size_t'])); - 执行方式:运行
frida -n target_binary -l replace_malloc.js即可自动完成进程注入和函数替换,无需手动处理ptrace细节。
3. 离线二进制重写
如果不需要运行时动态替换,也可以直接修改静态链接的二进制文件本身:
- 实现方式:用
objcopy、patch工具或自定义ELF解析程序,找到malloc的符号地址,修改其入口指令为跳转至自定义函数;或遍历所有调用malloc的指令位置,修改跳转目标。 - 注意:修改前需确保二进制文件可写,且修改后不会破坏ELF文件结构;若程序有代码签名,修改后可能无法正常运行。
4. Intel Pin动态二进制插桩
Intel Pin是一款面向性能分析和插桩的工具,支持在程序运行时拦截所有函数调用(包括静态链接函数):
- 示例Pin工具代码(C++):
#include "pin.H" #include <cstdio> // 自定义malloc实现 void* MyMalloc(size_t size) { printf("Custom malloc triggered, size: %zu\n", size); // 调用原malloc完成分配 return malloc(size); } // 拦截malloc的返回地址,替换为自定义函数的结果 VOID ReplaceMalloc(ADDRINT* retAddr, size_t size) { *retAddr = (ADDRINT)MyMalloc(size); } // 注册插桩逻辑 VOID InstrumentRoutine(RTN rtn, VOID* v) { if (RTN_Name(rtn) == "malloc") { RTN_ReplaceSignature(rtn, AFUNPTR(ReplaceMalloc), IARG_RETURN_IP, IARG_FUNCARG_ENTRYPOINT_VALUE, 0, IARG_END); } } int main(int argc, char* argv[]) { PIN_Init(argc, argv); RTN_AddInstrumentFunction(InstrumentRoutine, 0); PIN_StartProgram(); return 0; } - 执行方式:编译Pin工具后,运行
pin -t my_malloc_patch.so -- target_binary,Pin会自动完成指令插桩和函数替换。
核心注意事项
- 函数兼容性:自定义
malloc必须和原函数的参数、返回值、调用约定(如cdecl、fastcall)完全一致,否则会导致程序崩溃。 - 线程安全:如果目标程序是多线程的,自定义实现必须保证线程安全,比如添加互斥锁避免竞态条件。
- 权限:操作进程内存或修改二进制文件需要足够权限(如root),尤其是针对系统级进程。
内容的提问来源于stack exchange,提问作者Frontier_Setter
相关产品推荐
相关产品推荐

