
substitute 运行时 Hook 库核心原理深度解析transform-dis 如何改写机器码生成 trampoline【免费下载链接】substituteA free runtime modification library.项目地址: https://gitcode.com/gh_mirrors/su/substitutesubstitute 是一款免费的运行时修改runtime hook库其核心模块 transform-dis 负责逐条反汇编目标函数、改写机器码并自动生成 trampoline跳板码让你既能拦截函数又能安全地调用原实现。本文将用尽量少的代码带你完整看懂这条改写流水线是如何工作的。一、substitute 是什么能做什么substitute 是一个面向 DarwinmacOS / iOS的开源函数 Hook 库对外提供三类能力函数头改写substitute_hook_functions直接修改目标函数开头的机器码把入口重定向到你的替换函数导入表替换substitute_interpose_imports修改 GOT/PLT 条目无需写可执行代码即可拦截导出函数Objective-C 方法替换substitute_hook_objc_message基于class_replaceMethod的线程安全方法 Hook。本文聚焦最核心、也最烧脑的第一种函数头改写inline hook。二、为什么必须改写机器码、又必须生成 trampolineHook 一个函数的最直接做法是把它开头的若干字节改成一条跳转到你的替换函数的跳转指令。但这带来两个难题被覆盖的指令去哪了函数开头被覆盖的那些原始指令仍然需要可执行——比如你通过old_ptr拿到的原函数指针或者被 Hook 的函数内部逻辑。substitute 的解法是把这些指令原样或改写后复制到一块新的可执行内存中拼成所谓的outro trampoline尾跳板尾跳板 被覆盖指令的改写副本 一条跳回原函数剩余部分的跳转。跳转距离可能不够。ARM 的条件分支只有 ±128MB 左右范围x86 的相对跳转更短。如果替换函数离得太远substitute 会先分配一块靠近目标的内存写入长跳转make_jump_patch函数入口先跳到这个intro trampoline头跳板再由它跳到最终目标。因此一次完整的 Hook 头跳板可选 函数入口短跳转 尾跳板改写指令 回跳其中尾跳板正是 transform-dis 的产物。三、整体流水线从 API 调用到落盘核心调度逻辑在lib/hook-functions.c的substitute_hook_functions中整体分为两个阶段阶段 1预检与生成不碰原代码计算需要的覆盖字节数check_intro_trampoline根据替换函数距离和架构ARM 8 或 10 字节ARM64 8/12/16 字节见lib/arm/jump-patch.h、lib/arm64/jump-patch.h决定 patch 区域大小生成 intro trampoline若需要远跳转先写入头跳板生成函数入口的跳转补丁make_jump_patch调用transform_dis_main生成 outro trampoline——本文主角见下节调用jump_dis_main做安全性启发式扫描检查函数后文是否有跳回 patch 区域的跳转见第五节。阶段 2原子提交把跳板内存页密封seal禁止继续修改暂停其他线程把跳转补丁写入原函数入口同时校正正好停在被覆盖区域内的线程 PCexecmem_foreign_write_with_pc_patch。这种先全部预演、再一次性提交的设计保证任何一步失败都不会破坏目标函数。四、transform-dis机器码改写的核心4.1 通用骨架一条指令一条指令地走入口是lib/transform-dis.c中的transform_dis_main它的工作循环非常朴素从 patch 区域起点开始逐条反汇编指令判断指令类型分两种处理普通指令原样复制到跳板区含 PC 相关地址的指令复制到跳板后地址就错了必须重新编码每复制一条就在offset_by_pcdiff偏移表中记录原始偏移 → 跳板内偏移的映射供后续修正被覆盖区域内的 PC 修正逻辑使用遇到错误如 patch 区域中间出现ret立即返回对应错误码。架构相关的怎么解码、怎么改写则由lib/arch/arch-transform-dis.inc.h与lib/arch/dis-main.inc.h提供通过回调函数接入通用骨架。ARM 的反汇编还会按最低位自动分发到 Thumb / Thumb-2 / ARM 三种模式见lib/arm/dis-main.inc.h。4.2 三类必须改写的指令以 ARM 为例lib/arm/arch-transform-dis.inc.htransform-dis 需要处理三类 PC 相关指令指令类型例子改写策略数据运算中使用 PC 作为操作数adr r3, X、ldr pc, [pc, r0, lsl #2]分析寄存器读写关系分 4 种情况处理直接重编码、借用空闲寄存器、或借用栈保存临时值PUSH/POP MOVW/MOVTPC 相对寻址adr、字面量池ldr、PC 相对跳转把相对地址换算为绝对地址改用MOVW/MOVT载入必要时重写加载模式分支/调用b、bl、Thumbcbz等条件分支先补一条反向条件跳转保持语义再用MOVW/MOVTBLX r14实现远跳转其中 ThumbIT 块指令谓词条件处理得尤为精细transform_dis_pre_dis/transform_dis_post_dis会把一个 IT 块拆解为每条指令各自一条条件跳转的形式因此force_keep_transforming标志会让主循环强制继续消费整个 IT 块避免在块中间截断。此外还有一个TRANSFORM_DIS_BAN_CALLS选项线程安全模式下默认开启如果 patch 区域中间出现函数调用就放弃 Hook返回SUBSTITUTE_ERR_FUNC_CALLS_AT_START。原因是调用产生的返回地址可能正停留在其他线程的栈上落在被覆盖区域里就无法安全修正了。4.3 trampoline 的拼装细节回到hook-functions.ctransform_dis_main返回后pc_patch_end会被向后扩展到完整的指令边界甚至整个 IT 块确保覆盖区域不会咬断一条指令在跳板尾部再写入一条跳回pc_patch_end的make_jump_patchoutro trampoline 就此完成hook-old_ptr指向的就是这个尾跳板入口——调用原函数时实际执行的是改写副本 原函数剩余部分的无缝拼接。而offset_by_pcdiff偏移表则负责最后一块拼图原函数中凡是引用被覆盖区域内部位置的 PC 相对指令都可以按表把地址修正到跳板中的对应副本位置。五、jump-dis动手之前的安全检查生成跳板后lib/jump-dis.c的jump_dis_main还会做一次启发式跳转分析从函数开头向后再走最多 512 条指令追踪所有分支目标一旦发现某条指令会跳回 patch 区域内部SUBSTITUTE_ERR_FUNC_JUMPS_TO_START就放弃本次 Hook。这个检查是尽力而为的——它不处理跳转表遇到ret之后的代码也不再深入——但足以挡住绝大多数会把程序执行流带进已被覆盖区域、从而崩溃的情况。六、常见错误码速查substitute_hook_functions的失败原因一目了然定义在lib/substitute.h错误码含义SUBSTITUTE_ERR_FUNC_TOO_SHORTpatch 区域中间就出现了ret函数太短SUBSTITUTE_ERR_FUNC_BAD_INSN_AT_START开头的指令属于暂不支持的特殊情况如跳回本区域SUBSTITUTE_ERR_FUNC_CALLS_AT_START线程安全模式下开头存在调用指令SUBSTITUTE_ERR_FUNC_JUMPS_TO_STARTjump-dis 发现函数后文跳回被覆盖区域SUBSTITUTE_ERR_OUT_OF_RANGE目标过远且拿不到足够近的跳板内存SUBSTITUTE_ERR_VMmmap / mprotect / vm_remap 失败如内核禁止页面可执行想在自己的环境验证这些行为test/test-transform-dis.c、test/test-jump-dis.c、test/test-pc-patch.c以及各架构的test/transform-dis-cases-*.S用例是很好的起点。七、关键源码导读路径对外 API 与文档注释lib/substitute.hHook 主流程预检 → 生成 → 原子提交lib/hook-functions.c机器码改写核心循环lib/transform-dis.c接口在lib/transform-dis.h通用反汇编分发层lib/dis.h、lib/darwin/各架构dis-main.inc.hARM 的 PC 相关指令改写策略lib/arm/arch-transform-dis.inc.h各架构跳转补丁决定 patch 大小lib/arm/jump-patch.h、lib/arm64/jump-patch.h、lib/x86/jump-patch.h跳转回溯安全检查lib/jump-dis.c内存写入与线程 PC 修正lib/darwin/execmem.c八、总结substitute 的巧妙之处在于把改机器码这件高风险的事拆成了清晰的四层通用反汇编分发层保证多架构可移植transform-dis 改写层逐条修正 PC 相关指令并顺带产出 trampolinejump-dis 检查层用启发式分析拦截危险目标execmem 提交层用暂停线程 PC 校正保证原子性。理解了这条流水线你就理解了绝大多数 inline hook 库的通用设计也为读懂各类动态插桩工具打下了坚实基础。【免费下载链接】substituteA free runtime modification library.项目地址: https://gitcode.com/gh_mirrors/su/substitute创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考