diff --git a/Makefile b/Makefile index 82bd711..aeb1a51 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,8 @@ -all: tardis novdso.so +all: tardis -tardis: tardis.c - gcc tardis.c -o tardis -lm -Wall -Ofast -std=c99 -DPID_MAX=$(shell cat /proc/sys/kernel/pid_max) -novdso.so: novdso.c - gcc -std=c99 -Wall -fPIC -shared -o novdso.so novdso.c +tardis: tardis.c + gcc tardis.c ptrace.c -o tardis -lm -Wall -Ofast -std=c99 -DPID_MAX=$(shell cat /proc/sys/kernel/pid_max) clean: rm -f tardis novdso.so diff --git a/README.md b/README.md index 94440b5..7182c28 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,7 @@ $ ./tardis 10 10 /bin/sh - Currently only x86_64 Linux is supported. It should be possible to port to i386 with fairly minimal effort. -- I used `PTRACE_SEIZE`, which only exists since kernel version 3.4. - -- `novdso.so` is preloaded to prevent libc from using vDSO - otherwise `ptrace(PTRACE_SYSCALL, ...)` -wouldn't work for those syscalls (Take a look at `man vdso` for more information). You might need to -modify the `LD_PRELOAD` value to be an absolute path for some programs/environments, I only made it -relative for simplicity. +- In order to prevent vDSO functions from being used instead of syscalls, the auxiliary vector is patched to ignore vDSO pointer entry. - Certain simple programs, like `glxgears`, don't mind being run with time flowing in reverse! Most programs don't however, and of course there's no way to have a negative delay. diff --git a/novdso.c b/novdso.c deleted file mode 100644 index 66eb4dc..0000000 --- a/novdso.c +++ /dev/null @@ -1,21 +0,0 @@ -#define _GNU_SOURCE -#include -#include - -// this is a simple shim to disable vDSO use in libc - -int clock_gettime(void * clk_id, void * tp) { - return syscall(SYS_clock_gettime, clk_id, tp); -} - -int gettimeofday(void * tv, void * tz) { - return syscall(SYS_gettimeofday, tv, tz); -} - -int time(void * tloc) { - return syscall(SYS_time, tloc); -} - -int nanosleep(const struct timespec *req, struct timespec *rem) { - return syscall(SYS_nanosleep,req,rem); -} diff --git a/ptrace.c b/ptrace.c new file mode 100644 index 0000000..1653804 --- /dev/null +++ b/ptrace.c @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include +#include +#include + +void read_block(pid_t pid, void *dst, void *src, size_t len) { + for (size_t i = 0; i < len; i += sizeof(void *)) { + *(void **)(dst + i) = (void *)ptrace(PTRACE_PEEKDATA, pid, src + i, NULL); +#ifdef DEBUG_READ + fprintf(stderr, "read_block: read 0x%lx at 0x%lx\n", *(long *)(dst + i), (long)(src + i)); +#endif + } +} + +void write_block(pid_t pid, void *src, void *dst, size_t len) { + for (size_t i = 0; i < len; i += sizeof(void *)) { + ptrace(PTRACE_POKEDATA, pid, dst + i, *(void **)(src + i)); +#ifdef DEBUG_WRITE + fprintf(stderr, "write_block: write 0x%lx at 0x%lx\n", *(long *)(src + i), (long)(dst + i)); +#endif + } +} +unsigned long get_stack_addr(pid_t pid) { + struct user_regs_struct uregs; + ptrace(PTRACE_GETREGS, pid, 0, &uregs); + return uregs.rsp; +} + +unsigned long get_auxv_addr(pid_t pid) { + unsigned long addr = get_stack_addr(pid); +#ifdef DEBUG + fprintf(stderr, "get_auxv_addr: stack addr = 0x%lx\n", addr); +#endif + + long argc; + read_block(pid, &argc, (void *)addr, sizeof(argc)); +#ifdef DEBUG + fprintf(stderr, "get_auxv_addr: argc = %ld\n", argc); +#endif + + // skip argc & argv[] + addr += sizeof(long); + addr += (argc + 1) * sizeof(long); + + // skip envp[] + long data; + read_block(pid, &data, (void *)addr, sizeof(data)); + while (data != 0) { + read_block(pid, &data, (void *)addr, sizeof(data)); + addr += sizeof(long); + } + + return addr; +} + +int disable_vdso(pid_t pid) { + long cur = get_auxv_addr(pid); + if (cur == -1) { + fprintf(stderr, "disable_vdso: error: auxv address not found\n"); + return -1; + } + + Elf64_auxv_t auxv; + read_block(pid, &auxv, (void *)cur, sizeof(auxv.a_type)); + + while (auxv.a_type != AT_NULL) { + if (auxv.a_type == AT_SYSINFO_EHDR) { +#ifdef DEBUG + fprintf(stderr, "disable_vdso: found AT_SYSINFO_EHDR tag at %lx. setting type = AT_IGNORE\n", cur); +#endif + + auxv.a_type = AT_IGNORE; + write_block(pid, &auxv, (void *)cur, sizeof(auxv.a_type)); + return 0; + } + + cur += sizeof(auxv); + read_block(pid, &auxv, (void *)cur, sizeof(auxv.a_type)); + } + + fprintf(stderr, "disable_vdso: error: AT_SYSINFO_EHDR tag not found\n"); + return -1; +} \ No newline at end of file diff --git a/ptrace.h b/ptrace.h new file mode 100644 index 0000000..81853e4 --- /dev/null +++ b/ptrace.h @@ -0,0 +1,9 @@ +#include +#include + +unsigned long get_stack_addr(pid_t pid); +unsigned long get_auxv_addr(pid_t pid); +int disable_vdso(pid_t pid); + +void read_block(pid_t pid, void* src, void* dst, size_t len); +void write_block(pid_t pid, void* src, void* dst, size_t len); diff --git a/tardis.c b/tardis.c index 75b2219..e841db9 100644 --- a/tardis.c +++ b/tardis.c @@ -14,6 +14,8 @@ #include #include +#include "ptrace.h" + #define MICROSECONDS 1000000 #define NANOSECONDS (MICROSECONDS*1000) #define NUM_SYSCALLS 512 // this is higher than the real number, but shouldn't matter @@ -38,18 +40,6 @@ int is64bit(pid_t pid) { return iov.iov_len == sizeof(x64regs); } -void read_block(pid_t pid, void * dst, void * src, size_t len) { - for (size_t i = 0; i < len; i += sizeof(void *)) { - *(void **)(dst + i) = (void *)ptrace(PTRACE_PEEKDATA, pid, src + i, NULL); // XXX - } -} - -void write_block(pid_t pid, void * src, void * dst, size_t len) { - for (size_t i = 0; i < len; i += sizeof(void *)) { - ptrace(PTRACE_POKEDATA, pid, dst + i, *(void **)(src + i)); // XXX - } -} - void scale_timespec(struct timespec * ts, double factor, double starttime) { double time = ts->tv_sec + (double)ts->tv_nsec / NANOSECONDS; if (starttime != 0) { @@ -159,26 +149,47 @@ int main(int argc, char *argv[], char *envp[]) { starttimes[id] = sts.tv_sec + (double)sts.tv_nsec / NANOSECONDS; } - pid_t child = fork(); - - if(child == 0) { + pid_t child = vfork(); + + if (child == 0) { /* child */ - envp[0] = "LD_PRELOAD=./novdso.so"; // FIXME: Do something more sensible - kill(getpid(), SIGSTOP); + ptrace(PTRACE_TRACEME, 0, NULL, NULL); execvpe(argv[3], &argv[3], envp); - perror("execvpe"); // execvpe only returns on error - exit(-1); + perror("execvpe"); // execvpe only returns on error + exit(EXIT_FAILURE); } #ifdef DEBUG - fprintf(stderr, "Child spawned with PID %d\n", child); + fprintf(stderr, "Child spawned with PID %d\n", child); #endif - ptrace(PTRACE_SEIZE, child, 0, PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXEC | PTRACE_O_EXITKILL | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE); - wait(NULL); // wait for SIGSTOP to happen - ptrace(PTRACE_SYSCALL, child, 0, 0); // continue execution - + + // wait for SIGSTOP to happen + if (wait(NULL) == -1) { + perror("wait"); + exit(EXIT_FAILURE); + } + + // set ptrace options + int options = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXEC | PTRACE_O_EXITKILL | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE; + + if (ptrace(PTRACE_SETOPTIONS, child, 0, options) == -1) { + perror("ptrace setoptions"); + exit(EXIT_FAILURE); + } + + // vDSO will interfere with syscall hooking, so we need to disable it + if (disable_vdso(child) == -1) { + exit(EXIT_FAILURE); + } + + // continue execution + if (ptrace(PTRACE_SYSCALL, child, 0, 0) == -1) { + perror("ptrace syscall"); + exit(EXIT_FAILURE); + } + if (is64bit(child)) { #ifdef DEBUG - fprintf(stderr, "Child is 64-bit\n"); + fprintf(stderr, "Child is 64-bit\n"); #endif } else { fprintf(stderr, "ERROR: 32-bit processes are currently unsupported\n");