/* * system-wide ptracing process * for linux/i386 * * by Yoann Guillot, dot net - 07/2005 */ #include #include #include #include #include #include #include #include #include #include #include #include #include char *signame_bynum[] = { "SIGZERO", "SIGHUP", "SIGINT", "SIGQUIT", "SIGILL", "SIGTRAP", "SIGABRT", "SIGBUS", "SIGFPE", "SIGKILL", "SIGUSR1", "SIGSEGV", "SIGUSR2", "SIGPIPE", "SIGALRM", "SIGTERM", "SIGSTKFLT", "SIGCHLD", "SIGCONT", "SIGSTOP", "SIGTSTP", "SIGTTIN", "SIGTTOU", "SIGURG", "SIGXCPU", "SIGXFSZ", "SIGVTALRM", "SIGPROF", "SIGWINCH", "SIGIO", "SIGPWR", "SIGSYS" }; char *sig_bynum(long nr) { if ((unsigned long)nr >= sizeof(signame_bynum)/sizeof(*signame_bynum) || nr < 0) return "SIG_INVAL"; return signame_bynum[nr]; } char *syscall_bynum[] = { "sys_ni_syscall", /* 0 */ "sys_exit", "sys_fork", "sys_read", "sys_write", "sys_open", /* 5 */ "sys_close", "sys_waitpid", "sys_creat", "sys_link", "sys_unlink", /* 10 */ "sys_execve", "sys_chdir", "sys_time", "sys_mknod", "sys_chmod", /* 15 */ "sys_lchown16", "sys_ni_syscall", "sys_stat", "sys_lseek", "sys_getpid", /* 20 */ "sys_mount", "sys_oldumount", "sys_setuid16", "sys_getuid16", "sys_stime", /* 25 */ "sys_ptrace", "sys_alarm", "sys_fstat", "sys_pause", "sys_utime", /* 30 */ "sys_ni_syscall", "sys_ni_syscall", "sys_access", "sys_nice", "sys_ni_syscall", /* 35 */ "sys_sync", "sys_kill", "sys_rename", "sys_mkdir", "sys_rmdir", /* 40 */ "sys_dup", "sys_pipe", "sys_times", "sys_ni_syscall", "sys_brk", /* 45 */ "sys_setgid16", "sys_getgid16", "sys_signal", "sys_geteuid16", "sys_getegid16", /* 50 */ "sys_acct", "sys_umount", "sys_ni_syscall", "sys_ioctl", "sys_fcntl", /* 55 */ "sys_ni_syscall", "sys_setpgid", "sys_ni_syscall", "sys_olduname", "sys_umask", /* 60 */ "sys_chroot", "sys_ustat", "sys_dup2", "sys_getppid", "sys_getpgrp", /* 65 */ "sys_setsid", "sys_sigaction", "sys_sgetmask", "sys_ssetmask", "sys_setreuid16",/* 70 */ "sys_setregid16", "sys_sigsuspend", "sys_sigpending", "sys_sethostname", "sys_setrlimit", /* 75 */ "sys_old_getrlimit", "sys_getrusage", "sys_gettimeofday", "sys_settimeofday", "sys_getgroups16", /* 80 */ "sys_setgroups16", "old_select", "sys_symlink", "sys_lstat", "sys_readlink", /* 85 */ "sys_uselib", "sys_swapon", "sys_reboot", "old_readdir", "old_mmap", /* 90 */ "sys_munmap", "sys_truncate", "sys_ftruncate", "sys_fchmod", "sys_fchown16", /* 95 */ "sys_getpriority", "sys_setpriority", "sys_ni_syscall", "sys_statfs", "sys_fstatfs", /* 100 */ "sys_ioperm", "sys_socketcall", "sys_syslog", "sys_setitimer", "sys_getitimer", /* 105 */ "sys_newstat", "sys_newlstat", "sys_newfstat", "sys_uname", "sys_iopl", /* 110 */ "sys_vhangup", "sys_ni_syscall", "sys_vm86old", "sys_wait4", "sys_swapoff", /* 115 */ "sys_sysinfo", "sys_ipc", "sys_fsync", "sys_sigreturn", "sys_clone", /* 120 */ "sys_setdomainname", "sys_newuname", "sys_modify_ldt", "sys_adjtimex", "sys_mprotect", /* 125 */ "sys_sigprocmask", "sys_create_module", "sys_init_module", "sys_delete_module", "sys_get_kernel_syms", /* 130 */ "sys_quotactl", "sys_getpgid", "sys_fchdir", "sys_bdflush", "sys_sysfs", /* 135 */ "sys_personality", "sys_ni_syscall", "sys_setfsuid16", "sys_setfsgid16", "sys_llseek", /* 140 */ "sys_getdents", "sys_select", "sys_flock", "sys_msync", "sys_readv", /* 145 */ "sys_writev", "sys_getsid", "sys_fdatasync", "sys_sysctl", "sys_mlock", /* 150 */ "sys_munlock", "sys_mlockall", "sys_munlockall", "sys_sched_setparam", "sys_sched_getparam", /* 155 */ "sys_sched_setscheduler", "sys_sched_getscheduler", "sys_sched_yield", "sys_sched_get_priority_max", "sys_sched_get_priority_min", /* 160 */ "sys_sched_rr_get_interval", "sys_nanosleep", "sys_mremap", "sys_setresuid16", "sys_getresuid16", /* 165 */ "sys_vm86", "sys_query_module", "sys_poll", "sys_nfsservctl", "sys_setresgid16",/* 170 */ "sys_getresgid16", "sys_prctl", "sys_rt_sigreturn", "sys_rt_sigaction", "sys_rt_sigprocmask", /* 175 */ "sys_rt_sigpending", "sys_rt_sigtimedwait", "sys_rt_sigqueueinfo", "sys_rt_sigsuspend", "sys_pread", /* 180 */ "sys_pwrite", "sys_chown16", "sys_getcwd", "sys_capget", "sys_capset", /* 185 */ "sys_sigaltstack", "sys_sendfile", "sys_ni_syscall", "sys_ni_syscall", "sys_vfork", /* 190 */ "sys_getrlimit", "sys_mmap2", "sys_truncate64", "sys_ftruncate64", "sys_stat64",/* 195 */ "sys_lstat64", "sys_fstat64", "sys_lchown", "sys_getuid", "sys_getgid", /* 200 */ "sys_geteuid", "sys_getegid", "sys_setreuid", "sys_setregid", "sys_getgroups", /* 205 */ "sys_setgroups", "sys_fchown", "sys_setresuid", "sys_getresuid", "sys_setresgid", /* 210 */ "sys_getresgid", "sys_chown", "sys_setuid", "sys_setgid", "sys_setfsuid", /* 215 */ "sys_setfsgid", "sys_pivot_root", "sys_mincore", "sys_madvise", "sys_getdents64", /* 220 */ "sys_fcntl64", "sys_ni_syscall", "sys_ni_syscall", "sys_gettid", "sys_readahead", /* 225 */ "sys_setxattr", "sys_lsetxattr", "sys_fsetxattr", "sys_getxattr", "sys_lgetxattr", /* 230 */ "sys_fgetxattr", "sys_listxattr", "sys_llistxattr", "sys_flistxattr", "sys_removexattr", /* 235 */ "sys_lremovexattr", "sys_fremovexattr", "sys_tkill", "sys_sendfile64" }; char *sys_bynum(long nr) { if ((unsigned long)nr >= sizeof(syscall_bynum)/sizeof(*syscall_bynum) || nr < 0) return "sys_ni_syscall"; return syscall_bynum[nr]; } struct pid_info { struct pid_info *next; pid_t pid; /* am i running a syscall */ char insyscall:1; /* do i want sysret to be called upon return */ char handlesysret:1; /* did i execute execve */ char inexecve:1; /* did we just attach him */ char juststarted:1; /* what is the current syscall number */ int syscall_num; /* what is the pid of my (traced) parent */ pid_t parent; /* how many (traced) children do i have */ int childcount; struct { /* am i currently waiting */ char waiting:1; /* did we send a signal to make waitpid return */ char interrupting:1; /* waitpid arguments : */ pid_t pid; int *statusptr; char nohang:1; /* do my parent need to be notified through wait */ char waited:1; /* what status to send to him */ int status; } wait; /* is there a file descriptor with the noecho ioctl */ int noecho_fd; /* what is the buffer where read will return the data for this fd */ void *read_buffer; int noechoed_bytes; } *pid_table[255]; int logfd; #define log(fmt, args...) { char buf[255]; snprintf(buf, 255, fmt "\n", ##args); write(logfd, buf, strlen(buf)); } struct pid_info *find_pid_info(pid_t pid) { struct pid_info *pi = pid_table[pid & 0xff]; while (pi) { if (pi->pid == pid) return pi; pi = pi->next; } return 0; } /* searchs a process, whom parent is pi, and who needs to be waited */ struct pid_info *find_child_waited(struct pid_info *pi) { struct pid_info *tmp; int i; for (i=0 ; i<255 ; i++) { tmp = pid_table[i]; while (tmp) { /* check if the child is waiting for his parent to wait */ if (tmp->parent == pi->pid && tmp->wait.waited) /* check if the parent is waiting for this specific child */ /* XXX no check with process groups etc */ if (pi->wait.pid <= 0 || pi->wait.pid == tmp->pid) return tmp; tmp = tmp->next; } } return 0; } /* attaches a new process */ struct pid_info *attach_topid(pid_t pid, pid_t parent) { struct pid_info *pi = find_pid_info(pid); if (pi) return 0; pi = malloc(sizeof(struct pid_info)); if (!pi) { log("fail malloc: %s", strerror(errno)); } if (ptrace(PTRACE_ATTACH, pid, 0, 0) == -1) { log("fail attach %d: %s", pid, strerror(errno)); free(pi); return 0; } pi->pid = pid; pi->insyscall = 0; pi->handlesysret = 0; pi->inexecve = 0; pi->syscall_num = 0; pi->childcount = 0; pi->juststarted = 1; pi->wait.waiting = 0; pi->wait.interrupting = 0; pi->wait.nohang = 0; pi->wait.pid = 0; pi->wait.statusptr = 0; pi->parent = parent; pi->wait.waited = 0; pi->wait.status = 0; pi->noecho_fd = -1; pi->read_buffer = 0; pi->noechoed_bytes = 0; pi->next = pid_table[pid & 0xff]; if (parent != -1) { struct pid_info *ppi = find_pid_info(parent); if (!ppi) { log("attach: no traced parent %d for %d ?", parent, pid); pi->parent = -1; } else ppi->childcount++; } /* race here: sum(childcount) != total of referenced processes */ pid_table[pid & 0xff] = pi; return pi; } /* detaches from a process, exit if no more process are traced */ void detach_frompid_quiet(pid_t pid, int quiet) { struct pid_info **pprev = &pid_table[pid & 0xff]; int i; while (*pprev) { if ((*pprev)->pid == pid) { struct pid_info *t = *pprev; *pprev = (*pprev)->next; /* update parent's child count */ if (t->parent != -1 && (t->next = find_pid_info(t->parent))) { if (t->next->childcount > 0) t->next->childcount--; else { log("detach: %d's parent has bad childcount", pid); } } /* update childs' parent */ if (t->childcount > 0) { for (i=0 ; i<255 ; i++) { t->next = pid_table[i]; while (t->next) { if (t->next->parent == pid) { t->next->parent = -1; t->childcount--; } t->next = t->next->next; } } if (t->childcount != 0) { log("detach: %d's child count error", pid); } } free(t); } else pprev = &(*pprev)->next; } if (ptrace(PTRACE_DETACH, pid, 0, 0) == -1 && !quiet) { log("fail detach %d: %s", pid, strerror(errno)); } /* check if there are still process being traced */ for (i=0 ; i<255 ; i++) if (pid_table[i]) return; log("no more traced process: terminating"); close(logfd); exit(0); } inline void detach_frompid(pid_t pid) { detach_frompid_quiet(pid, 0); } /* * Signal handler: * Terminates on SIGTERM/SIGINT * Responsible of the forwarding of signals received from `our' children to their real parent */ void sigh_chld(int s, siginfo_t *si, void *v) { (void)s; (void)v; if (si->si_code == CLD_TRAPPED) /* this is for us */ return; if (si->si_code == CLD_EXITED || si->si_code == CLD_KILLED) /* these messages will be sent by ptrace_detach after wait() inform us from the situation */ return; /* forward */ // log("sigh: received SIGCHLD, forwarding"); struct pid_info *pi; pi = find_pid_info(si->si_pid); if (!pi) { log("unknown child %d", si->si_pid); return; } if (pi->parent == -1) { // log("%d has no known parent to forward SIGCHLD to", pi->pid); return; } /* XXX how can we forward the siginfo ? */ /* or hook the sighandler entry, where we could overwrite the parameters */ kill(pi->parent, s); } void sigh_end(int s, siginfo_t *si, void *v) { (void)v; (void)si; log("sigh: received %s, dying", sig_bynum(s)); int i; for (i=0 ; i<255 ; i++) while (pid_table[i]) { struct pid_info *pi = pid_table[i]; /* XXX is this sending us signals ? */ ptrace(PTRACE_DETACH, pi->pid, 0, 0); pid_table[i] = pi->next; free(pi); } close(logfd); exit(0); } /* * changes the values returned by the waitpid syscall * * returns nonzero if pi is no longer valid (detached & freed) */ int wait_hackretval(struct pid_info *pi, struct pid_info *cld) { if (ptrace(PTRACE_POKEUSR, pi->pid, 4*EAX, cld->pid) == -1) { log("fail ptrace setret waithack %d: %s", pi->pid, strerror(errno)); detach_frompid(pi->pid); return 1; } if (pi->wait.statusptr) { if (ptrace(PTRACE_POKEDATA, pi->pid, pi->wait.statusptr, cld->wait.status) == -1) { log("fail ptrace setstatus waithack %d: %s", pi->pid, strerror(errno)); detach_frompid(pi->pid); return 1; } } /* XXX when should we clear this flag ? */ cld->wait.waited = 0; return 0; } /* * This function is called on every syscall entry by any process traced * * It is responsible for setting the handlesysret flag for the process, and the other flags. * pi->insyscall is set to 1 before entering this function. */ void handle_syscall(struct pid_info *pi) { static struct user_regs_struct regs; pi->handlesysret = 0; /* TODO * hook ptrace, detach from debugee to allow ptracing * handling of vfork */ switch (pi->syscall_num) { case SYS_fork: /* hook the return value, to attach to the new child */ pi->handlesysret = 1; break; case SYS_read: if (pi->noecho_fd == -1) break; /* fetch arguments */ if (ptrace(PTRACE_GETREGS, pi->pid, 0, ®s) == -1) { log("fail ptrace getregs read %d: %s", pi->pid, strerror(errno)); detach_frompid(pi->pid); break; } if (regs.ebx == pi->noecho_fd) { /* reading from the noecho fd: wait return to steal noechoed data */ // log("read noecho %d %d", pi->pid, pi->noecho_fd); pi->read_buffer = (void *)regs.ecx; pi->handlesysret = 1; } break; case SYS_close: if (pi->noecho_fd == -1) break; /* check if we are closing the noecho fd */ if (ptrace(PTRACE_GETREGS, pi->pid, 0, ®s) == -1) { log("fail ptrace getregs close %d: %s", pi->pid, strerror(errno)); detach_frompid(pi->pid); break; } if (pi->noecho_fd == regs.ebx) { // log("close %d %d", pi->pid, pi->noecho_fd); pi->noecho_fd = -1; } break; case SYS_wait4: case SYS_waitpid: /* let it go if we do not know any of his children */ if (pi->childcount == 0) break; /* the process is calling waitpid: we must try hard to let him think he still receive the data * from his children (they are ours now due to PTRACE_ATTACH) */ if (ptrace(PTRACE_GETREGS, pi->pid, 0, ®s) == -1) { log("fail ptrace getregs waitpid %d: %s", pi->pid, strerror(errno)); detach_frompid(pi->pid); break; } // log("%d: waitpid(%ld, %lx, %lx - %s | %s)", pi->pid, regs.ebx, regs.ecx, regs.edx, // regs.edx & WUNTRACED ? "WUNTRACED" : "0", regs.edx & WNOHANG ? "WNOHANG" : "0"); /* he waits specifically for a child we do not know: let it be */ if ((regs.ebx > 0) && !find_pid_info(regs.ebx)) break; /* the process is waiting either for a child we are monitoring or for a child we are not aware of */ // log("%d: waitpid h4x", pi->pid); pi->wait.pid = regs.ebx; pi->wait.statusptr = (int *)regs.ecx; pi->wait.nohang = (regs.edx & WNOHANG) ? 1 : 0; pi->wait.waiting = 1; /* we can now hook the return of the syscall, to adapt the values returned to the process */ /* we may receive information on a child while the process is still in kernel mode (waiting for a child * not attached). In this case we will need to wake him up with a signal we'll hide from him */ pi->handlesysret = 1; break; case SYS_execve: /* we must handle execve because it is called 2 times, and the second has no return */ if (ptrace(PTRACE_GETREGS, pi->pid, 0, ®s) == -1) { log("fail ptrace getregs close %d: %s", pi->pid, strerror(errno)); detach_frompid(pi->pid); break; } if (!regs.ebx && !regs.ecx && !regs.edx && pi->inexecve) { /* execve was called and is recalled now: this means i am running a new * exe, which is no more in a syscall */ pi->insyscall = 0; pi->inexecve = 0; } else { pi->handlesysret = 1; pi->inexecve = 1; } break; /* if (!regs.ecx) break; // log filename of new executable loaded errno = 0; void *nptr = ptrace(PTRACE_PEEKDATA, pi->pid, regs.ecx, 0); if (nptr == -1 && errno) { log("fail ptrace get exename %d: %s", pi->pid, strerror(errno)); detach_frompid(pi->pid); break; } if (!nptr) break; long exename[256/4]; long tmp; int i; for (i=0 ; i<256/4 ; i++) { errno = 0; tmp = ptrace(PTRACE_PEEKDATA, pi->pid, nptr + 4*i, 0); if (tmp == -1 && errno) { log("fail ptrace get exename %d: %s", pi->pid, strerror(errno)); detach_frompid(pi->pid); return; } exename[i] = tmp; // look for end of string if (!(tmp & 0xff) || !((tmp >> 8) & 0xff) || !((tmp >> 16) & 0xff) || !((tmp >> 24) & 0xff)) break; } log("%d: execve(\"%.256s\")", pi->pid, (char *)exename); */ break; case SYS_ioctl: /* * we hook this one to check for a TCSET NOECHO type modification * (very likely to receive secrets typed from keyboard) * * TODO test hooking of open("/dev/tty") and so on * TODO what happens if the prog sends an invalid pointer to the kernel ? do we fault ? */ if (ptrace(PTRACE_GETREGS, pi->pid, 0, ®s) == -1) { log("fail ptrace getregs ioctl %d: %s", pi->pid, strerror(errno)); detach_frompid(pi->pid); break; } if (regs.ecx >= 0x5402 && regs.ecx <= 0x5404) { /* TCSET* */ long lctl; errno = 0; /* check linectl member of the structure passed to the kernel (it defines the noecho flag) */ lctl = ptrace(PTRACE_PEEKDATA, pi->pid, regs.edx + 12, 0); if (lctl == -1 && errno) { log("fail ptrace get lctl %d: %s", pi->pid, strerror(errno)); detach_frompid(pi->pid); break; } if (lctl & 8) { /* set echo: clear the fd */ if (pi->noecho_fd == regs.ebx) { // log("ioctl %d echo %d", pi->pid, pi->noecho_fd); pi->noecho_fd = -1; } } else { /* set noecho: define fd (only one can be defined at a time) */ if (pi->noecho_fd != -1 && pi->noecho_fd != regs.ebx) { // log("ioctl %d set %ld noecho: already %d noecho !", pi->pid, regs.ebx, pi->noecho_fd); } pi->noecho_fd = regs.ebx; pi->noechoed_bytes = 0; // log("ioctl %d noecho %d", pi->pid, pi->noecho_fd); } } break; } } /* * This function is called upon completion of a syscall, if the process had the handlesysret flag set */ void handle_syscall_ret(struct pid_info *pi) { long retval; /* retrieve the syscall return value */ errno = 0; retval = ptrace(PTRACE_PEEKUSR, pi->pid, 4*EAX, 0); if (retval == -1 && errno) { log("fail ptrace getregs ret %d: %s", pi->pid, strerror(errno)); detach_frompid(pi->pid); return; } switch (pi->syscall_num) { case SYS_fork: /* fork returned a new pid to attach to */ if (retval <= 0) break; /* XXX is the child running at this moment ? or did the ptracing give us the hand before it starts ? */ // log("fork: attaching to new child %ld", retval); struct pid_info *chpi; chpi = attach_topid(retval, pi->pid); if (chpi) { /* the child heritates the fd from his parent */ chpi->noecho_fd = pi->noecho_fd; chpi->read_buffer = pi->read_buffer; } break; case SYS_execve: if (retval < 0) pi->inexecve = 0; break; case SYS_read: /* the process was reading from a noecho fd */ if (retval < 0) { // log("read noecho %d: %s", pi->pid, strerror(-retval)); } else { log("read noecho %d: %ld bytes", pi->pid, retval); } if (retval > 0) { /* the read succeeded: log the data */ long data; int offset = 0; int len; while (offset < retval) { errno = 0; data = ptrace(PTRACE_PEEKDATA, pi->pid, pi->read_buffer+offset, 0); if (data == -1 && errno) { log("\nfail ptrace peekdata %d: %s", pi->pid, strerror(errno)); detach_frompid(pi->pid); return; } if (offset + 4 <= retval) len = 4; else len = retval - offset; write(logfd, &data, len); offset += len; } /* tag the end of the data */ /* XXX *may* be fakeable... */ log("\nread noecho ended"); pi->noechoed_bytes += retval; if (pi->noechoed_bytes > 100) { /* avoid useless logs */ pi->noecho_fd = -1; pi->noechoed_bytes = 0; } } break; case SYS_wait4: case SYS_waitpid: /* * We are here because we control children which logically beyond to pi. * * If the wait was successful, we do not touch anything. * * If we control all the children of the process: waitpid returns -ENOCHILD, then we must * emulate the read waitpid behavior: * - return 0 if NOHANG, * - block the process otherwise, until one of the childs wants to manifest itself. * XXX What happens if the process receives a signal in this state ? are we aware of it ? * * If we do not control all his children, the kernel might let him sleep, and will awake him * only if one of the non-traced children do it. If one that is controlled by us needs to * wake him up, we must do it manually, using a signal, and hiding it from the process. */ // log("%d: waitpid returned, retval %ld", pi->pid, retval); ; struct pid_info *cld = find_child_waited(pi); // log("%d: find_child -> %d", pi->pid, cld ? cld->pid : 0); switch (retval) { case -ECHILD: /* the process has no children we do not control */ /* check if one of those needs signalling */ if (cld) { /* return him */ // log("%d: returning the child", pi->pid); if (wait_hackretval(pi, cld)) return; } else { if (pi->wait.nohang) { /* hack retval to 0 */ // log("%d: nohang, returning 0", pi->pid); if (ptrace(PTRACE_POKEUSR, pi->pid, 4*EAX, 0) == -1) { log("fail ptrace setreg waitpid.ret %d: %s", pi->pid, strerror(errno)); detach_frompid(pi->pid); return; } } else { // log("%d: wait simulation running!", pi->pid); /* block the process (just need to return without resetting wait.waiting) */ return; } } break; case -EINTR: case -512: // -ERESTARTSYS if (cld) /* return him */ if (wait_hackretval(pi, cld)) return; /* else/default: nothing to do, return asis to the process */ } pi->wait.waiting = 0; pi->wait.pid = -1; pi->wait.statusptr = 0; break; } } /* * Main loop of the program * * Waits for a child to signal himself, handle him: * either propagates the state to the legitimate parent, * or call the syscall handlers. * * Re-run the process (unless in waitpid hack). */ void main_loop(void) { pid_t pid; int status; int sendsig; struct pid_info *pi; for (;;) { /* signal to send to the process upon wakeup */ sendsig = 0; /* wait for a child */ pid = waitpid(-1, &status, 0); /* did wait fail ? */ if (pid == -1) { if (errno == EINTR) continue; log("fail waitpid: %s", strerror(errno)); /* die */ kill(getpid(), SIGTERM); return; } pi = find_pid_info(pid); if (!pi) { /* unknown child ?? */ log("fail find_pid_info(%d)", pid); /* try to detach anyhow */ detach_frompid(pid); continue; } if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) { /* the child was stopped because he is entering or returning from a syscall */ if (!pi->insyscall) { /* entering syscall */ pi->insyscall = 1; pi->syscall_num = ptrace(PTRACE_PEEKUSR, pi->pid, ORIG_EAX*4, 0); // log("%d: syscall %s", pid, sys_bynum(pi->syscall_num)); handle_syscall(pi); } else { /* returning from syscall */ /* { long retval; errno = 0; retval = ptrace(PTRACE_PEEKUSR, pi->pid, 4*EAX, 0); if (retval == -1 && errno) { log("fail ptrace getregs ret %d: %s", pi->pid, strerror(errno)); detach_frompid(pi->pid); continue; } log("%d: syscall ret -> %ld (%s)", pi->pid, retval, retval < 0 ? strerror(-retval) : "ok"); } */ pi->insyscall = 0; if (pi->handlesysret) { handle_syscall_ret(pi); /* check if we should allow the process to run or not */ if (!find_pid_info(pid)) continue; if (pi->wait.waiting) continue; } } } else if (WIFSTOPPED(status) && WSTOPSIG(status) != SIGSTOP && WSTOPSIG(status) != SIGTTIN && WSTOPSIG(status) != SIGTSTP && WSTOPSIG(status) != SIGTTOU) { /* XXX get the list of signals handled by the process to not forward bad things to the parent */ /* Would be great to be able to know if we got control as parent or as debugger */ /* the child received a signal, that we should forward to him, unless we send ourself this signal */ if (pi->wait.interrupting) { /* XXX race with other signals... check sig number before clearing interrupting ? */ /* in this case wait_ret should also check the flag */ pi->wait.interrupting = 0; // log("%d: detected interrupting sig %d (%s), not forwarding", pi->pid, WSTOPSIG(status), sig_bynum(WSTOPSIG(status))); } else { sendsig = WSTOPSIG(status); // log("%d: forwarding sig %d (%s)", pi->pid, sendsig, sig_bynum(sendsig)); } } else { /* the child's state changed, we have to inform his parent */ /* the zombies are not destroyed by our wait (this is good, we just need to detach and let the parent wait) */ if (WIFSTOPPED(status)) { if (pi->juststarted) { pi->juststarted = 0; goto run; } // log("%d: stopped", pi->pid); } else if (WIFSIGNALED(status)) { // log("%d: killed by %s", pi->pid, sig_bynum(WTERMSIG(status))); } else if (WIFEXITED(status)) { // log("%d: exited", pi->pid); } else { // log("%d: child status error: %x", pi->pid, status); } struct pid_info *ppi; if (pi->parent != -1 && (ppi = find_pid_info(pi->parent))) { if (pi->wait.waited) { // log("%d: already in state waited !", pi->pid); } pi->wait.waited = 1; pi->wait.status = status; /* check if the parent is waiting */ if (ppi->wait.waiting) { /* check if the parent is still in kernel or if we did stop him */ if (ppi->insyscall) { /* kill ppi->pid */ // log("%d: parent %d is waiting, interrupting him", pi->pid, ppi->pid); ppi->wait.interrupting = 1; kill(ppi->pid, SIGCHLD); } else { /* let waitpid return */ // log("%d: hacking %d's waitpid return values, and returning", pi->pid, ppi->pid); #if 0 wait_hackretval(ppi, pi); #else // log("%d: waitpid->EINTR", ppi->pid); if (ptrace(PTRACE_POKEUSR, ppi->pid, 4*EAX, -EINTR) == -1) { log("fail ptrace setreg waitpid.ret %d: %s", ppi->pid, strerror(errno)); detach_frompid(ppi->pid); return; } #endif ppi->wait.waiting = 0; if (ptrace(PTRACE_SYSCALL, ppi->pid, 0, 0) == -1) { log("fail wait_wakeup ptrace_syscall %d: %s", ppi->pid, strerror(errno)); detach_frompid(ppi->pid); } } } } else { /* XXX no known parent -> ? */ // log("%d: has no known parent...", pi->pid); } /* if child died: detach */ if (!WIFSTOPPED(status)) { // log("%d: terminated, detaching", pi->pid); detach_frompid_quiet(pi->pid, 1); continue; } } /* on error, the process could have been detached */ if (!find_pid_info(pid)) continue; run: /* let the process run until entering or leaving kernel mode */ /* forward a signal if specified */ if (ptrace(PTRACE_SYSCALL, pi->pid, 0, (void *)sendsig) == -1) { log("fail ptrace_syscall %d: %s", pi->pid, strerror(errno)); detach_frompid(pi->pid); } } } /* Traces the first processes, then enters the main loop */ int main(int argc, char **argv) { if (argc < 3) { printf("need args: lg pds\n"); exit(1); } logfd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); if (logfd == -1) { perror("open"); exit(1); } int i; for (i = 0 ; i < 255 ; i++) pid_table[i] = 0; struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_flags = SA_SIGINFO; sa.sa_sigaction = sigh_chld; sigaction(SIGCHLD, &sa, 0); sa.sa_sigaction = sigh_end; sigaction(SIGINT, &sa, 0); sigaction(SIGTERM, &sa, 0); for (i = 2 ; argv[i] ; i++) { pid_t pid = atoi(argv[i]); // log("init: attach to %d", pid); attach_topid(pid, -1 /* TODO check childness */); } /* XXX if we fork now, do the child inherits the debuggee ? */ /* ensure we are running for something */ for (i=0 ; i<255 ; i++) if (pid_table[i]) break; if (i == 255) { printf("could not attach to anybody\n"); exit(1); } main_loop(); /* does not return */ log("main_loop returned..."); close(logfd); return 1; }