【发布时间】:2021-03-27 16:15:15
【问题描述】:
我正在使用ptrace 跟踪进程并监控其行为。在某些时候,我想在点击下一条指令之前获得下一个rip 地址。事实上,我想在callq 指令之后调用获取指令的地址。有几种不同的此类指令(近、远、相对、绝对等),它们的长度并不相同。
一旦检索到指令,有没有办法使用ptrace 来获取指令的字节大小。类似于以下内容:
int ip = ptrace(PTRACE_PEEKUSER, t_pid, ipoffs, 0); // some addr where ip points
long isntruction = ptrace(PTRACE_PEEKTEXT, t_pid, 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, t_pid, 0, signo) < 0){
perror("ptrace single step error\n");
exit(EXIT_FAILURE);
}
ip = ptrace(PTRACE_PEEKUSER, t_pid, ipoffs, 0);
full_instruction = ptrace(PTRACE_PEEKTEXT, t_pid, ip, NULL);
opcode = (unsigned)0xFF & full_instruction;
if(opcode == ADDR32){
opcode = ((unsigned)0xFF00 & full_instruction) >> 8;
}
if(call_found){
sp = ptrace(PTRACE_PEEKUSER, t_pid, spoffs, 0);
// print sp ...
call_found = false;
}
if(opcode == CALL)
call_found = true;
}
【问题讨论】:
-
但是,如果您想要移植到非 x86 ISA,许多使用链接寄存器而不是推送返回地址。 OTOH,其中许多具有固定的指令宽度。不过,还有其他问题,比如 MIPS 有一个分支延迟槽,因此返回地址实际上是在
jal之后的 next 指令之后。当然,这与您的标题问题相去甚远。找到通话说明后,您真正需要/想要做什么? -
lDebug 的 P 命令会进行一些有限的反汇编以计算出调用指令的大小。这仅适用于 16 位和 32 位模式,但可以扩展到 64 位。这是实现此反汇编的the handler in run.asm。这里是the ppbytes and ppinfo tables,用于调度大部分前缀和操作码。
-
@PeterCordes 正如我所料,这比我想要的要复杂得多。现在我只需要 x86-32 上的相对(近或远)调用的返回地址,但我想尽可能地通用。检索 SP 指向的地址听起来是最好的选择。
-
re: 更新:评论说“打印 sp”,但 RSP 指向返回地址。你需要在那个地址偷看。 (此外,您在编辑中写了“返回值”,而不是“地址”。)此外,您必须在 RIP 指向调用时再执行一次 SINGLESTEP,以使其实际运行调用并推送返回地址。看起来您只检查一个
call操作码。间接调用是FF /2,所以需要检查2个字节(屏蔽掉ModRM中的其他位)