微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

使用 ptrace 动态获取以字节为单位的指令大小

如何解决使用 ptrace 动态获取以字节为单位的指令大小

我正在使用 ptrace 来跟踪流程并监控其行为。在某些时候,我想在点击下一条指令之前获得下一个 rip 地址。事实上,我想在 callq 指令之后调用获取指令的地址。有几种不同的此类指令(near、far、relative、absolute 等),并且它们的长度并不相同。

一旦检索到指令,有没有办法使用 ptrace获取指令的字节大小。类似于以下内容

int ip = ptrace(PTRACE_PEEKUSER,t_pid,ipoffs,0);                    // some addr where ip points
long isntruction = ptrace(PTRACE_PEEKTEXT,ip,NULL);           // e8 ae 72 f8 ff (relative call)
printf("Instruction is %d bytes",get_instruction_size(instruction));  // Instrction is 5 bytes

我猜实现 get_instruction_size 的一种方法获取指令的操作码(前 1 或 2 个字节),然后根据 x86 架构/手册确定它应该有多长。但是我觉得会有很多特殊情况需要考虑,并且需要大量阅读以找到值+这将从一种 cpu 架构更改为另一种。另一方面,动态查找大小似乎更方便。我还没有找到答案。

------ 编辑-------

尝试在调用后立即从 rsp 检索返回值:

#define M_OFFSetoF(STRUCT,ELEMENT) \
        (unsigned long) &((STRUCT *)NULL)->ELEMENT;
...
ipoffs = M_OFFSetoF(struct user,regs.rip);
spoffs = M_OFFSetoF(struct user,regs.rsp);
...
while(1) {
    // exec one instruction
    if(ptrace(PTRACE_SINGLESTEP,signo) < 0){
        perror("ptrace single step error\n");
        exit(EXIT_FAILURE);
    }
    ip = ptrace(PTRACE_PEEKUSER,0);
    full_instruction = ptrace(PTRACE_PEEKTEXT,NULL);
    opcode = (unsigned)0xFF & full_instruction;
    if(opcode == ADDR32){
        opcode = ((unsigned)0xFF00 & full_instruction) >> 8;
    }
    if(call_found){
        sp = ptrace(PTRACE_PEEKUSER,spoffs,0);
        // print sp ...
        call_found = false;
    }
    if(opcode == CALL)
       call_found = true;
}

解决方法

ptrace 在内核中没有反汇编器1,硬件本身不会在 call 指令执行之后告诉你这一点。

如果您可以等到指令执行后,最好的办法可能是PTRACE_SINGLESTEP然后读取压入堆栈的返回地址call。 (ESP/RSP 会指向它2)。


另一个选项当然是自己解码(包括任何可能用于填充的前缀,例如 ld 将 6 字节 call [got_entry] 放松为 1+5 字节 addr32 call rel32 )。使用反汇编程序库,或通过扫描前缀直到获得 call 操作码之一,然后您可以从它(E8 call rel32)或从解码 ModRM 字节获得长度FF /2 call [r/m32]。 (https://www.felixcloutier.com/x86/call)。


如果您希望可移植到非 x86 ISA,许多使用链接寄存器而不是推送返回地址,因此它不相同;您不能只是一般地取消引用宽度为 uintptr_t 的堆栈指针。

而且许多 ISA 具有固定的指令宽度,因此您可以向前执行一条指令,而不是单步执行和读取寄存器。 (尽管许多支持使用 2 或 4 字节指令的紧凑编码,例如 ARM Thumb、MIPS 和 RISC-V)。

某些 ISA 上还有其他问题,例如 MIPS 有一个分支延迟槽,因此返回地址实际上是在 jal 之后的 next 指令之后。


脚注 1:(有趣的事实:ARM Linux 内核曾经有一个反汇编器,支持一些指令,因此它可以为您模拟单步,但是 that hack was removed)。

脚注 2:即使对于带有 far 调用的手写 asm,CS:[ER]IP 返回地址也会在最低地址处具有偏移量部分,即由 ESP/RSP 指向。当然,远调用使用不同的操作码,因此您可以单独对待它们或忽略它们。

我不确定前缀是否有可能以某种方式覆盖大小,从而使 call 推送不同大小的返回地址。 (例如 32 位模式下的 16 位)。可能不会,即使是这样,也只会担心恶意二进制文件试图故意欺骗您的跟踪器。由于任何正常原因,即使是为 GNU/Linux 手写的 asm 也几乎不可能做到这一点。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。