├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── novdso.c └── tardis.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | tardis 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 David Buchanan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: tardis novdso.so 2 | 3 | tardis: tardis.c 4 | gcc tardis.c -o tardis -lm -Wall -Ofast -std=c99 -DPID_MAX=$(shell cat /proc/sys/kernel/pid_max) 5 | 6 | novdso.so: novdso.c 7 | gcc -std=c99 -Wall -fPIC -shared -o novdso.so novdso.c 8 | 9 | clean: 10 | rm -f tardis novdso.so 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TARDIS 2 | 3 | Trace And Rewrite Delays In Syscalls: Hooking time-related Linux syscalls to warp a process's perspective of time. 4 | 5 | This code is rather buggy, mainly due to my lack of understanding of the ptrace API. 6 | You probably shouldn't use it for anything serious, although it could be useful for 7 | testing/debugging certain applications. 8 | 9 | ## Things to try: 10 | 11 | ``` 12 | $ ./tardis 10000 10000 xclock 13 | $ ./tardis 1 3 glxgears 14 | $ ./tardis 1 -1 glxgears 15 | $ ./tardis 10 10 firefox 16 | $ ./tardis 10 10 /bin/sh 17 | ``` 18 | 19 | ![xclock demo](https://i.imgur.com/UnFYuLs.gif) 20 | 21 | ## Notes: 22 | 23 | - Currently only x86_64 Linux is supported. It should be possible to port to i386 with fairly minimal effort. 24 | 25 | - I used `PTRACE_SEIZE`, which only exists since kernel version 3.4. 26 | 27 | - `novdso.so` is preloaded to prevent libc from using vDSO - otherwise `ptrace(PTRACE_SYSCALL, ...)` 28 | wouldn't work for those syscalls (Take a look at `man vdso` for more information). You might need to 29 | modify the `LD_PRELOAD` value to be an absolute path for some programs/environments, I only made it 30 | relative for simplicity. 31 | 32 | - 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. 33 | 34 | - There are many more syscalls that I still need to handle. 35 | 36 | Currently handled syscalls: 37 | 38 | - `nanosleep` 39 | - `clock_nanosleep` 40 | - `select` 41 | - `poll` 42 | - `gettimeofday` 43 | - `clock_gettime` 44 | - `time` 45 | -------------------------------------------------------------------------------- /novdso.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | 5 | // this is a simple shim to disable vDSO use in libc 6 | 7 | int clock_gettime(void * clk_id, void * tp) { 8 | return syscall(SYS_clock_gettime, clk_id, tp); 9 | } 10 | 11 | int gettimeofday(void * tv, void * tz) { 12 | return syscall(SYS_gettimeofday, tv, tz); 13 | } 14 | 15 | int time(void * tloc) { 16 | return syscall(SYS_time, tloc); 17 | } 18 | 19 | int nanosleep(const struct timespec *req, struct timespec *rem) { 20 | return syscall(SYS_nanosleep,req,rem); 21 | } 22 | -------------------------------------------------------------------------------- /tardis.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #define MICROSECONDS 1000000 18 | #define NANOSECONDS (MICROSECONDS*1000) 19 | #define NUM_SYSCALLS 512 // this is higher than the real number, but shouldn't matter 20 | #ifndef PID_MAX 21 | #define PID_MAX 32768 // XXX: assumption 22 | #endif 23 | #define NUM_CLKIDS 16 // XXX: incorrect, but "works" anyway 24 | 25 | double starttimes[NUM_CLKIDS], delayfactor, timefactor; 26 | bool leavesys[PID_MAX]; 27 | void (*before_handlers[NUM_SYSCALLS])(pid_t, struct user_regs_struct *); 28 | void (*after_handlers[NUM_SYSCALLS])(pid_t, struct user_regs_struct *); 29 | 30 | int is64bit(pid_t pid) { 31 | struct user_regs_struct x64regs; 32 | struct iovec iov = { 33 | .iov_base = &x64regs, 34 | .iov_len = sizeof(x64regs) 35 | }; 36 | 37 | ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov); 38 | return iov.iov_len == sizeof(x64regs); 39 | } 40 | 41 | void read_block(pid_t pid, void * dst, void * src, size_t len) { 42 | for (size_t i = 0; i < len; i += sizeof(void *)) { 43 | *(void **)(dst + i) = (void *)ptrace(PTRACE_PEEKDATA, pid, src + i, NULL); // XXX 44 | } 45 | } 46 | 47 | void write_block(pid_t pid, void * src, void * dst, size_t len) { 48 | for (size_t i = 0; i < len; i += sizeof(void *)) { 49 | ptrace(PTRACE_POKEDATA, pid, dst + i, *(void **)(src + i)); // XXX 50 | } 51 | } 52 | 53 | void scale_timespec(struct timespec * ts, double factor, double starttime) { 54 | double time = ts->tv_sec + (double)ts->tv_nsec / NANOSECONDS; 55 | if (starttime != 0) { 56 | time = starttime + (time - starttime) * factor; 57 | } else { 58 | time *= factor; 59 | } 60 | ts->tv_sec = time; 61 | ts->tv_nsec = fmod(time, 1) * NANOSECONDS; 62 | } 63 | 64 | void scale_timeval(struct timeval * tv, double factor, double starttime) { 65 | double time = tv->tv_sec + (double)tv->tv_usec / MICROSECONDS; 66 | if (starttime != 0) { 67 | time = starttime + (time - starttime) * factor; 68 | } else { 69 | time *= factor; 70 | } 71 | tv->tv_sec = time; 72 | tv->tv_usec = fmod(time, 1) * MICROSECONDS; 73 | } 74 | 75 | /* pre-syscall handlers */ 76 | 77 | void before_nanosleep(pid_t pid, struct user_regs_struct * uregs) { 78 | struct timespec ts; 79 | read_block(pid, &ts, (void *)uregs->rdi, sizeof(struct timespec)); 80 | scale_timespec(&ts, 1.0/delayfactor, 0); 81 | write_block(pid, &ts, (void *)uregs->rdi, sizeof(struct timespec)); 82 | } 83 | 84 | void before_poll(pid_t pid, struct user_regs_struct * uregs) { 85 | int timeout = uregs->rdx & 0xFFFFFFFF; // isolate edx 86 | if (timeout > 0) { 87 | uregs->rdx = timeout / delayfactor; // not sure if this behaves the way I want 88 | } 89 | ptrace(PTRACE_SETREGS, pid, 0, uregs); 90 | } 91 | 92 | void before_select(pid_t pid, struct user_regs_struct * uregs) { 93 | if (uregs->r8 != 0) { 94 | struct timeval tv; 95 | read_block(pid, &tv, (void *)uregs->r8, sizeof(struct timeval)); 96 | scale_timeval(&tv, 1.0/delayfactor, 0); 97 | write_block(pid, &tv, (void *)uregs->r8, sizeof(struct timeval)); 98 | } 99 | } 100 | 101 | void before_clock_nanosleep(pid_t pid, struct user_regs_struct * uregs) { 102 | struct timespec rqtp; 103 | read_block(pid, &rqtp, (void *)uregs->rdx, sizeof(struct timespec)); 104 | scale_timespec(&rqtp, 1.0/delayfactor, 0); 105 | write_block(pid, &rqtp, (void *)uregs->rdx, sizeof(struct timespec)); 106 | } 107 | 108 | /* post-syscall handlers */ 109 | 110 | void after_gettimeofday(pid_t pid, struct user_regs_struct * uregs) { 111 | struct timeval tv; 112 | read_block(pid, &tv, (void *)uregs->rdi, sizeof(struct timeval)); 113 | scale_timeval(&tv, timefactor, starttimes[CLOCK_REALTIME]); 114 | write_block(pid, &tv, (void *)uregs->rdi, sizeof(struct timeval)); 115 | } 116 | 117 | void after_clock_gettime(pid_t pid, struct user_regs_struct * uregs) { 118 | struct timespec ts; 119 | read_block(pid, &ts, (void *)uregs->rsi, sizeof(struct timespec)); 120 | scale_timespec(&ts, timefactor, starttimes[uregs->rdi]); // FIXME check bounds 121 | write_block(pid, &ts, (void *)uregs->rsi, sizeof(struct timespec)); 122 | } 123 | 124 | void after_time(pid_t pid, struct user_regs_struct * uregs) { 125 | uregs->rdi = starttimes[CLOCK_REALTIME] + (uregs->rdi - starttimes[CLOCK_REALTIME]) * timefactor; 126 | ptrace(PTRACE_SETREGS, pid, 0, uregs); 127 | } 128 | 129 | void after_clock_nanosleep(pid_t pid, struct user_regs_struct * uregs) { 130 | struct timespec rmtp; 131 | read_block(pid, &rmtp, (void *)uregs->rcx, sizeof(struct timespec)); 132 | scale_timespec(&rmtp, 1.0/delayfactor, 0); 133 | write_block(pid, &rmtp, (void *)uregs->rcx, sizeof(struct timespec)); 134 | } 135 | 136 | int main(int argc, char *argv[], char *envp[]) { 137 | 138 | if (argc < 3) { 139 | printf("USAGE: %s DELAY_FACTOR TIME_FACTOR COMMAND [ARGS]...\n", argv[0]); 140 | exit(EXIT_FAILURE); 141 | } 142 | 143 | delayfactor = strtod(argv[1], NULL); 144 | timefactor = strtod(argv[2], NULL); 145 | 146 | before_handlers[SYS_nanosleep] = before_nanosleep; 147 | before_handlers[SYS_poll] = before_poll; 148 | before_handlers[SYS_select] = before_select; 149 | before_handlers[SYS_clock_nanosleep] = before_clock_nanosleep; 150 | 151 | after_handlers[SYS_gettimeofday] = after_gettimeofday; 152 | after_handlers[SYS_clock_gettime] = after_clock_gettime; 153 | after_handlers[SYS_time] = after_time; 154 | after_handlers[SYS_clock_nanosleep] = after_clock_nanosleep; 155 | 156 | struct timespec sts; 157 | for (clockid_t id = 0; id < NUM_CLKIDS; id++) { 158 | clock_gettime(id, &sts); // sometimes id will be invalid, but it shouldn't matter 159 | starttimes[id] = sts.tv_sec + (double)sts.tv_nsec / NANOSECONDS; 160 | } 161 | 162 | pid_t child = fork(); 163 | 164 | if(child == 0) { 165 | /* child */ 166 | envp[0] = "LD_PRELOAD=./novdso.so"; // FIXME: Do something more sensible 167 | kill(getpid(), SIGSTOP); 168 | execvpe(argv[3], &argv[3], envp); 169 | perror("execvpe"); // execvpe only returns on error 170 | exit(-1); 171 | } 172 | #ifdef DEBUG 173 | fprintf(stderr, "Child spawned with PID %d\n", child); 174 | #endif 175 | ptrace(PTRACE_SEIZE, child, 0, PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACEEXEC | PTRACE_O_EXITKILL | PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE); 176 | wait(NULL); // wait for SIGSTOP to happen 177 | ptrace(PTRACE_SYSCALL, child, 0, 0); // continue execution 178 | 179 | if (is64bit(child)) { 180 | #ifdef DEBUG 181 | fprintf(stderr, "Child is 64-bit\n"); 182 | #endif 183 | } else { 184 | fprintf(stderr, "ERROR: 32-bit processes are currently unsupported\n"); 185 | exit(-1); 186 | } 187 | 188 | for (;;) { // TODO: Understand ptrace, simplify code structure 189 | struct user_regs_struct uregs; 190 | int status; 191 | pid_t pid = waitpid(-1, &status, 0); 192 | if (WIFEXITED(status)) { 193 | if (pid == child) { 194 | exit(WEXITSTATUS(status)); 195 | } else { 196 | continue; 197 | } 198 | } 199 | if (WIFSTOPPED(status) && WSTOPSIG(status) != SIGTRAP && WSTOPSIG(status) != SIGSTOP) { 200 | if (WSTOPSIG(status) & 0x80) { 201 | // handle syscall 202 | } else { 203 | ptrace(PTRACE_SYSCALL, pid, 0, WSTOPSIG(status)); 204 | continue; 205 | } 206 | } else { 207 | ptrace(PTRACE_SYSCALL, pid, 0, 0); 208 | continue; 209 | } 210 | 211 | ptrace(PTRACE_GETREGS, pid, 0, &uregs); 212 | if (!leavesys[pid]) { 213 | #ifdef DEBUG 214 | fprintf(stderr, "[pid %d] syscall(%llu)\t0x%016llX 0x%016llX 0x%016llX = ...\n", pid, uregs.orig_rax, uregs.rdi, uregs.rsi, uregs.rdx); 215 | #endif 216 | if (uregs.orig_rax < NUM_SYSCALLS && before_handlers[uregs.orig_rax] != NULL) { 217 | before_handlers[uregs.orig_rax](pid, &uregs); 218 | } 219 | } else { 220 | #ifdef DEBUG 221 | fprintf(stderr, "... 0x%llX\n", uregs.rax); 222 | #endif 223 | if (uregs.orig_rax < NUM_SYSCALLS && after_handlers[uregs.orig_rax] != NULL) { 224 | after_handlers[uregs.orig_rax](pid, &uregs); 225 | } 226 | } 227 | leavesys[pid] ^= true; 228 | ptrace(PTRACE_SYSCALL, pid, 0, 0); 229 | } 230 | } 231 | --------------------------------------------------------------------------------