Repository: DavidBuchanan314/TARDIS Branch: master Commit: 5560d6497e2e Files: 6 Total size: 10.2 KB Directory structure: gitextract_75bsxhu7/ ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── novdso.c └── tardis.c ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.so tardis ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2017 David Buchanan 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. ================================================ FILE: Makefile ================================================ all: tardis novdso.so 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 clean: rm -f tardis novdso.so ================================================ FILE: README.md ================================================ # TARDIS Trace And Rewrite Delays In Syscalls: Hooking time-related Linux syscalls to warp a process's perspective of time. This code is rather buggy, mainly due to my lack of understanding of the ptrace API. You probably shouldn't use it for anything serious, although it could be useful for testing/debugging certain applications. ## Things to try: ``` $ ./tardis 10000 10000 xclock $ ./tardis 1 3 glxgears $ ./tardis 1 -1 glxgears $ ./tardis 10 10 firefox $ ./tardis 10 10 /bin/sh ``` ![xclock demo](https://i.imgur.com/UnFYuLs.gif) ## Notes: - 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. - 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. - There are many more syscalls that I still need to handle. Currently handled syscalls: - `nanosleep` - `clock_nanosleep` - `select` - `poll` - `gettimeofday` - `clock_gettime` - `time` ================================================ FILE: novdso.c ================================================ #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); } ================================================ FILE: tardis.c ================================================ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #define MICROSECONDS 1000000 #define NANOSECONDS (MICROSECONDS*1000) #define NUM_SYSCALLS 512 // this is higher than the real number, but shouldn't matter #ifndef PID_MAX #define PID_MAX 32768 // XXX: assumption #endif #define NUM_CLKIDS 16 // XXX: incorrect, but "works" anyway double starttimes[NUM_CLKIDS], delayfactor, timefactor; bool leavesys[PID_MAX]; void (*before_handlers[NUM_SYSCALLS])(pid_t, struct user_regs_struct *); void (*after_handlers[NUM_SYSCALLS])(pid_t, struct user_regs_struct *); int is64bit(pid_t pid) { struct user_regs_struct x64regs; struct iovec iov = { .iov_base = &x64regs, .iov_len = sizeof(x64regs) }; ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov); 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) { time = starttime + (time - starttime) * factor; } else { time *= factor; } ts->tv_sec = time; ts->tv_nsec = fmod(time, 1) * NANOSECONDS; } void scale_timeval(struct timeval * tv, double factor, double starttime) { double time = tv->tv_sec + (double)tv->tv_usec / MICROSECONDS; if (starttime != 0) { time = starttime + (time - starttime) * factor; } else { time *= factor; } tv->tv_sec = time; tv->tv_usec = fmod(time, 1) * MICROSECONDS; } /* pre-syscall handlers */ void before_nanosleep(pid_t pid, struct user_regs_struct * uregs) { struct timespec ts; read_block(pid, &ts, (void *)uregs->rdi, sizeof(struct timespec)); scale_timespec(&ts, 1.0/delayfactor, 0); write_block(pid, &ts, (void *)uregs->rdi, sizeof(struct timespec)); } void before_poll(pid_t pid, struct user_regs_struct * uregs) { int timeout = uregs->rdx & 0xFFFFFFFF; // isolate edx if (timeout > 0) { uregs->rdx = timeout / delayfactor; // not sure if this behaves the way I want } ptrace(PTRACE_SETREGS, pid, 0, uregs); } void before_select(pid_t pid, struct user_regs_struct * uregs) { if (uregs->r8 != 0) { struct timeval tv; read_block(pid, &tv, (void *)uregs->r8, sizeof(struct timeval)); scale_timeval(&tv, 1.0/delayfactor, 0); write_block(pid, &tv, (void *)uregs->r8, sizeof(struct timeval)); } } void before_clock_nanosleep(pid_t pid, struct user_regs_struct * uregs) { struct timespec rqtp; read_block(pid, &rqtp, (void *)uregs->rdx, sizeof(struct timespec)); scale_timespec(&rqtp, 1.0/delayfactor, 0); write_block(pid, &rqtp, (void *)uregs->rdx, sizeof(struct timespec)); } /* post-syscall handlers */ void after_gettimeofday(pid_t pid, struct user_regs_struct * uregs) { struct timeval tv; read_block(pid, &tv, (void *)uregs->rdi, sizeof(struct timeval)); scale_timeval(&tv, timefactor, starttimes[CLOCK_REALTIME]); write_block(pid, &tv, (void *)uregs->rdi, sizeof(struct timeval)); } void after_clock_gettime(pid_t pid, struct user_regs_struct * uregs) { struct timespec ts; read_block(pid, &ts, (void *)uregs->rsi, sizeof(struct timespec)); scale_timespec(&ts, timefactor, starttimes[uregs->rdi]); // FIXME check bounds write_block(pid, &ts, (void *)uregs->rsi, sizeof(struct timespec)); } void after_time(pid_t pid, struct user_regs_struct * uregs) { uregs->rdi = starttimes[CLOCK_REALTIME] + (uregs->rdi - starttimes[CLOCK_REALTIME]) * timefactor; ptrace(PTRACE_SETREGS, pid, 0, uregs); } void after_clock_nanosleep(pid_t pid, struct user_regs_struct * uregs) { struct timespec rmtp; read_block(pid, &rmtp, (void *)uregs->rcx, sizeof(struct timespec)); scale_timespec(&rmtp, 1.0/delayfactor, 0); write_block(pid, &rmtp, (void *)uregs->rcx, sizeof(struct timespec)); } int main(int argc, char *argv[], char *envp[]) { if (argc < 3) { printf("USAGE: %s DELAY_FACTOR TIME_FACTOR COMMAND [ARGS]...\n", argv[0]); exit(EXIT_FAILURE); } delayfactor = strtod(argv[1], NULL); timefactor = strtod(argv[2], NULL); before_handlers[SYS_nanosleep] = before_nanosleep; before_handlers[SYS_poll] = before_poll; before_handlers[SYS_select] = before_select; before_handlers[SYS_clock_nanosleep] = before_clock_nanosleep; after_handlers[SYS_gettimeofday] = after_gettimeofday; after_handlers[SYS_clock_gettime] = after_clock_gettime; after_handlers[SYS_time] = after_time; after_handlers[SYS_clock_nanosleep] = after_clock_nanosleep; struct timespec sts; for (clockid_t id = 0; id < NUM_CLKIDS; id++) { clock_gettime(id, &sts); // sometimes id will be invalid, but it shouldn't matter starttimes[id] = sts.tv_sec + (double)sts.tv_nsec / NANOSECONDS; } pid_t child = fork(); if(child == 0) { /* child */ envp[0] = "LD_PRELOAD=./novdso.so"; // FIXME: Do something more sensible kill(getpid(), SIGSTOP); execvpe(argv[3], &argv[3], envp); perror("execvpe"); // execvpe only returns on error exit(-1); } #ifdef DEBUG 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 if (is64bit(child)) { #ifdef DEBUG fprintf(stderr, "Child is 64-bit\n"); #endif } else { fprintf(stderr, "ERROR: 32-bit processes are currently unsupported\n"); exit(-1); } for (;;) { // TODO: Understand ptrace, simplify code structure struct user_regs_struct uregs; int status; pid_t pid = waitpid(-1, &status, 0); if (WIFEXITED(status)) { if (pid == child) { exit(WEXITSTATUS(status)); } else { continue; } } if (WIFSTOPPED(status) && WSTOPSIG(status) != SIGTRAP && WSTOPSIG(status) != SIGSTOP) { if (WSTOPSIG(status) & 0x80) { // handle syscall } else { ptrace(PTRACE_SYSCALL, pid, 0, WSTOPSIG(status)); continue; } } else { ptrace(PTRACE_SYSCALL, pid, 0, 0); continue; } ptrace(PTRACE_GETREGS, pid, 0, &uregs); if (!leavesys[pid]) { #ifdef DEBUG fprintf(stderr, "[pid %d] syscall(%llu)\t0x%016llX 0x%016llX 0x%016llX = ...\n", pid, uregs.orig_rax, uregs.rdi, uregs.rsi, uregs.rdx); #endif if (uregs.orig_rax < NUM_SYSCALLS && before_handlers[uregs.orig_rax] != NULL) { before_handlers[uregs.orig_rax](pid, &uregs); } } else { #ifdef DEBUG fprintf(stderr, "... 0x%llX\n", uregs.rax); #endif if (uregs.orig_rax < NUM_SYSCALLS && after_handlers[uregs.orig_rax] != NULL) { after_handlers[uregs.orig_rax](pid, &uregs); } } leavesys[pid] ^= true; ptrace(PTRACE_SYSCALL, pid, 0, 0); } }