Skip to content
Advertisement

How to detach from process, so that it can be traced by another process?

Program steps:

  1. Create child process by fork and call execv inside it
  2. Ptrace attach to child process
  3. Do something with ptrace
  4. Detach from child
  5. execute gdb -p child_pid

But when gdb starts, it writes that child process is already tracked. How to detach from traced process, so that it can be traced by another process?

Code that do things above

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/signal.h>
#include <sys/user.h>
#include <sys/ptrace.h>

#define Error(msg) do { perror(msg); exit(0); } while(0)
#define PTRACE_E(req, pid, addr, data) 
    do { 
        if(ptrace(req, pid, addr, data) < 0) { 
            perror(#req); 
            exit(0); 
        } 
    } while(0)
#define BUF_SIZE 16

int main(int argc, char **argv) {
    pid_t pid;
    struct user_regs_struct regs;
    int status;
    char buf[BUF_SIZE];

    if (argc < 2) {
        fprintf(stderr, "Usage: %s <executable> [ARGS]n", argv[0]);
        exit(0);
    }

    pid = fork();
    if(pid < 0) {
        Error("fork");
    } else if(pid == 0) {
        if(execv(argv[1], &argv[1]) < 0)
            Error("execv");
    }

    PTRACE_E(PTRACE_ATTACH, pid, NULL, NULL);

    while(wait(&status) && !WIFEXITED(status)) {
        PTRACE_E(PTRACE_GETREGS, pid, NULL, &regs);

        if(regs.orig_eax == 26 && regs.ebx == PTRACE_TRACEME) {
            regs.eax = 0;

            PTRACE_E(PTRACE_SETREGS, pid, NULL, &regs);
            break;
        }

        PTRACE_E(PTRACE_SYSCALL, pid, NULL, NULL);
    }

    ptrace(PTRACE_DETACH, pid, NULL, NULL);

    snprintf(buf, BUF_SIZE, "%d", pid);
    execl("/usr/bin/gdb", "/usr/bin/gdb", "-p", buf, NULL);
}

Advertisement

Answer

The important thing to note here is that the PTRACE_SYSCALL request will make the target process stop at entry to or exit from a system call. The manual page says

Syscall-enter-stop and syscall-exit-stop are indistinguishable from each other by the tracer. The tracer needs to keep track of the sequence of ptrace-stops in order to not misinterpret syscall-enter-stop as syscall-exit-stop or vice versa.

If you use ptrace to change the target’s register values, you’ll change the system call arguments that the kernel will see, or the return value that the user process will see, depending on whether you do it at syscall-enter-stop or syscall-exit-stop.

Your code here is run at syscall-enter-stop:

if (regs.orig_eax == 26 && regs.ebx == PTRACE_TRACEME) {
    regs.eax = 0;
    PTRACE_E(PTRACE_SETREGS, pid, NULL, &regs);
    break;
}

It changes eax (which is -38 on entry to a system call) to 0. Since your intent was to change the return code from the target’s PTRACE_TRACEME request from -1 to 0, you’ll need to do PTRACE_SYSCALL one more time, so that the target will stop at syscall-exit-stop, before running the above code.

Currently, your code breaks out of the loop after the PTRACE_SETREGS request, and then does

ptrace(PTRACE_DETACH, pid, NULL, NULL);

which will detach from the target and continue it. The (now ex-)target completes its PTRACE_TRACEME request, which succeeds, making its parent the tracer once again.

execl("/usr/bin/gdb", "/usr/bin/gdb", "-p", buf, NULL);

Gdb will give a warning message, because it is, unexpectedly, already the tracer for the target.

User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement