Permalink
Browse files

fixed stability issues by injecting into VDSO

  • Loading branch information...
pforemski committed Feb 29, 2012
1 parent 718b658 commit de281611cfdaf0b393f0d4d46715146110dfac09
Showing with 122 additions and 55 deletions.
  1. +6 −7 doc/tracedump.1.markdown
  2. +54 −22 inject.c
  3. +2 −1 pid.c
  4. +3 −3 port.c
  5. +39 −16 ptrace.c
  6. +2 −0 ptrace.h
  7. +12 −6 tracedump.c
  8. +4 −0 tracedump.h
View
@@ -52,18 +52,17 @@ separated with spaces), or a command to execute with execvp(3).
## LIMITATIONS
* currently works on x86-32 Linux hosts only
* IP packets past the first fragment will not be captured
* maximum number of monitored ports is limited to less than 300 ports, due to limits on the
BPF filter attached to the sniffing socket
* some applications (e.g. chromium-browser) cant be started from tracedump, but attaching to
existing process by PID works
## BUGS
* sometimes the traced process segfaults - more testing needed
* cant start chromium-browser within tracedump, but attaching works
* currently works on x86-32 Linux hosts only
* maximum number of monitored ports is limited to less than 300 ports, due to limits on the
BPF filter attached to the sniffing socket
* there is a low probability of loosing TCP packets if the time distance between a particular
bind() system call and a connect() or listen() call is greater than 60 seconds
* probably much more :)
bind() system call and a connect() or listen() call is greater than 10 seconds
## SEE ALSO
View
@@ -13,11 +13,47 @@
#include "tracedump.h"
static void _prepare(struct pid *sp)
{
FILE *fp;
char buf[128];
unsigned char code[4] = { 0xcd, 0x80, 0, 0 }; // int 0x80
if (sp->vdso_addr)
return;
/* find VDSO address */
snprintf(buf, sizeof buf, "/proc/%d/maps", sp->pid);
fp = fopen(buf, "r");
while (fgets(buf, sizeof buf, fp)) {
if (strlen(buf) < 49 + 6)
continue;
/* found it? */
if (strncmp(buf + 49, "[vdso]", 6) == 0) {
/* parse address */
buf[8] = 0;
sp->vdso_addr = strtoul(buf, NULL, 16);
break;
}
}
fclose(fp);
if (!sp->vdso_addr)
dbg(0, "pid %d: no [vdso] memory region\n", sp->pid);
#ifdef VDSO_PIGGYBACK
/* on x86-32 the INT 0x80 is already there :) */
sp->vdso_addr += 0x406;
#else
/* inject our code */
dbg(3, "pid %d: installing code at 0x%x\n", sp->pid, sp->vdso_addr);
ptrace_write(sp, sp->vdso_addr, code, sizeof code);
#endif
}
int32_t inject_socketcall(struct tracedump *td, struct pid *sp, uint32_t sc_code, ...)
{
/* int 0x80, int3 */
unsigned char code[4] = { 0xcd, 0x80, 0xcc, 0 };
char backup[4];
struct user_regs_struct regs, regs2;
int ss_vals, ss_mem, ss;
va_list vl;
@@ -28,8 +64,6 @@ int32_t inject_socketcall(struct tracedump *td, struct pid *sp, uint32_t sc_code
uint32_t *stack32;
int i, j;
dbg(4, "inject_socketcall(pid=%d, code=%d)\n", sp->pid, sc_code);
/*
* get the required amount of stack space
*/
@@ -58,7 +92,6 @@ int32_t inject_socketcall(struct tracedump *td, struct pid *sp, uint32_t sc_code
*/
ptrace_getregs(sp, &regs);
memcpy(&regs2, &regs, sizeof regs);
ptrace_read(sp, regs.eip, backup, sizeof backup);
/*
* write the stack
@@ -93,13 +126,16 @@ int32_t inject_socketcall(struct tracedump *td, struct pid *sp, uint32_t sc_code
/*
* write the code and run
*/
regs2.eax = 102; // socketcall
_prepare(sp);
regs2.eax = 102; // socketcall
regs2.ebx = sc_code;
regs2.ecx = regs.esp - ss;
regs2.eip = sp->vdso_addr; // gateway to int3
ptrace_write(sp, regs.eip, code, sizeof code);
ptrace_setregs(sp, &regs2);
ptrace_cont(sp, 0, true); // sync call, wait for it
ptrace_cont_syscall(sp, 0, true); // enter...
ptrace_cont_syscall(sp, 0, true); // ...and exit
/*
* read back
@@ -124,9 +160,7 @@ int32_t inject_socketcall(struct tracedump *td, struct pid *sp, uint32_t sc_code
va_end(vl);
/* restore */
ptrace_write(sp, regs.eip, backup, sizeof backup);
ptrace_setregs(sp, &regs);
mmatic_free(stack);
return regs2.eax;
@@ -152,25 +186,23 @@ void inject_escape_socketcall(struct tracedump *td, struct pid *sp)
void inject_restore_socketcall(struct tracedump *td, struct pid *sp)
{
/* int 0x80, int3 */
unsigned char code[4] = { 0xcd, 0x80, 0xcc, 0 };
char backup[4];
struct user_regs_struct regs2;
/* backup */
ptrace_read(sp, sp->regs.eip, backup, 4);
/* prepare */
_prepare(sp);
memcpy(&regs2, &sp->regs, sizeof regs2);
regs2.eax = sp->regs.orig_eax;
regs2.eip = sp->vdso_addr;
/* exec */
sp->regs.eax = sp->regs.orig_eax;
ptrace_setregs(sp, &sp->regs);
ptrace_write(sp, sp->regs.eip, code, 4);
ptrace_cont(sp, 0, true);
ptrace_setregs(sp, &regs2);
ptrace_cont_syscall(sp, 0, true);
ptrace_cont_syscall(sp, 0, true);
/* read the return code */
/* rewrite the return code */
ptrace_getregs(sp, &regs2);
sp->regs.eax = regs2.eax;
/* restore */
ptrace_setregs(sp, &sp->regs);
ptrace_write(sp, sp->regs.eip, backup, 4);
}
View
3 pid.c
@@ -18,9 +18,10 @@ struct pid *pid_get(struct tracedump *td, pid_t pid)
if (!sp) {
dbg(7, "new pid %d\n", pid);
sp = mmatic_zalloc(td->mm, sizeof *sp);
thash_uint_set(td->pids, pid, sp);
sp->td = td;
sp->pid = pid;
thash_uint_set(td->pids, pid, sp);
}
return sp;
View
6 port.c
@@ -22,7 +22,7 @@ static void *gc_thread(void *arg)
pthread_sigmask(SIG_SETMASK, &ss, NULL);
while (1) {
sleep(60);
sleep(3);
/* read list of active tcp/udp ports */
tcp = port_list(td, true);
@@ -42,9 +42,9 @@ static void *gc_thread(void *arg)
/* TCP */
thash_reset(td->tcp_ports);
while ((sp = thash_uint_iter(td->tcp_ports, &port))) {
/* skip ports "younger" than 60 secs
/* skip ports "younger" than 10 secs
* workaround that autobound TCP ports are not visible in procfs - Linux bug? */
if (pjf_timediff(&now, &sp->since)/1000000 < 60)
if (pjf_timediff(&now, &sp->since)/1000000 < 10)
continue;
if (!PORT_ISSET(tcp, port)) {
View
@@ -33,7 +33,6 @@ static long _run_ptrace(enum __ptrace_request request, struct pid *sp,
}
dbg(1, "%s(req %d, pid %d): %s\n", func, request, pid, strerror(errno));
dbg(1, "pid %d state: %s", pid_state(pid));
} else {
dbg(5, "%s(req %d, pid %d)\n", func, request, pid);
}
@@ -80,16 +79,26 @@ int ptrace_attach_pid(struct pid *sp, void (*cb)(struct pid *sp))
int ptrace_attach_child(struct pid *sp, void (*cb)(struct pid *sp))
{
int status;
int rc, status;
FILE *fp;
char buf[128];
ptrace_wait(sp, &status);
if (WIFSTOPPED(status) &&
rc = waitpid(sp->pid, &status, __WALL);
if (rc == sp->pid && WIFSTOPPED(status) &&
(WSTOPSIG(status) == SIGSTOP || WSTOPSIG(status) == SIGTRAP)) {
dbg(2, "attached to PID %d\n", sp->pid);
/* read PID name */
snprintf(buf, sizeof buf, "/proc/%d/cmdline", sp->pid);
fp = fopen(buf, "r");
fgets(sp->cmdline, sizeof sp->cmdline, fp);
fclose(fp);
dbg(1, "attached to PID %d (%s)\n", sp->pid, sp->cmdline);
/* set ptrace options */
run_ptrace(PTRACE_SETOPTIONS, sp, NULL,
PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE);
PTRACE_O_TRACEFORK | PTRACE_O_TRACEVFORK | PTRACE_O_TRACECLONE | PTRACE_O_TRACESYSGOOD);
/* callback */
if (cb)
cb(sp);
@@ -124,11 +133,20 @@ int ptrace_wait(struct pid *sp, int *st)
if (WIFEXITED(status))
dbg(2, "wait(%d): process exited with status %d\n", pid, WEXITSTATUS(status));
else if (WIFSIGNALED(status))
dbg(1, "wait(%d): process terminated with signal %d\n", pid, WTERMSIG(status));
else if (WIFSTOPPED(status) &&
WSTOPSIG(status) != SIGSTOP &&
WSTOPSIG(status) != SIGTRAP)
dbg(2, "wait(%d): process received signal %d\n", pid, WSTOPSIG(status));
dbg(2, "wait(%d): process terminated with signal %d\n", pid, WTERMSIG(status));
else if (WIFSTOPPED(status)) {
switch (WSTOPSIG(status)) {
case SIGSTOP:
case SIGTRAP:
case SIGTRAPS:
break;
case SIGSEGV:
dbg(1, "wait(%d): process received SIGSEGV - segmentation fault\n", pid);
break;
default:
dbg(2, "wait(%d): process received signal %d\n", pid, WSTOPSIG(status));
}
}
if (st)
*st = status;
@@ -150,7 +168,7 @@ static inline void _ptrace_cont(bool syscall, struct pid *sp, unsigned long sig,
break;
sig = WSTOPSIG(status);
if (sig == SIGSTOP || sig == SIGTRAP)
if (sig == SIGTRAP || sig == SIGTRAPS)
break;
}
}
@@ -178,10 +196,15 @@ void ptrace_detach(struct pid *sp, unsigned long sig)
ptrace_wait(sp, &status);
if (WIFSTOPPED(status)) {
if (WSTOPSIG(status) == SIGTRAP || WSTOPSIG(status) == SIGSTOP)
run_ptrace(PTRACE_DETACH, sp, NULL, (void *) SIGCONT);
else
run_ptrace(PTRACE_DETACH, sp, NULL, (void *) WSTOPSIG(status));
switch (WSTOPSIG(status)) {
case SIGSTOP:
case SIGTRAP:
case SIGTRAPS:
run_ptrace(PTRACE_DETACH, sp, NULL, (void *) SIGCONT);
break;
default:
run_ptrace(PTRACE_DETACH, sp, NULL, (void *) WSTOPSIG(status));
}
} else {
run_ptrace(PTRACE_DETACH, sp, NULL, (void *) 0);
}
View
@@ -11,6 +11,8 @@
#include <stdint.h>
#include <sys/ptrace.h>
#define SIGTRAPS (SIGTRAP | 0x80)
/** Attach to process pid
* @param cb call cb before continuing
* @retval 0 success
View
@@ -211,6 +211,7 @@ int main(int argc, char *argv[])
struct user_regs_struct regs;
int status;
int stopped_pid;
int stopsig;
int i;
int attached;
struct sigaction sa;
@@ -289,25 +290,30 @@ int main(int argc, char *argv[])
/* wait for syscall from any pid */
stopped_pid = ptrace_wait(NULL, &status);
/* TODO?: filter out our threads :) */
if (stopped_pid == -1) {
break;
} else if (WIFEXITED(status) || WIFSIGNALED(status)) {
pid_del(td, stopped_pid);
continue;
} else if (!WIFSTOPPED(status)) {
goto next_syscall;
}
/* fetch pid info */
sp = pid_get(td, stopped_pid);
/* handle signal passing */
if (WIFSTOPPED(status)) {
if (WSTOPSIG(status) != SIGTRAP && WSTOPSIG(status) != SIGSTOP) {
stopsig = WSTOPSIG(status);
switch (stopsig) {
case SIGTRAPS:
break;
case SIGTRAP:
case SIGSTOP:
goto next_syscall;
default:
/* pass the signal to child */
ptrace_cont_syscall(sp, WSTOPSIG(status), false);
ptrace_cont_syscall(sp, stopsig, false);
continue;
}
}
/* get regs, skip syscalls other than socketcall */
View
@@ -29,6 +29,8 @@
#define TRACEDUMP_VERSION "0.5"
//#define VDSO_PIGGYBACK
struct tracedump;
struct pid;
struct sock;
@@ -71,12 +73,14 @@ struct tracedump {
struct pid {
struct tracedump *td; /**< path to the root data structure */
int pid; /**< process ID */
char cmdline[128]; /**< process cmdline */
bool in_socketcall; /**< true if in syscall 102 and its bind(), sendto() or connect() */
int code; /**< socketcall code */
struct sock *ss; /**< cache */
struct user_regs_struct regs; /**< regs backup */
uint32_t vdso_addr; /**< VDSO address (linux-gate.so.1) */
};
/** Represents a socket */

0 comments on commit de28161

Please sign in to comment.