#!/usr/bin/env compileAndGo # -*- coding: utf-8 mode: c -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 # cf https://github.com/nelhage/reptyr language=c compiler=gcc compilerArgs=-O2 -D_GNU_SOURCE -o $cacheDir/$executableFilename $cacheDir/$sourceFilename !# //@inc[lib/reptyr/ptrace.h /* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include enum child_state { ptrace_detached = 0, ptrace_at_syscall, ptrace_after_syscall, ptrace_running, ptrace_stopped, ptrace_exited }; struct ptrace_child { pid_t pid; enum child_state state; int personality; int status; int error; unsigned long forked_pid; struct user user; unsigned long saved_syscall; }; struct syscall_numbers { long nr_mmap; long nr_mmap2; long nr_munmap; long nr_getsid; long nr_setsid; long nr_setpgid; long nr_fork; long nr_wait4; long nr_signal; long nr_rt_sigaction; long nr_open; long nr_close; long nr_ioctl; long nr_dup2; }; typedef unsigned long child_addr_t; int ptrace_wait(struct ptrace_child *child); int ptrace_attach_child(struct ptrace_child *child, pid_t pid); int ptrace_finish_attach(struct ptrace_child *child, pid_t pid); int ptrace_detach_child(struct ptrace_child *child); int ptrace_wait(struct ptrace_child *child); int ptrace_advance_to_state(struct ptrace_child *child, enum child_state desired); int ptrace_save_regs(struct ptrace_child *child); int ptrace_restore_regs(struct ptrace_child *child); unsigned long ptrace_remote_syscall(struct ptrace_child *child, unsigned long sysno, unsigned long p0, unsigned long p1, unsigned long p2, unsigned long p3, unsigned long p4, unsigned long p5); int ptrace_memcpy_to_child(struct ptrace_child *, child_addr_t, const void*, size_t); int ptrace_memcpy_from_child(struct ptrace_child *, void*, child_addr_t, size_t); struct syscall_numbers *ptrace_syscall_numbers(struct ptrace_child *child); //@inc]lib/reptyr/ptrace.h //@inc[lib/reptyr/reptyr.h /* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #define REPTYR_VERSION "0.4dev" int attach_child(pid_t pid, const char *pty, int force_stdio); #define __printf __attribute__((format(printf, 1, 2))) void __printf die(const char *msg, ...); void __printf debug(const char *msg, ...); void __printf error(const char *msg, ...); //@inc]lib/reptyr/reptyr.h //@inc[lib/reptyr/attach.c /* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include "ptrace.h" //#include "reptyr.h" #define TASK_COMM_LENGTH 16 struct proc_stat { pid_t pid; char comm[TASK_COMM_LENGTH+1]; char state; pid_t ppid, sid, pgid; dev_t ctty; }; #define do_syscall(child, name, a0, a1, a2, a3, a4, a5) \ ptrace_remote_syscall((child), ptrace_syscall_numbers((child))->nr_##name, \ a0, a1, a2, a3, a4, a5) int parse_proc_stat(int statfd, struct proc_stat *out) { char buf[1024]; int n; unsigned dev; lseek(statfd, 0, SEEK_SET); if (read(statfd, buf, sizeof buf) < 0) return errno; n = sscanf(buf, "%d (%16[^)]) %c %d %d %d %u", &out->pid, out->comm, &out->state, &out->ppid, &out->sid, &out->pgid, &dev); if (n == EOF) return errno; if (n != 7) { return EINVAL; } out->ctty = dev; return 0; } int read_proc_stat(pid_t pid, struct proc_stat *out) { char stat_path[PATH_MAX]; int statfd; int err; snprintf(stat_path, sizeof stat_path, "/proc/%d/stat", pid); statfd = open(stat_path, O_RDONLY); if (statfd < 0) { error("Unable to open %s: %s", stat_path, strerror(errno)); return -statfd; } err = parse_proc_stat(statfd, out); close(statfd); return err; } static void do_unmap(struct ptrace_child *child, child_addr_t addr, unsigned long len) { if (addr == (unsigned long)-1) return; do_syscall(child, munmap, addr, len, 0, 0, 0, 0); } int *get_child_tty_fds(struct ptrace_child *child, int statfd, int *count) { struct proc_stat child_status; struct stat tty_st, st; char buf[PATH_MAX]; int n = 0, allocated = 0; int *fds = NULL; DIR *dir; struct dirent *d; int *tmp = NULL; debug("Looking up fds for tty in child."); if ((child->error = parse_proc_stat(statfd, &child_status))) return NULL; debug("Resolved child tty: %x", (unsigned)child_status.ctty); if (stat("/dev/tty", &tty_st) < 0) { child->error = errno; error("Unable to stat /dev/tty"); return NULL; } snprintf(buf, sizeof buf, "/proc/%d/fd/", child->pid); if ((dir = opendir(buf)) == NULL) return NULL; while ((d = readdir(dir)) != NULL) { if (d->d_name[0] == '.') continue; snprintf(buf, sizeof buf, "/proc/%d/fd/%s", child->pid, d->d_name); if (stat(buf, &st) < 0) continue; if (st.st_rdev == child_status.ctty || st.st_rdev == tty_st.st_rdev) { if (n == allocated) { allocated = allocated ? 2 * allocated : 2; tmp = realloc(fds, allocated * sizeof *tmp); if (tmp == NULL) { child->error = errno; error("Unable to allocate memory for fd array."); free(fds); fds = NULL; goto out; } fds = tmp; } debug("Found an alias for the tty: %s", d->d_name); fds[n++] = atoi(d->d_name); } } out: *count = n; closedir(dir); return fds; } void move_process_group(struct ptrace_child *child, pid_t from, pid_t to) { DIR *dir; struct dirent *d; pid_t pid; char *p; int err; if ((dir = opendir("/proc/")) == NULL) return; while ((d = readdir(dir)) != NULL) { if (d->d_name[0] == '.') continue; pid = strtol(d->d_name, &p, 10); if (*p) continue; if (getpgid(pid) == from) { debug("Change pgid for pid %d", pid); err = do_syscall(child, setpgid, pid, to, 0, 0, 0, 0); if (err < 0) error(" failed: %s", strerror(-err)); } } closedir(dir); } int do_setsid(struct ptrace_child *child) { int err = 0; struct ptrace_child dummy; err = do_syscall(child, fork, 0, 0, 0, 0, 0, 0); if (err < 0) return err; debug("Forked a child: %ld", child->forked_pid); err = ptrace_finish_attach(&dummy, child->forked_pid); if (err < 0) goto out_kill; dummy.state = ptrace_after_syscall; memcpy(&dummy.user, &child->user, sizeof child->user); if (ptrace_restore_regs(&dummy)) { err = dummy.error; goto out_kill; } err = do_syscall(&dummy, setpgid, 0, 0, 0, 0, 0, 0); if (err < 0) { error("Failed to setpgid: %s", strerror(-err)); goto out_kill; } move_process_group(child, child->pid, dummy.pid); err = do_syscall(child, setsid, 0, 0, 0, 0, 0, 0); if (err < 0) { error("Failed to setsid: %s", strerror(-err)); move_process_group(child, dummy.pid, child->pid); goto out_kill; } debug("Did setsid()"); out_kill: kill(dummy.pid, SIGKILL); ptrace_detach_child(&dummy); ptrace_wait(&dummy); do_syscall(child, wait4, dummy.pid, 0, WNOHANG, 0, 0, 0); return err; } int ignore_hup(struct ptrace_child *child, unsigned long scratch_page) { int err; if (ptrace_syscall_numbers(child)->nr_signal != -1) { err = do_syscall(child, signal, SIGHUP, (unsigned long)SIG_IGN, 0, 0, 0, 0); } else { struct sigaction act = { .sa_handler = SIG_IGN, }; err = ptrace_memcpy_to_child(child, scratch_page, &act, sizeof act); if (err < 0) return err; err = do_syscall(child, rt_sigaction, SIGHUP, scratch_page, 0, 8, 0, 0); } return err; } /* * Wait for the specific pid to enter state 'T', or stopped. We have to pull the * /proc file rather than attaching with ptrace() and doing a wait() because * half the point of this exercise is for the process's real parent (the shell) * to see the TSTP. * * In case the process is masking or ignoring SIGTSTP, we time out after a * second and continue with the attach -- it'll still work mostly right, you * just won't get the old shell back. */ void wait_for_stop(pid_t pid, int fd) { struct timeval start, now; struct timespec sleep; struct proc_stat st; gettimeofday(&start, NULL); while (1) { gettimeofday(&now, NULL); if ((now.tv_sec > start.tv_sec && now.tv_usec > start.tv_usec) || (now.tv_sec - start.tv_sec > 1)) { error("Timed out waiting for child stop."); break; } /* * If anything goes wrong reading or parsing the stat node, just give * up. */ if (parse_proc_stat(fd, &st)) break; if (st.state == 'T') break; sleep.tv_sec = 0; sleep.tv_nsec = 10000000; nanosleep(&sleep, NULL); } } int copy_tty_state(pid_t pid, const char *pty) { char buf[PATH_MAX]; int fd, err = EINVAL; struct termios tio; int i; for (i = 0; i < 3 && err; i++) { err = 0; snprintf(buf, sizeof buf, "/proc/%d/fd/%d", pid, i); if ((fd = open(buf, O_RDONLY)) < 0) { err = -fd; continue; } if (!isatty(fd)) { err = ENOTTY; goto retry; } if (tcgetattr(fd, &tio) < 0) { err = -errno; } retry: close(fd); } if (err) return err; if ((fd = open(pty, O_RDONLY)) < 0) return -errno; if (tcsetattr(fd, TCSANOW, &tio) < 0) err = errno; close(fd); return -err; } int check_pgroup(pid_t target) { pid_t pg; DIR *dir; struct dirent *d; pid_t pid; char *p; int err = 0; struct proc_stat pid_stat; debug("Checking for problematic process group members..."); pg = getpgid(target); if (pg < 0) { error("Unable to get pgid (does process %d exist?)", (int)target); return pg; } if ((dir = opendir("/proc/")) == NULL) return errno; while ((d = readdir(dir)) != NULL) { if (d->d_name[0] == '.') continue; pid = strtol(d->d_name, &p, 10); if (*p) continue; if (pid == target) continue; if (getpgid(pid) == pg) { /* * We are actually being somewhat overly-conservative here * -- if pid is a child of target, and has not yet called * execve(), reptyr's setpgid() strategy may suffice. That * is a fairly rare case, and annoying to check for, so * for now let's just bail out. */ if ((err = read_proc_stat(pid, &pid_stat))) { memcpy(pid_stat.comm, "???", 4); } error("Process %d (%.*s) shares %d's process group. Unable to attach.\n" "(This most commonly means that %d has a suprocesses).", (int)pid, TASK_COMM_LENGTH, pid_stat.comm, (int)target, (int)target); err = EINVAL; goto out; } } out: closedir(dir); return err; } int attach_child(pid_t pid, const char *pty, int force_stdio) { struct ptrace_child child; unsigned long scratch_page = -1; int *child_tty_fds = NULL, n_fds, child_fd, statfd; int i; int err = 0; long page_size = sysconf(_SC_PAGE_SIZE); char stat_path[PATH_MAX]; long mmap_syscall; if ((err = check_pgroup(pid))) { return err; } if ((err = copy_tty_state(pid, pty))) { if (err == ENOTTY && !force_stdio) { error("Target is not connected to a terminal.\n" " Use -s to force attaching anyways."); return err; } } snprintf(stat_path, sizeof stat_path, "/proc/%d/stat", pid); statfd = open(stat_path, O_RDONLY); if (statfd < 0) { error("Unable to open %s: %s", stat_path, strerror(errno)); return -statfd; } kill(pid, SIGTSTP); wait_for_stop(pid, statfd); if (ptrace_attach_child(&child, pid)) { err = child.error; goto out_cont; } if (ptrace_advance_to_state(&child, ptrace_at_syscall)) { err = child.error; goto out_detach; } if (ptrace_save_regs(&child)) { err = child.error; goto out_detach; } mmap_syscall = ptrace_syscall_numbers(&child)->nr_mmap2; if (mmap_syscall == -1) mmap_syscall = ptrace_syscall_numbers(&child)->nr_mmap; scratch_page = ptrace_remote_syscall(&child, mmap_syscall, 0, page_size, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, 0, 0); if (scratch_page > (unsigned long)-1000) { err = -(signed long)scratch_page; goto out_unmap; } debug("Allocated scratch page: %lx", scratch_page); if (force_stdio) { child_tty_fds = malloc(3 * sizeof(int)); if (!child_tty_fds) { err = ENOMEM; goto out_unmap; } n_fds = 3; child_tty_fds[0] = 0; child_tty_fds[1] = 1; child_tty_fds[2] = 2; } else { child_tty_fds = get_child_tty_fds(&child, statfd, &n_fds); if (!child_tty_fds) { err = child.error; goto out_unmap; } } if (ptrace_memcpy_to_child(&child, scratch_page, pty, strlen(pty)+1)) { err = child.error; error("Unable to memcpy the pty path to child."); goto out_free_fds; } child_fd = do_syscall(&child, open, scratch_page, O_RDWR|O_NOCTTY, 0, 0, 0, 0); if (child_fd < 0) { err = child_fd; error("Unable to open the tty in the child."); goto out_free_fds; } debug("Opened the new tty in the child: %d", child_fd); err = ignore_hup(&child, scratch_page); if (err < 0) goto out_close; err = do_syscall(&child, getsid, 0, 0, 0, 0, 0, 0); if (err != child.pid) { debug("Target is not a session leader, attempting to setsid."); err = do_setsid(&child); } else { do_syscall(&child, ioctl, child_tty_fds[0], TIOCNOTTY, 0, 0, 0, 0); } if (err < 0) goto out_close; err = do_syscall(&child, ioctl, child_fd, TIOCSCTTY, 0, 0, 0, 0); if (err < 0) { error("Unable to set controlling terminal."); goto out_close; } debug("Set the controlling tty"); for (i = 0; i < n_fds; i++) do_syscall(&child, dup2, child_fd, child_tty_fds[i], 0, 0, 0, 0); err = 0; out_close: do_syscall(&child, close, child_fd, 0, 0, 0, 0, 0); out_free_fds: free(child_tty_fds); out_unmap: do_unmap(&child, scratch_page, page_size); ptrace_restore_regs(&child); out_detach: ptrace_detach_child(&child); if (err == 0) { kill(child.pid, SIGSTOP); wait_for_stop(child.pid, statfd); } kill(child.pid, SIGWINCH); out_cont: kill(child.pid, SIGCONT); close(statfd); return err < 0 ? -err : err; } //@inc]lib/reptyr/attach.c //@inc[lib/reptyr/ptrace.c /* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include "ptrace.h" /* * RHEL 5's kernel supports these flags, but their libc doesn't ship a ptrace.h * that defines them. Define them here, and if our kernel doesn't support them, * we'll find out when PTRACE_SETOPTIONS fails. */ #ifndef PTRACE_O_TRACESYSGOOD #define PTRACE_O_TRACESYSGOOD 0x00000001 #endif #ifndef PTRACE_O_TRACEFORK #define PTRACE_O_TRACEFORK 0x00000002 #endif #ifndef PTRACE_EVENT_FORK #define PTRACE_EVENT_FORK 1 #endif #define min(x, y) ({ \ typeof(x) _min1 = (x); \ typeof(y) _min2 = (y); \ _min1 < _min2 ? _min1 : _min2; }) static long __ptrace_command(struct ptrace_child *child, enum __ptrace_request req, void *, void*); #define ptrace_command(cld, req, ...) _ptrace_command(cld, req, ## __VA_ARGS__, NULL, NULL) #define _ptrace_command(cld, req, addr, data, ...) __ptrace_command((cld), (req), (void*)(addr), (void*)(data)) struct ptrace_personality { size_t syscall_rv; size_t syscall_arg0; size_t syscall_arg1; size_t syscall_arg2; size_t syscall_arg3; size_t syscall_arg4; size_t syscall_arg5; size_t reg_ip; }; static struct ptrace_personality *personality(struct ptrace_child *child); #if defined(__amd64__) //@inc[arch/amd64.h /* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ //@inc[x86_common.h /* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ struct x86_personality { size_t orig_ax; size_t ax; }; struct x86_personality x86_personality[]; static inline struct x86_personality *x86_pers(struct ptrace_child *child) { return &x86_personality[child->personality]; } static inline void arch_fixup_regs(struct ptrace_child *child) { struct x86_personality *x86pers = x86_pers(child); struct ptrace_personality *pers = personality(child); struct user *user = &child->user; #define ptr(user, off) ((unsigned long*)((void*)(user)+(off))) *ptr(user, pers->reg_ip) -= 2; *ptr(user, x86pers->ax) = *ptr(user, x86pers->orig_ax); } static inline int arch_set_syscall(struct ptrace_child *child, unsigned long sysno) { return ptrace_command(child, PTRACE_POKEUSER, x86_pers(child)->orig_ax, sysno); } static inline int arch_save_syscall(struct ptrace_child *child) { child->saved_syscall = *ptr(&child->user, x86_pers(child)->orig_ax); return 0; } static inline int arch_restore_syscall(struct ptrace_child *child) { return 0; } #undef ptr //@inc]x86_common.h #define ARCH_HAVE_MULTIPLE_PERSONALITIES static struct ptrace_personality arch_personality[2] = { { offsetof(struct user, regs.rax), offsetof(struct user, regs.rdi), offsetof(struct user, regs.rsi), offsetof(struct user, regs.rdx), offsetof(struct user, regs.r10), offsetof(struct user, regs.r8), offsetof(struct user, regs.r9), offsetof(struct user, regs.rip), }, { offsetof(struct user, regs.rax), offsetof(struct user, regs.rbx), offsetof(struct user, regs.rcx), offsetof(struct user, regs.rdx), offsetof(struct user, regs.rsi), offsetof(struct user, regs.rdi), offsetof(struct user, regs.rbp), offsetof(struct user, regs.rip), }, }; struct x86_personality x86_personality[2] = { { offsetof(struct user, regs.orig_rax), offsetof(struct user, regs.rax), }, { offsetof(struct user, regs.orig_rax), offsetof(struct user, regs.rax), }, }; struct syscall_numbers arch_syscall_numbers[2] = { //@inc[default-syscalls.h #define SC(name) .nr_##name = __NR_##name { #ifdef __NR_mmap SC(mmap), #else .nr_mmap = -1, #endif #ifdef __NR_mmap2 SC(mmap2), #else .nr_mmap2 = -1, #endif SC(munmap), SC(getsid), SC(setsid), SC(setpgid), SC(fork), SC(wait4), #ifdef __NR_signal SC(signal), #else .nr_signal = -1, #endif SC(rt_sigaction), SC(open), SC(close), SC(ioctl), SC(dup2), }, #undef SC //@inc]default-syscalls.h { /* * These don't seem to be available in any convenient header. We could * include unistd_32.h, but those definitions would conflict with the * standard ones. So, let's just hardcode the values for now. Probably * we should generate this from unistd_32.h during the build process or * soemthing. */ .nr_mmap = 90, .nr_mmap2 = 192, .nr_munmap = 91, .nr_getsid = 147, .nr_setsid = 66, .nr_setpgid = 57, .nr_fork = 2, .nr_wait4 = 114, .nr_signal = 48, .nr_rt_sigaction = 173, .nr_open = 5, .nr_close = 6, .nr_ioctl = 54, .nr_dup2 = 63 } }; int arch_get_personality(struct ptrace_child *child) { unsigned long cs; cs = ptrace_command(child, PTRACE_PEEKUSER, offsetof(struct user, regs.cs)); if (child->error) return -1; if (cs == 0x23) child->personality = 1; return 0; } //@inc]arch/amd64.h #elif defined(__i386__) //@inc[arch/i386.h /* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ //@inc[x86_common.h /* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ struct x86_personality { size_t orig_ax; size_t ax; }; struct x86_personality x86_personality[]; static inline struct x86_personality *x86_pers(struct ptrace_child *child) { return &x86_personality[child->personality]; } static inline void arch_fixup_regs(struct ptrace_child *child) { struct x86_personality *x86pers = x86_pers(child); struct ptrace_personality *pers = personality(child); struct user *user = &child->user; #define ptr(user, off) ((unsigned long*)((void*)(user)+(off))) *ptr(user, pers->reg_ip) -= 2; *ptr(user, x86pers->ax) = *ptr(user, x86pers->orig_ax); } static inline int arch_set_syscall(struct ptrace_child *child, unsigned long sysno) { return ptrace_command(child, PTRACE_POKEUSER, x86_pers(child)->orig_ax, sysno); } static inline int arch_save_syscall(struct ptrace_child *child) { child->saved_syscall = *ptr(&child->user, x86_pers(child)->orig_ax); return 0; } static inline int arch_restore_syscall(struct ptrace_child *child) { return 0; } #undef ptr //@inc]x86_common.h static struct ptrace_personality arch_personality[1] = { { offsetof(struct user, regs.eax), offsetof(struct user, regs.ebx), offsetof(struct user, regs.ecx), offsetof(struct user, regs.edx), offsetof(struct user, regs.esi), offsetof(struct user, regs.edi), offsetof(struct user, regs.ebp), offsetof(struct user, regs.eip), } }; struct x86_personality x86_personality[1] = { { offsetof(struct user, regs.orig_eax), offsetof(struct user, regs.eax), } }; //@inc]arch/i386.h #elif defined(__arm__) //@inc[arch/arm.h /* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ static struct ptrace_personality arch_personality[1] = { { offsetof(struct user, regs.uregs[0]), offsetof(struct user, regs.uregs[0]), offsetof(struct user, regs.uregs[1]), offsetof(struct user, regs.uregs[2]), offsetof(struct user, regs.uregs[3]), offsetof(struct user, regs.uregs[4]), offsetof(struct user, regs.uregs[5]), offsetof(struct user, regs.ARM_pc), } }; static inline void arch_fixup_regs(struct ptrace_child *child) { child->user.regs.ARM_pc -= 4; } static inline int arch_set_syscall(struct ptrace_child *child, unsigned long sysno) { return ptrace_command(child, PTRACE_SET_SYSCALL, 0, sysno); } static inline int arch_save_syscall(struct ptrace_child *child) { unsigned long swi; swi = ptrace_command(child, PTRACE_PEEKTEXT, child->user.regs.ARM_pc); if (child->error) return -1; if (swi == 0xef000000) child->saved_syscall = child->user.regs.uregs[7]; else child->saved_syscall = (swi & 0x000fffff); return 0; } static inline int arch_restore_syscall(struct ptrace_child *child) { return arch_set_syscall(child, child->saved_syscall); } //@inc]arch/arm.h #else #error Unsupported architecture. #endif #ifndef ARCH_HAVE_MULTIPLE_PERSONALITIES int arch_get_personality(struct ptrace_child *child) { return 0; } struct syscall_numbers arch_syscall_numbers[] = { //@inc[arch/default-syscalls.h #define SC(name) .nr_##name = __NR_##name { #ifdef __NR_mmap SC(mmap), #else .nr_mmap = -1, #endif #ifdef __NR_mmap2 SC(mmap2), #else .nr_mmap2 = -1, #endif SC(munmap), SC(getsid), SC(setsid), SC(setpgid), SC(fork), SC(wait4), #ifdef __NR_signal SC(signal), #else .nr_signal = -1, #endif SC(rt_sigaction), SC(open), SC(close), SC(ioctl), SC(dup2), }, #undef SC //@inc]arch/default-syscalls.h }; #endif static struct ptrace_personality *personality(struct ptrace_child *child) { return &arch_personality[child->personality]; } struct syscall_numbers *ptrace_syscall_numbers(struct ptrace_child *child) { return &arch_syscall_numbers[child->personality]; } int ptrace_attach_child(struct ptrace_child *child, pid_t pid) { memset(child, 0, sizeof *child); child->pid = pid; if (ptrace_command(child, PTRACE_ATTACH) < 0) return -1; return ptrace_finish_attach(child, pid); } int ptrace_finish_attach(struct ptrace_child *child, pid_t pid) { memset(child, 0, sizeof *child); child->pid = pid; kill(pid, SIGCONT); if (ptrace_wait(child) < 0) goto detach; if (arch_get_personality(child)) goto detach; if (ptrace_command(child, PTRACE_SETOPTIONS, 0, PTRACE_O_TRACESYSGOOD|PTRACE_O_TRACEFORK) < 0) goto detach; return 0; detach: /* Don't clobber child->error */ ptrace(PTRACE_DETACH, child->pid, 0, 0); return -1; } int ptrace_detach_child(struct ptrace_child *child) { if (ptrace_command(child, PTRACE_DETACH, 0, 0) < 0) return -1; child->state = ptrace_detached; return 0; } int ptrace_wait(struct ptrace_child *child) { if (waitpid(child->pid, &child->status, 0) < 0) { child->error = errno; return -1; } if (WIFEXITED(child->status) || WIFSIGNALED(child->status)) { child->state = ptrace_exited; } else if (WIFSTOPPED(child->status)) { int sig = WSTOPSIG(child->status); if (sig & 0x80) { child->state = (child->state == ptrace_at_syscall) ? ptrace_after_syscall : ptrace_at_syscall; } else { if (sig == SIGTRAP && (((child->status >> 8) & PTRACE_EVENT_FORK) == PTRACE_EVENT_FORK)) ptrace_command(child, PTRACE_GETEVENTMSG, 0, &child->forked_pid); if (child->state != ptrace_at_syscall) child->state = ptrace_stopped; } } else { child->error = EINVAL; return -1; } return 0; } int ptrace_advance_to_state(struct ptrace_child *child, enum child_state desired) { int err; while (child->state != desired) { switch(desired) { case ptrace_after_syscall: case ptrace_at_syscall: if (WIFSTOPPED(child->status) && WSTOPSIG(child->status) == SIGSEGV) { child->error = EAGAIN; return -1; } err = ptrace_command(child, PTRACE_SYSCALL, 0, 0); break; case ptrace_running: return ptrace_command(child, PTRACE_CONT, 0, 0); case ptrace_stopped: err = kill(child->pid, SIGSTOP); if (err < 0) child->error = errno; break; default: child->error = EINVAL; return -1; } if (err < 0) return err; if (ptrace_wait(child) < 0) return -1; } return 0; } int ptrace_save_regs(struct ptrace_child *child) { if (ptrace_advance_to_state(child, ptrace_at_syscall) < 0) return -1; if (ptrace_command(child, PTRACE_GETREGS, 0, &child->user) < 0) return -1; arch_fixup_regs(child); if (arch_save_syscall(child) < 0) return -1; return 0; } int ptrace_restore_regs(struct ptrace_child *child) { int err; err = ptrace_command(child, PTRACE_SETREGS, 0, &child->user); if (err < 0) return err; return arch_restore_syscall(child); } unsigned long ptrace_remote_syscall(struct ptrace_child *child, unsigned long sysno, unsigned long p0, unsigned long p1, unsigned long p2, unsigned long p3, unsigned long p4, unsigned long p5) { unsigned long rv; if (ptrace_advance_to_state(child, ptrace_at_syscall) < 0) return -1; #define setreg(r, v) do { \ if (ptrace_command(child, PTRACE_POKEUSER, \ personality(child)->r, \ (v)) < 0) \ return -1; \ } while (0) if (arch_set_syscall(child, sysno) < 0) return -1; setreg(syscall_arg0, p0); setreg(syscall_arg1, p1); setreg(syscall_arg2, p2); setreg(syscall_arg3, p3); setreg(syscall_arg4, p4); setreg(syscall_arg5, p5); if (ptrace_advance_to_state(child, ptrace_after_syscall) < 0) return -1; rv = ptrace_command(child, PTRACE_PEEKUSER, personality(child)->syscall_rv); if (child->error) return -1; setreg(reg_ip, *(unsigned long*)((void*)&child->user + personality(child)->reg_ip)); #undef setreg return rv; } int ptrace_memcpy_to_child(struct ptrace_child *child, child_addr_t dst, const void *src, size_t n) { unsigned long scratch; while (n >= sizeof(unsigned long)) { if (ptrace_command(child, PTRACE_POKEDATA, dst, *((unsigned long*)src)) < 0) return -1; dst += sizeof(unsigned long); src += sizeof(unsigned long); n -= sizeof(unsigned long); } if (n) { scratch = ptrace_command(child, PTRACE_PEEKDATA, dst); if (child->error) return -1; memcpy(&scratch, src, n); if (ptrace_command(child, PTRACE_POKEDATA, dst, scratch) < 0) return -1; } return 0; } int ptrace_memcpy_from_child(struct ptrace_child *child, void *dst, child_addr_t src, size_t n) { unsigned long scratch; while (n) { scratch = ptrace_command(child, PTRACE_PEEKDATA, src); if (child->error) return -1; memcpy(dst, &scratch, min(n, sizeof(unsigned long))); dst += sizeof(unsigned long); src += sizeof(unsigned long); if (n >= sizeof(unsigned long)) n -= sizeof(unsigned long); else n = 0; } return 0; } static long __ptrace_command(struct ptrace_child *child, enum __ptrace_request req, void *addr, void *data) { long rv; errno = 0; rv = ptrace(req, child->pid, addr, data); child->error = errno; return rv; } #ifdef BUILD_PTRACE_MAIN int main(int argc, char **argv) { struct ptrace_child child; pid_t pid; if (argc < 2) { printf("Usage: %s pid\n", argv[0]); return 1; } pid = atoi(argv[1]); assert(!ptrace_attach_child(&child, pid)); assert(!ptrace_save_regs(&child)); printf("mmap = %lx\n", ptrace_remote_syscall(&child, mmap_syscall, 0, 4096, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, 0, 0)); reset_user_struct(&child.user); assert(!ptrace_restore_regs(&child)); assert(!ptrace_detach_child(&child)); return 0; } #endif //@inc]lib/reptyr/ptrace.c //@inc[lib/reptyr/reptyr.c /* * Copyright (C) 2011 by Nelson Elhage * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include //#include "reptyr.h" #ifndef __linux__ #error reptyr is currently Linux-only. #endif static int verbose = 0; void _debug(const char *pfx, const char *msg, va_list ap) { if (pfx) fprintf(stderr, "%s", pfx); vfprintf(stderr, msg, ap); fprintf(stderr, "\n"); } void die(const char *msg, ...) { va_list ap; va_start(ap, msg); _debug("[!] ", msg, ap); va_end(ap); exit(1); } void debug(const char *msg, ...) { va_list ap; if (!verbose) return; va_start(ap, msg); _debug("[+] ", msg, ap); va_end(ap); } void error(const char *msg, ...) { va_list ap; va_start(ap, msg); _debug("[-] ", msg, ap); va_end(ap); } void setup_raw(struct termios *save) { struct termios set; if (tcgetattr(0, save) < 0) die("Unable to read terminal attributes: %m"); set = *save; cfmakeraw(&set); if (tcsetattr(0, TCSANOW, &set) < 0) die("Unable to set terminal attributes: %m"); } void resize_pty(int pty) { struct winsize sz; if (ioctl(0, TIOCGWINSZ, &sz) < 0) return; ioctl(pty, TIOCSWINSZ, &sz); } int writeall(int fd, const void *buf, ssize_t count) { ssize_t rv; while (count > 0) { rv = write(fd, buf, count); if (rv < 0) { if (errno == EINTR) continue; return rv; } count -= rv; buf += rv; } return 0; } volatile sig_atomic_t winch_happened = 0; void do_winch(int signal) { winch_happened = 1; } void do_proxy(int pty) { char buf[4096]; ssize_t count; fd_set set; while (1) { if (winch_happened) { winch_happened = 0; /* * FIXME: If a signal comes in after this point but before * select(), the resize will be delayed until we get more * input. signalfd() is probably the cleanest solution. */ resize_pty(pty); } FD_ZERO(&set); FD_SET(0, &set); FD_SET(pty, &set); if (select(pty+1, &set, NULL, NULL, NULL) < 0) { if (errno == EINTR) continue; fprintf(stderr, "select: %m"); return; } if (FD_ISSET(0, &set)) { count = read(0, buf, sizeof buf); if (count < 0) return; writeall(pty, buf, count); } if (FD_ISSET(pty, &set)) { count = read(pty, buf, sizeof buf); if (count < 0) return; writeall(1, buf, count); } } } void usage(char *me, FILE* out) { if (out == NULL) out = stderr; fprintf(out, "Usage: %s [-s] PID\n", me); fprintf(out, " %s -l|-L [COMMAND [ARGS]]\n", me); fprintf(out, " -l Create a new pty pair and print the name of the slave.\n"); fprintf(out, " if there are command-line arguments after -l\n"); fprintf(out, " they are executed with REPTYR_PTY set to path of pty.\n"); fprintf(out, " -L Like '-l', but also redirect the child's stdio to the slave.\n"); fprintf(out, " -s Attach fds 0-2 on the target, even if it is not attached to a tty.\n"); fprintf(out, " -h Print this help message and exit.\n"); fprintf(out, " -v Print the version number and exit.\n"); fprintf(out, " -V Print verbose debug output.\n"); } void check_yama_ptrace_scope(void) { int fd = open("/proc/sys/kernel/yama/ptrace_scope", O_RDONLY); if (fd >= 0) { char buf[256]; int n; n = read(fd, buf, sizeof buf); close(fd); if (n > 0) { if (!atoi(buf)) { return; } } } else if (errno == ENOENT) return; fprintf(stderr, "The kernel denied permission while attaching. If your uid matches\n"); fprintf(stderr, "the target's, check the value of /proc/sys/kernel/yama/ptrace_scope.\n"); fprintf(stderr, "For more information, see /etc/sysctl.d/10-ptrace.conf\n"); } int main(int argc, char **argv) { struct termios saved_termios; struct sigaction act; int pty; int arg = 1; int do_attach = 1; int force_stdio = 0; int unattached_script_redirection = 0; if (argc < 2) { usage(argv[0], NULL); return 2; } else if (argc == 2 && !strcmp(argv[1], "--help")) { usage(argv[0], stdout); return 0; } if (argv[arg][0] == '-') { switch(argv[arg][1]) { case 'h': usage(argv[0], stdout); return 0; case 'l': do_attach = 0; break; case 'L': do_attach = 0; unattached_script_redirection = 1; break; case 's': arg++; force_stdio = 1; break; case 'v': printf("This is reptyr version %s.\n", REPTYR_VERSION); printf(" by Nelson Elhage \n"); printf("http://github.com/nelhage/reptyr/\n"); return 0; case 'V': arg++; verbose = 1; break; default: usage(argv[0], NULL); return 1; } } if (do_attach && arg >= argc) { fprintf(stderr, "%s: No pid specified to attach\n", argv[0]); usage(argv[0], NULL); return 1; } if ((pty = open("/dev/ptmx", O_RDWR|O_NOCTTY)) < 0) die("Unable to open /dev/ptmx: %m"); if (unlockpt(pty) < 0) die("Unable to unlockpt: %m"); if (grantpt(pty) < 0) die("Unable to grantpt: %m"); if (do_attach) { pid_t child = atoi(argv[arg]); int err; if ((err = attach_child(child, ptsname(pty), force_stdio))) { fprintf(stderr, "Unable to attach to pid %d: %s\n", child, strerror(err)); if (err == EPERM) { check_yama_ptrace_scope(); } return 1; } } else { printf("Opened a new pty: %s\n", ptsname(pty)); fflush(stdout); if (argc > 2) { if(!fork()) { setenv("REPTYR_PTY", ptsname(pty), 1); if (unattached_script_redirection) { int f; setpgid(0, getppid()); setsid(); f = open(ptsname(pty), O_RDONLY, 0); dup2(f, 0); close(f); f = open(ptsname(pty), O_WRONLY, 0); dup2(f, 1); dup2(f,2); close(f); } close(pty); execvp(argv[2], argv+2); exit(1); } } } setup_raw(&saved_termios); memset(&act, 0, sizeof act); act.sa_handler = do_winch; act.sa_flags = 0; sigaction(SIGWINCH, &act, NULL); resize_pty(pty); do_proxy(pty); tcsetattr(0, TCSANOW, &saved_termios); return 0; } //@inc]lib/reptyr/reptyr.c