Architek
Architek

Reputation: 129

Intercept only syscall with PTRACE_SINGLESTEP

We have a school project where we need to re-code "strace".

We have to only intercept syscall like write and read, but we cannot use PTRACE_SYSCALL. I'm looking for a way to do that using PTRACE_SINGLESTEP, I've already coded a way to print the syscall and when I'm using PTRACE_SYSCALL it works fine, but when I use PTRACE_SINGLESTEP I can't find a way to only print the syscalls.

Here is the code I use, maybe someone can help me figure out what's wrong with it:

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);
    }
}

Upvotes: 2

Views: 835

Answers (1)

Marco Bonelli
Marco Bonelli

Reputation: 69276

If you cannot use PTRACE_SYSCALL to stop the child right before/after a syscall, then you will have to manually detect when one is about to happen. I doubt that checking the source code of strace would help, since strace is most likely using PTRACE_SYSCALL, no reason to manually decode instructions.

Assuming you are working on x86-64, here's how you can do it:

  1. Using PTRACE_SINGLESTEP, keep stepping one instruction at a time.
  2. Each instruction, use PTRACE_PEEKTEXT to fetch the next instruction pointed by the instruction pointer.
  3. Check if the instruction is a syscall instruction by comparing the bytes with the opcode of syscall, which is two bytes: 0x0f 0x05. Since x86 is little endian, this means checking whether the return value of ptrace(PTRACE_PEEKDATA, ...) has the two least significant bytes set to 0x050f.

NOTE: If you are on another architecture, or if you also want to detect 32-bit syscalls, you can simply check for different/more values on step 3. On Linux x86-64, there are multiple ways to issue a syscall, with different opcodes. For example, 32-bit syscalls on Linux are done through int 0x80 (opcode 0xcd 0x80). Check this other answer of mine for a list.

Here's an example:

#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);
}

Upvotes: 4

Related Questions