You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Ptrace注入SO库时dlopen频繁返回垃圾值导致注入失败的原因及可靠方案咨询

Ptrace注入SO库时dlopen频繁返回垃圾值导致注入失败的原因及可靠方案咨询

嗨,我仔细看了你的问题和代码,这种ptrace注入不稳定的情况我之前也碰到过,咱们一步步拆解原因和解决办法:

一、频繁失败的核心原因

你的注入逻辑框架是对的,但几个细节没处理好,导致dlopen执行时上下文异常,返回垃圾值而非预期的错误码或有效句柄:

1. 注入点选择不安全

你直接在目标进程当前RIP位置打int3断点并劫持执行,这个位置很可能处于非安全上下文:比如目标进程正在执行系统调用的中间、栈处于未对齐状态,或者当前指令依赖某些寄存器的特定值。这种情况下强行切换到dlopen执行,会导致dlopen内部因为上下文错乱而崩溃,返回的rax是崩溃时的随机值(比如0xdb),而非正常的错误码。

2. 栈对齐破坏导致dlopen执行异常

x86-64架构要求函数调用前栈必须是16字节对齐(call指令会压入8字节的返回地址,进入函数后rsp会保持8字节偏移,满足指令对栈对齐的要求)。你的逻辑直接push假返回地址到栈上,没有检查当前rsp的对齐状态,一旦栈未对齐,dlopen内部的指令(比如SSE指令)会执行出错,进而返回垃圾值。

3. 断点处理的RIP偏移错误

int3是单字节指令,当目标进程命中int3断点时,内核会自动将RIP设置为原RIP + 1(跳过int3指令)。如果你直接把假返回地址设为原RIP(断点位置),当dlopen返回后,进程会从int3的下一个字节开始执行,而不是原指令位置,这会导致指令流错乱,甚至触发新的崩溃。

4. 被调用者保存寄存器未保护

x86-64的调用约定中,RBX、RBP、R12-R15属于被调用者保存寄存器,dlopen会修改这些寄存器的值。你的代码只保存了user_regs_struct但后续恢复时,没有确保这些寄存器回到原始状态,可能导致dlopen执行时因为寄存器状态异常而出现不可预期的行为。

二、可靠的注入方案调整建议

针对上述问题,你可以调整以下几个关键环节,让注入逻辑稳定可靠:

1. 选择安全的注入点

不要在任意RIP位置注入,优先选择两种安全场景:

  • 拦截系统调用:使用PTRACE_SYSCALL跟踪目标进程,拦截某个频繁触发的系统调用(比如writegetpid),当系统调用返回用户态时,此时目标进程的上下文是完全干净的(栈对齐、寄存器状态符合调用约定),适合执行dlopen调用。
  • 目标进程主动暂停:如果可以修改目标程序,让它在某个循环中主动调用pause()或者sleep(),此时进程处于用户态的安全暂停点,注入时上下文稳定。

2. 严格保证栈对齐

在设置dlopen的调用上下文前,检查当前RSP的对齐状态:

// 确保RSP是16字节对齐,符合x86-64调用约定
if ((regs.rsp % 16) != 0) {
    regs.rsp -= 8;
}

然后再push假返回地址到栈上,确保dlopen执行时栈环境符合架构要求。

3. 正确处理断点与RIP偏移

  • 打int3断点时,先保存原指令的1字节数据(因为int3是单字节指令)。
  • 当目标进程命中断点后,先恢复原指令,然后将RIP调整为原RIP(而非原RIP+1),再让进程继续执行。
  • 假返回地址应该设置为恢复后的原指令位置,而非断点位置。

4. 保护并恢复所有被调用者保存寄存器

在修改寄存器调用dlopen前,保存RBX、RBP、R12-R15的值,调用完成后恢复这些寄存器:

// 保存被调用者保存寄存器
struct user_regs_struct original_regs = regs;
long rbx = regs.rbx;
long rbp = regs.rbp;
long r12 = regs.r12;
long r13 = regs.r13;
long r14 = regs.r14;
long r15 = regs.r15;

// 设置dlopen调用的寄存器
regs.rip = (unsigned long)dlopen_addr;
regs.rdi = (unsigned long)so_path_addr;
regs.rsi = RTLD_NOW | RTLD_GLOBAL;
regs.rsp -= 8; // 预留返回地址的位置
ptrace(PTRACE_SETREGS, target_pid, NULL, &regs);

// 写入假返回地址到栈
ptrace_write(target_pid, (void*)regs.rsp, &original_regs.rip, sizeof(long));

// 执行dlopen...

// 恢复被调用者保存寄存器
regs.rbx = rbx;
regs.rbp = rbp;
regs.r12 = r12;
regs.r13 = r13;
regs.r14 = r14;
regs.r15 = r15;
ptrace(PTRACE_SETREGS, target_pid, NULL, &regs);

5. 确保SO路径的正确性

  • 写入目标栈的SO路径必须以\0结尾,否则dlopen会读取栈中的垃圾数据,导致加载失败。
  • 选择栈中足够安全的位置写入路径:比如当前RSP向下偏移0x200的位置,避免覆盖目标进程正在使用的栈数据。

三、额外的调试建议

你可以在dlopen调用后,读取目标进程的errno值(通过读取目标进程__errno_location()返回的地址对应的内存值),来判断dlopen失败的具体原因,这有助于进一步排查问题。

内容来源于stack exchange

火山引擎 最新活动