不能直接在原函数地址执行备份指令,因入口被改写为跳转后内存不可执行或RIP变化致相对寻址错误,且硬截字节易跨指令边界导致非法执行。

为什么不能直接在原函数地址上执行备份指令
因为原函数入口被你改写成跳转指令后,那块内存已经不是可执行状态,或者即使可执行,执行备份指令时的 RIP 值和原始上下文不一致,会导致相对寻址类指令(如 call、jmp rel32、lea rax, [rip + offset])计算错误。更关键的是:原函数前几字节可能跨指令边界——比如你硬截 5 字节,结果切在一条 mov rax, [rcx + 0x12345678] 的中间,执行会非法。
Trampoline 内存申请与权限设置必须手动控制
跳板内存不能用 malloc 或栈分配,必须调用 VirtualAlloc(Windows)或 mmap(Linux)申请 PAGE_EXECUTE_READWRITE 权限页。常见错误包括:
- 用
new分配内存后只调VirtualProtect改权限,但该内存所在页原本不可执行,VirtualProtect无法提升页级执行权限 - 申请时没指定
MEM_COMMIT | MEM_RESERVE,导致后续写入失败 - 申请后未用
FlushInstructionCache(Windows)或__builtin___clear_cache(GCC/Clang),CPU 指令缓存未同步,执行旧指令或崩溃
搬移指令必须做 RIP 相对修复
x64 下大量指令依赖 RIP 相对寻址,比如:
call 0x1234 ; 编码为 E8 xx xx xx xx,目标 = RIP + 5 + sign_extend(dword) lea rax, [rip + 0x5678] ; 目标地址 = RIP + 7 + 0x5678
这些指令搬到 trampoline 后,RIP 基址变了,必须重算偏移。实操要点:
立即学习“C++免费学习笔记(深入)”;
- 不能只 memcpy 前 N 字节;必须用指令解析器(如
udis86或手写简易解析)逐条识别并修正 - 遇到
call/jmp rel32,把原目标地址还原成绝对地址,再按新RIP重算 rel32 值 - 遇到
lea、mov等含[rip + imm32]的,同样需还原绝对地址再重算 imm32 - 若指令本身含跨页跳转或无法安全修复(如某些 AVX 指令边界模糊),应扩大搬移长度,直到能完整容纳一条指令
Trampoline 尾部跳转必须精确跳回原函数剩余部分
trampoline 执行完搬移指令后,必须无损跳转到原函数被覆盖区域之后的第一条指令。这个地址是:original_func_addr + copied_bytes。容易出错的地方:
- 误用
original_func_addr + 5硬编码,忽略实际搬移字节数(x64 下可能需 6~12 字节) - 没检查原函数该位置是否为合法指令起始点(比如跳到了某条指令中间)
- 尾部跳转用了
jmp rel32,但目标超出 ±2GB,应改用mov rax, imm64; jmp rax - 没保存/恢复所有被 trampoline 修改的寄存器(尤其是
rax、rcx等调用者易失寄存器),导致原函数逻辑错乱


















