Utiliser ptrace pour suivre tous les appels execve () sur des enfants

J’essaie d’écrire un outil sur Linux CentOS pour suivre tous les processus générés et ce qui est exécuté. En gros, je suis intéressé à parcourir tous les fork / clones et à émettre toutes les lignes de commande de execve() . Strace le fait déjà (en partie), mais il tronque également les appels et les arguments. Je voulais aussi mieux comprendre comment fonctionne ptrace() .

Ainsi, le premier obstacle consistait à déterminer comment utiliser ptrace() pour parcourir un fork / clone sans que le programme de suivi ne nécessite de copier une copie de lui-même. J’ai creusé et découvert comment strace fait cela. Puisque fork est implémenté avec clone sous Linux, j’ai remarqué que strace pesait quelques bits dans le syscall du clone pour permettre le traçage des enfants sans aucun mal de tête supplémentaire.

Donc, le code est essentiellement un gros:

 while (1) { int pid = wait3(-1,...); /* process what happened */ ptrace(PTRACE_SYSCALL, pid,...); } 

Cela fonctionne très bien pour des processus relativement simples comme /bin/sh , cependant, certains processus entraînent le blocage indéfini de l’ wait() . La seule chose que j’ai été capable de déterminer, c’est que le processus que je suis en train de faire consiste à exécuter un sys_rt_sigsuspend() sur son enfant (donc, le petit-enfant du traceur) et ensuite les choses se calent.

J’étais curieux de savoir s’il était possible de déboguer ce qui pourrait arriver. Quelque chose empêche clairement l’arborescence des processus d’avancer

