Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
@@ -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
7 changes: 1 addition & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
21 changes: 0 additions & 21 deletions novdso.c

This file was deleted.

86 changes: 86 additions & 0 deletions ptrace.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#include <stdbool.h>
#include <stdio.h>
#include <sys/auxv.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/user.h>
#include <sys/wait.h>

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;
}
9 changes: 9 additions & 0 deletions ptrace.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include <stdint.h>
#include <sys/types.h>

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);
61 changes: 36 additions & 25 deletions tardis.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include <math.h>
#include <stdbool.h>

#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
Expand All @@ -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) {
Expand Down Expand Up @@ -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");
Expand Down