【问题标题】:Intercept only syscall with PTRACE_SINGLESTEP仅使用 PTRACE_SINGLESTEP 拦截系统调用
【发布时间】:2021-07-06 18:09:44
【问题描述】:

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

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

这是我使用的代码,也许有人可以帮我找出问题所在:

pid_t child;
long orig_eax;
user_regs_struct regs;

child = fork();
if (child == 0) {
    ptrace(PTRACE_TRACEME, 0, 0, 0);
    execve("/home/architek/a.out", {"/home/architek/a.out", NULL}, envp);
} else {
    waitpid(child, &status, 0);
    while (WIFSTOPPED(status)) {
        orig_eax = ptrace(PTRACE_PEEKUSER, child, 8 * ORIG_RAX, NULL);
        ptrace(PTRACE_GETREGS, child, NULL, &regs);
        call_printer(&regs, child);
        ptrace(PTRACE_SINGLESTEP, child, 0, 0);
        waitpid(child, &status, 0);
    }
}

【问题讨论】:

  • 您是否阅读了ptrace(2) 的所有(神秘)文档?另见Advanced Linux Programmingsyscalls(2)。顺便说一句,strace 是开源的,你可以研究它的源代码。请提供一些minimal reproducible example
  • 您可能需要查看内部/usr/include/x86_64-linux-gnu/sys/user.h
  • 我已经阅读了 ptrace(2) 并且我没有发现任何可能有用的请求,或者我没有看到它如何有用,我找到了“PTRACE_SYSEMU”,但它是也被这个主题所禁止,我已经阅读了一些 strace 源代码,会阅读更多,也许我会发现一些有用的东西;至于user.h 中的结构,我找不到可以帮助我确定被跟踪者是否在系统调用上停止的东西

标签: c ptrace


【解决方案1】:

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

假设您正在使用 x86-64,您可以这样做:

  1. 使用PTRACE_SINGLESTEP,一次只执行一条指令。
  2. 每条指令,使用PTRACE_PEEKTEXT获取指令指针指向的下一条指令。
  3. 通过将字节与opcode of syscall(两个字节:0x0f 0x05)进行比较来检查指令是否为syscall 指令。由于 x86 是 little endian,这意味着检查 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);
}

【讨论】:

  • 您好,感谢您的帮助,在系统调用之前停止 tracee 工作正常,(我必须使用带有 long 和 char[8] 的联合来获取操作码,因为 & 不是工作),这是在系统调用之后捕获的相同方式,还是我必须找到另一种方式?
  • @Architek 识别系统调用后,只需执行一次PTRACE_SINGLESTEP,系统调用将被执行,子程序将在以下指令处停止。