如何解决使用 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 举报,一经查实,本站将立刻删除。