Voici le code source du programme en question:

 #include  #include  #include  #include  #include  #include  /* For the clone flags */ #include  /* #include  */ #include  #include  /* Defines our syscalls like */ #include  #include  #include  #include  #include  #include  using namespace std; char bufstr[4096]; #ifdef __x86_64__ #define REG_ACC RAX #define REG_ARG1 RDI #define REG_ARG2 RSI #else #define REG_ACC EAX #define REG_ARG1 EBX #define REG_ARG2 ECX #endif /* Trace control structure per PID that we're tracking */ class tcb { int pid_; int entering_; public: tcb(int pid, int entering = 1) : pid_(pid), entering_(entering) {}; tcb() : pid_(-1) {}; // tcb(const tcb& p) : pid_(pid.pid()), entering_(entering.entering()) {}; int& pid() { return pid_; } int& entering() { return entering_; } }; /* Fetch a ssortingng from process (pid) at location (ptr). Buf is the place * to store the data with size limit (size). Return the number of bytes * copied. */ int get_ssortingng(int pid, long ptr, char *buf, int size) { long data; char *p = (char *) &data; int j = 0; while ((data = ptrace(PTRACE_PEEKTEXT, pid, (void *) ptr, 0)) && j < size) { int i; for (i = 0; i < sizeof(data) && j < size; i++, j++) { if (!(buf[j] = p[i])) goto done; } ptr += sizeof(data); } done: buf[j] = '\0'; return j; } int main(int argc, char *argv[]) { int status = 0; long scno = 0; // int entering = 1; struct user_regs_struct regs; map pidTable; struct sigaction sa; /* Setup */ int pid = fork(); if (!pid && argc) { if (ptrace(PTRACE_TRACEME, 0, 0, 0)  0) { //fprintf(stderr, "%d: Restarting %d\n", getpid(), pid); if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) { perror("ptrace(PTRACE_SYSCALL,..."); exit(1); } } // waitpid(pid, &status, 0); // pid = waitpid(-1, &status, 0); pid = wait3(&status, __WALL, 0); // fprintf(stderr, "Pid from wait is %d\n", pid); if (pid < 0) { perror("waitpid"); break; } else { /* fprintf(stderr, "%d: Status is: ", pid); */ /* if (WIFEXITED(status)) { fprintf(stderr, "exited"); } else if (WIFSIGNALED(status)) { fprintf(stderr, "exited"); } else if (WIFSTOPPED(status), "stopped") { fprintf(stderr, "stopped"); } else if (WIFCONTINUED(status)) { fprintf(stderr, "continued"); } fprintf(stderr, "\n"); */ if (WIFEXITED(status) || WIFSIGNALED(status)) { /* Probably empty the table here */ pidTable.erase(pid); fprintf(stderr, "Detect process term/kill %d\n", pid); /* if (ptrace(PTRACE_DETACH, pid, 0, 0) < 0) { perror("ptrace"); } */ pid = -1; continue; } } ptrace(PTRACE_GETREGS, pid, 0, &regs); #ifdef __x86_64__ scno = regs.orig_rax; #else scno = regs.orig_eax; #endif /* __x86_64__ */ if (scno == SYS_execve) { fprintf(stderr, "%d: Exec branch\n", pid); if (pidTable[pid].entering()) { long ldata, ptr, ptr1; ptrace(PTRACE_GETREGS, pid, 0, &regs); #ifdef __x86_64__ ptr = regs.rdi; #else ptr = regs.ebx; #endif /* __x86_64__ */ fprintf(stderr, "%d: exec(", pid); if (ptr) { get_string(pid, ptr, bufstr, sizeof(bufstr)); fprintf(stderr, "%s", bufstr); } #ifdef __x86_64__ ptr1 = regs.rsi; #else ptr1 = regs.ecx; #endif /* __x86_64__ */ for (; ptr1; ptr1 += sizeof(unsigned long)) { ptr = ptr1; /* Indirect through ptr since we have char *argv[] */ ptr = ptrace(PTRACE_PEEKTEXT, pid, (void *) ptr, 0); if (!ptr) break; get_string(pid, ptr, bufstr, sizeof(bufstr)); fprintf(stderr, ", %s", bufstr); } fprintf(stderr, ")\n"); pidTable[pid].entering() = 0; } else { long acc = ptrace(PTRACE_PEEKUSER, pid, sizeof(unsigned long) * REG_ACC, 0); pidTable[pid].entering() = 1; fprintf(stderr, "%d: Leaving exec: eax is %ld\n", pid, acc); } } else if (scno == SYS_fork || scno == SYS_clone) { fprintf(stderr, "%d: fork/clone branch\n", pid); if (pidTable[pid].entering()) { long flags = ptrace(PTRACE_PEEKUSER, pid, (sizeof(unsigned long) * REG_ARG1), 0); fprintf(stderr, "%d: Entering fork/clone\n", pid); pidTable[pid].entering() = 0; if (ptrace(PTRACE_POKEUSER, pid, (sizeof(unsigned long) * REG_ARG1), flags | CLONE_PTRACE & ~(flags & CLONE_VFORK ? CLONE_VFORK | CLONE_VM : 0)) < 0) { perror("ptrace"); } if (ptrace(PTRACE_POKEUSER, pid, (sizeof(unsigned long) * REG_ARG2), 0) < 0) { perror("ptrace"); } } else { // int child; ptrace(PTRACE_GETREGS, pid, 0, &regs); #ifdef __x86_64__ fprintf(stderr, "%d: Leaving fork/clone: rax = %ld\n", pid, regs.rax); #else fprintf(stderr, "%d: Leaving fork/clone: eax = %ld\n", pid, regs.eax); #endif pidTable[pid].entering() = 1; #ifdef __x86_64__ if (regs.rax <= 0) { #else if (regs.eax <= 0) { #endif continue; } #ifdef __x86_64__ int newpid = regs.rax; #else int newpid = regs.eax; #endif pidTable[newpid] = tcb(newpid, 0); //pidTable[newpid] = tcb(newpid, 1); //pidTable[newpid] = pidTable[pid]; fprintf(stderr, "%d: forked child is %d\n", pid, newpid); } } else if (scno == SYS_exit) { fprintf(stderr, "%d: exit syscall detected\n", pid); } else if (scno < 0) { fprintf(stderr, "Negative syscall number for %d\n", pid); exit(1); } else { fprintf(stderr, "%d: Scno is %ld\n", pid, scno); } } } return 0; } 

Il existe des drapeaux du sous-ensemble ptrace PTRACE_SETOPTIONS: PTRACE_O_TRACEFORK, PTRACE_O_TRACEEXEC et PTRACE_O_TRACEEXIT. Plus est à la page de manuel de ptrace.

Au fait. strace -f -s99999 -e trace=clone,execve semble donner des résultats de bonne qualité. Pour voir une trace des propres actions de strace, vous pouvez essayer systemtap, c.-à-d.

# stap -e 'probe syscall.ptrace {if (execname()=="strace") log(argstr)}' -c 'strace COMMAND'

(Le systemtap actuel n’imprime pas correctement les arguments de ptrace.)

Ou vous pouvez strace strace:

strace -e trace=ptrace strace -f -s99999 -e trace=clone,execve COMMAND