2

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);
    }
}
Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
Architek
  • 53
  • 5
  • Did you read all the (arcane) documentation of [ptrace(2)](https://man7.org/linux/man-pages/man2/ptrace.2.html)? See also [*Advanced Linux Programming*](https://mentorembedded.github.io/advancedlinuxprogramming/) and [syscalls(2)](https://man7.org/linux/man-pages/man2/syscalls.2.html). BTW, `strace` is open source, you could study its source code. Please provide some [mre] – Basile Starynkevitch Apr 11 '21 at 12:56
  • You may need to look inside `/usr/include/x86_64-linux-gnu/sys/user.h` – Basile Starynkevitch Apr 11 '21 at 12:59
  • I've read ptrace(2) and I do not found any request that may be useful, or maybe I didn't see how It can be useful, I've found "PTRACE_SYSEMU" but it's forbiden by the subject too, I had read a little of strace source code, gonna read more, maybe I'll find something useful; as for the struct in `user.h` I can't find somthing that help me figure out if the tracee stoped on a syscall or not – Architek Apr 11 '21 at 13:09

1 Answers1

4

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);
}
Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
  • Hi thanks for you help, it's working fine to stop the tracee before the syscall, (I juste had to use an union with a long and char[8] to get the opcode because the & is not working), is that the same way to catch after the syscall, or have I to find another way? – Architek Apr 11 '21 at 14:51
  • @Architek after identifying a syscall, just do one single `PTRACE_SINGLESTEP`, the syscall will be executed and the child will stop right at the following instruction. – Marco Bonelli Apr 11 '21 at 14:52
  • be careful, you need to put parentheses around the arguments of the '&' (bitwise and) operator, it has the same precedence as && (boolean and) – sybog64 Apr 07 '22 at 13:55