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

Assembly 使用ptrace动态获取以字节为单位的指令大小,assembly,x86,ptrace,Assembly,X86,Ptrace,我使用ptrace跟踪流程并监控其行为。在某个时刻,我希望在执行下一条指令之前获得下一个rip地址。实际上,我想在callq指令之后调用get来获取指令的地址。有几种不同的指令(近指令、远指令、相对指令、绝对指令等),它们的长度并不相同 一旦检索到指令,是否有方法使用ptrace获取指令的字节大小。如下所示: int ip = ptrace(PTRACE_PEEKUSER, t_pid, ipoffs, 0); // some addr where ip p

我使用
ptrace
跟踪流程并监控其行为。在某个时刻,我希望在执行下一条指令之前获得下一个
rip
地址。实际上,我想在
callq
指令之后调用get来获取指令的地址。有几种不同的指令(近指令、远指令、相对指令、绝对指令等),它们的长度并不相同

一旦检索到指令,是否有方法使用
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_指令\u size
的一种方法是获取指令的操作码(前1或2个字节),然后根据x86体系结构/手册确定指令的长度。但是我觉得会有很多特殊情况需要考虑,并且会有很多阅读来寻找价值观,这将从一个CPU体系结构改变到另一个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;
}
#定义M_偏移量(结构、元素)\
(无符号长)和((STRUCT*)NULL)->元素;
...
ipoffs=M_OFFSETOF(结构用户,regs.rip);
spoffs=M_OFFSETOF(结构用户,regs.rsp);
...
而(1){
//执行一条指令
if(ptrace(ptrace_SINGLESTEP,t_pid,0,signo)<0){
perror(“ptrace单步错误”);
退出(退出失败);
}
ip=ptrace(ptrace_PEEKUSER,t_pid,ipoffs,0);
full_指令=ptrace(ptrace_文本,t_pid,ip,NULL);
操作码=(无符号)0xFF&full_指令;
if(操作码==ADDR32){
操作码=((无符号)0xFF00&full_指令)>>8;
}
如果(呼叫_-found){
sp=ptrace(ptrace_PEEKUSER,t_pid,spoffs,0);
//打印sp。。。
call_found=false;
}
if(操作码==调用)
call_found=true;
}

ptrace在内核1中没有反汇编程序,硬件本身在执行
调用
指令之前不会告诉您这一点

如果您可以等到指令执行后再执行,那么最好的办法可能是
PTRACE\u SINGLESTEP
然后读取推到堆栈上的返回地址
call
。(ESP/RSP将指向it2)


另一个选项当然是自己解码(包括可能用于填充的任何前缀,例如当
ld
将6字节
调用[got_entry]
松弛为1+5字节
addr32 call rel32
时)。无论是使用反汇编程序库,还是通过扫描前缀直到找到一个
调用
操作码,都可以从中获得长度(E8
调用rel32
),或者从解码间接FF/2
调用[r/m32]
的ModRM字节中获得长度。()


如果您希望可移植到非x86 ISA,许多人使用链接寄存器,而不是推送返回地址,因此它是不相同的;不能仅使用
uintptr\u t
width对堆栈指针进行一般性的去引用

许多ISA都有固定的指令宽度,所以你可以只向前执行一条指令,而不是单步读取寄存器。(尽管许多支持2字节或4字节指令的压缩编码,如ARM Thumb、MIPS和RISC-V)

在某些ISA上还有其他褶皱,例如MIPS有一个分支延迟槽,因此返回地址实际上位于
jal
之后的下一条指令之后


脚注1:(有趣的事实:ARM Linux内核过去有一个反汇编程序,支持一些指令,所以它可以为您模拟单步操作,但是)

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


我不确定前缀是否可以覆盖一个大小,使
call
推送一个不同大小的返回地址。(例如,32位模式下为16位)。可能不会,即使是这样,也只会让恶意二进制文件故意愚弄跟踪程序。即使是手写的GNU/Linux asm,出于任何正常原因也不太可能做到这一点。

ptrace内核1中没有反汇编程序,硬件本身在执行
调用
指令之前不会告诉您这一点

如果您可以等到指令执行后再执行,那么最好的办法可能是
PTRACE\u SINGLESTEP
然后读取推到堆栈上的返回地址
call
。(ESP/RSP将指向it2)


另一个选项当然是自己解码(包括可能用于填充的任何前缀,例如当
ld
将6字节
调用[got_entry]
松弛为1+5字节
addr32 call rel32
时)。无论是使用反汇编程序库,还是通过扫描前缀直到找到一个
调用
操作码,都可以从中获得长度(E8
调用rel32
),或者从解码间接FF/2
调用[r/m32]
的ModRM字节中获得长度。()


如果您希望可移植到非x86 ISA,许多人使用链接寄存器,而不是推送返回地址,因此它是不相同的;不能仅使用
uintptr\u t
width对堆栈指针进行一般性的去引用

许多ISA都有固定的指令宽度,所以你可以只向前执行一条指令,而不是单步读取寄存器。(尽管许多支持2字节或4字节指令的压缩编码,如ARM Thumb、MIPS和RISC-V)

一些ISA上还有其他皱纹,例如MIPS有一个分支延迟槽,因此返回地址实际上在ne之后