仅使用 PTRACE_SINGLESTEP 拦截系统调用

时间:2021-04-11 12:53:00

标签: c ptrace

我们有一个学校项目,需要重新编码“strace”。

我们只需要拦截像write和read这样的系统调用,但是我们不能使用PTRACE_SYSCALL。我正在寻找一种使用 PTRACE_SINGLESTEP 的方法,我已经编写了一种打印系统调用的方法,当我使用 PTRACE_SYSCALL 时它工作正常,但是当我使用 {{1 }} 我找不到只打印系统调用的方法。

这是我使用的代码,也许有人可以帮我弄清楚它有什么问题:

PTRACE_SINGLESTEP

1 个答案:

答案 0 :(得分:2)

如果您不能在系统调用之前/之后使用 PTRACE_SYSCALL 来停止孩子,那么您将不得不手动检测何时将要发生。我怀疑检查 strace 的源代码会有所帮助,因为 strace 很可能使用 PTRACE_SYSCALL,没有理由手动解码指令。

假设您在 x86-64 上工作,您可以这样做:

  1. 使用 PTRACE_SINGLESTEP,一次执行一条指令。
  2. 每条指令,使用PTRACE_PEEKTEXT获取指令指针指向的下一条指令。
  3. 通过将字节与opcode of syscall(两个字节:syscall)进行比较来检查指令是否为0x0f 0x05 指令。由于 x86 是小端,这意味着检查 ptrace(PTRACE_PEEKDATA, ...) 的返回值是否将两个最低有效字节设置为 0x050f

注意:如果您在其他架构上,或者如果您还想检测 32 位系统调用,您可以在步骤 3 中简单地检查不同/更多的值。在 Linux x86-64 上,有多种方法可以使用不同的操作码发出系统调用。例如,Linux 上的 32 位系统调用是通过 int 0x80(操作码 0xcd 0x80)完成的。检查 this other answer of mine 以获取列表。

这是一个例子:

#include <errno.h>

long opcode;

// ...

waitpid(child, &status, 0);

while (WIFSTOPPED(status)) {
    ptrace(PTRACE_GETREGS, child, NULL, &regs);

    errno = 0;
    opcode = ptrace(PTRACE_PEEKTEXT, child, regs.rip, 0);
    if (opcode == -1 && errno != 0) {
        perror("ptrace(PTRACE_PEEK_DATA) failed");
        exit(1);
    }

    if ((unsigned long)opcode & 0xffff == 0x050f) {
        // Child about to execute a syscall instruction,
        // check the registers to know more...
    }

    ptrace(PTRACE_SINGLESTEP, child, 0, 0);
    waitpid(child, &status, 0);
}
相关问题