From 1b5f8d2e5a118a80a4373a7be1ca4e4eceebf7be Mon Sep 17 00:00:00 2001 From: Malfurious Date: Sat, 8 Jul 2023 11:27:56 -0400 Subject: Initial debugger core and test UI This is vaguely competent at tracing single-threaded programs. Vi-like keybinds defined in misplays.c. Signed-off-by: Malfurious --- CMakeLists.txt | 2 + debugger.c | 282 ++++++++++++++++++++++++++++++++++++++++++++++++++ debugger.h | 55 ++++++++++ misplays.c | 320 ++++++++++++++++++++++++++++++++++++++++++++++++--------- 4 files changed, 612 insertions(+), 47 deletions(-) create mode 100644 debugger.c create mode 100644 debugger.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b8644a7..a7ff378 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,12 +15,14 @@ add_compile_definitions( add_executable(${PROJECT_NAME} console.c + debugger.c helpers.c list.c misplays.c ) target_link_libraries(${PROJECT_NAME} + capstone ncurses panel ) diff --git a/debugger.c b/debugger.c new file mode 100644 index 0000000..19d9edc --- /dev/null +++ b/debugger.c @@ -0,0 +1,282 @@ +#include +#include +#include +#include +#include + +#include + +/* #include "config.h" */ +#include "debugger.h" +#include "helpers.h" + +static const int PTRACE_OPTIONS = PTRACE_O_TRACESYSGOOD; + +static void capture_state(struct tracee *dbg) { + struct state *s = xmalloc(sizeof(*s)); + ptrace(PTRACE_GETREGS, dbg->id, NULL, &s->regs); + ptrace(PTRACE_GETFPREGS, dbg->id, NULL, &s->fpregs); + list_init(&s->maps); + list_insert(dbg->states.end, s); + dbg->state = s; + + char mapspath[128], entry[512]; + snprintf(mapspath, sizeof(mapspath), "/proc/%li/maps", (long)dbg->id); + + FILE *maps = fopen(mapspath, "r"); + if (maps) { + while (fgets(entry, sizeof(entry), maps)) { + struct map *m = xmalloc(sizeof(*m)); + sscanf(entry, "%lx-%lx ", &m->start, &m->end); + size_t size = m->end - m->start; + m->data = xmalloc(size); + + struct iovec loc = { m->data, size }; + struct iovec rem = { (void *)m->start, size }; + if (process_vm_readv(dbg->id, &loc, 1, &rem, 1, 0) < 0) { + free(m->data); + free(m); + continue; + } + + list_insert(s->maps.end, m); + } + + fclose(maps); + } +} + +static void clear_states(struct tracee *dbg) { + while (dbg->states.head != dbg->states.end) { + struct state *s = dbg->states.head; + while (s->maps.head != s->maps.end) { + struct map *m = s->maps.head; + list_remove(m); + free(m->data); + free(m); + } + list_remove(s); + free(s); + } + dbg->state = NULL; +} + +static void install_breakpoints(struct tracee *dbg) { + struct list *breaks = &dbg->breaks; + for (struct breakpoint *b = breaks->head; b != breaks->end; b = b->next) { + if (b->enabled) { + unsigned long data; + data = ptrace(PTRACE_PEEKTEXT, dbg->id, b->address, NULL); + b->orig = data; + + data = (data & ~0xff) | BREAKPOINT_INSN; + ptrace(PTRACE_POKETEXT, dbg->id, b->address, data); + b->active = 1; + } + } +} + +static int uninstall_breakpoints(struct tracee *dbg, int stop) { + struct user_regs_struct regs; + int ch = 0; + int reenter = 0; + if (stop) { + ptrace(PTRACE_GETREGS, dbg->id, NULL, ®s); + } + + struct list *breaks = &dbg->breaks; + for (struct breakpoint *b = breaks->head; b != breaks->end; b = b->next) { + if (b->active) { + ptrace(PTRACE_POKETEXT, dbg->id, b->address, b->orig); + b->active = 0; + + /* detect stop at breakpoint */ + if (stop && !ch) { + if (regs.rip - 1 == b->address) { + regs.rip--; + ptrace(PTRACE_SETREGS, dbg->id, NULL, ®s); + ch = 1; + + if (b->stack != 0 && b->stack != regs.rsp) { + reenter = 1; + } + } + } + + if (b->enabled < 0 && !reenter) { + struct breakpoint *del = b; + b = b->prev; + list_remove(del); + free(del); + } + } + } + + return reenter; +} + +int dbg_process(struct tracee *dbg, pid_t pid) { + if (ptrace(PTRACE_ATTACH, pid, NULL, NULL) < 0) { + return -1; + } + + list_init(&dbg->breaks); + list_init(&dbg->states); + dbg->state = NULL; + dbg->id = pid; + dbg->child = 0; + dbg->stopped = 0; + dbg->status = 0; + dbg->signal = 0; + dbg->cont = 0; + dbg->buff = NULL; + dbg->buffsize = 0; + + while (!dbg_wait(dbg)) {} + ptrace(PTRACE_SETOPTIONS, pid, NULL, PTRACE_OPTIONS); + return 0; +} + +int dbg_new_process(struct tracee *dbg, char **argv, struct console *cons) { + pid_t pid = fork(); + if (pid < 0) { + return -1; + } + + if (pid == 0) { + console_configslave(cons); + setpgid(0, 0); + ptrace(PTRACE_TRACEME, 0, NULL, NULL); + close_range(STDERR_FILENO+1, ~0U, CLOSE_RANGE_UNSHARE); + execvp(argv[0], argv); + exit(-1); + } + + list_init(&dbg->breaks); + list_init(&dbg->states); + dbg->state = NULL; + dbg->id = pid; + dbg->child = 1; + dbg->stopped = 0; + dbg->status = 0; + dbg->signal = 0; + dbg->cont = 0; + dbg->buff = NULL; + dbg->buffsize = 0; + + while (!dbg_wait(dbg)) {} + ptrace(PTRACE_SETOPTIONS, pid, NULL, PTRACE_O_EXITKILL | PTRACE_OPTIONS); + return 0; +} + +int dbg_wait(struct tracee *dbg) { + if (dbg->stopped) { + return 1; + } + + if (waitpid(dbg->id, &dbg->status, WNOHANG | __WALL) <= 0) { + return 0; + } + + if (dbg->cont != 0) { + install_breakpoints(dbg); + ptrace(dbg->cont, dbg->id, NULL, NULL); + dbg->cont = 0; + return 0; + } + + if (uninstall_breakpoints(dbg, 1)) { + ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); + dbg->cont = PTRACE_CONT; + return 0; + } + + capture_state(dbg); + dbg->stopped = 1; + return 1; +} + +/* note: move head < state < tail checks from main into these function + * add a dbg_stepback function */ + +int dbg_stepin(struct tracee *dbg) { + /* consider making this a no-op if we are not stopped on a call */ + ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); + dbg->stopped = 0; + dbg->cont = 0; + return 0; +} + +int dbg_stepover(struct tracee *dbg) { + csh handle; + cs_open(CS_ARCH_X86, CS_MODE_64, &handle); + + uint64_t address = dbg->state->regs.rip; + size_t size = 128; + const uint8_t *code = deref(dbg, address, size); + cs_insn *insn = cs_malloc(handle); + + cs_disasm_iter(handle, &code, &size, &address, insn); + + if (insn->id == X86_INS_CALL) { + struct breakpoint *b = xmalloc(sizeof(*b)); + b->address = address; + b->stack = dbg->state->regs.rsp; + b->enabled = -1; + b->active = 0; + list_insert(dbg->breaks.end, b); + dbg->cont = PTRACE_CONT; + //dbg_cont(dbg, PTRACE_CONT); + } else { + dbg->cont = 0; + //dbg_stepin(dbg); + } + + ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); + dbg->stopped = 0; + + cs_free(insn, 1); + cs_close(&handle); + return 0; +} + +/* +int dbg_stepout(struct tracee *dbg) { + unsigned long bp = dbg->state->regs.rbp; + unsigned long ra = *(unsigned long *)deref(dbg, bp+8, sizeof(unsigned long)); + + struct breakpoint *b = xmalloc(sizeof(*b)); + b->address = ra; + b->stack = 0; + b->enabled = -1; + b->active = 0; + list_insert(dbg->breaks.end, b); + + ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); + //clear_states(dbg); // keep this? + dbg->stopped = 0; + dbg->cont = PTRACE_CONT; + return 0; +} +*/ + +int dbg_cont(struct tracee *dbg, int cont) { + ptrace(PTRACE_SINGLESTEP, dbg->id, NULL, NULL); + clear_states(dbg); + dbg->stopped = 0; + dbg->cont = cont; + return 0; +} + +void *deref(struct tracee *dbg, unsigned long addr, size_t size) { + (void)size; // todo + + struct list *maps = &dbg->state->maps; + for (struct map *m = maps->head; m != maps->end; m = m->next) { + if (m->start <= addr && addr < m->end) { + return (unsigned char *)m->data + (addr - m->start); + } + } + + return NULL; +} diff --git a/debugger.h b/debugger.h new file mode 100644 index 0000000..6305e96 --- /dev/null +++ b/debugger.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include + +#include "console.h" +#include "list.h" + +#define BREAKPOINT_INSN 0xcc + +struct breakpoint { + LINKEDLIST; + unsigned long address; + unsigned long stack; + unsigned long orig; + int enabled; + int active; +}; + +struct map { + LINKEDLIST; + unsigned long start; + unsigned long end; + void *data; +}; + +struct state { + LINKEDLIST; + struct user_regs_struct regs; + struct user_fpregs_struct fpregs; + struct list maps; +}; + +struct tracee { + struct list breaks; + struct list states; + struct state *state; + pid_t id; + int child; + int stopped; + int status; + int signal; + int cont; + void *buff; + size_t buffsize; +}; + +extern int dbg_process(struct tracee *dbg, pid_t pid); +extern int dbg_new_process(struct tracee *dbg, char **argv, struct console *cons); +extern int dbg_wait(struct tracee *dbg); +extern int dbg_stepin(struct tracee *dbg); +extern int dbg_stepover(struct tracee *dbg); +//extern int dbg_stepout(struct tracee *dbg); +extern int dbg_cont(struct tracee *dbg, int mode); +extern void *deref(struct tracee *dbg, unsigned long addr, size_t size); diff --git a/misplays.c b/misplays.c index 1d200a8..8f6e0ee 100644 --- a/misplays.c +++ b/misplays.c @@ -1,85 +1,311 @@ +#include #include -#include +#include +#include +#include #include +#include + #include "console.h" +#include "debugger.h" #include "helpers.h" -static char *const SPLOITCMD[] = { - "/bin/bash", "-c", "sploit <(echo 'io.interact()') cat", - NULL -}; - static PANEL *left, *right; -static struct console p1, p2; +static struct console cons; static int mode = 0; +static void describe_status(struct tracee *_dbg, PANEL *pan) { + struct tracee dbg = *_dbg; + int status = dbg.status; + struct ptrace_syscall_info info; + + if (WIFEXITED(status)) { + pprintw(pan, "exited with code: %u\n", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + pprintw(pan, "terminated by signal: %s\n", strsignal(WTERMSIG(status))); + } else { + unsigned long msg; + if (ptrace(PTRACE_GETEVENTMSG, dbg.id, NULL, &msg) < 0) { + perror("PTRACE_GETEVENTMSG"); + } + + if (WIFSTOPPED(status)) { + switch (status >> 8) { + case ((PTRACE_EVENT_VFORK << 8) | SIGTRAP): + pprintw(pan, "child vfork()'d to: %lu\n", msg); + break; + case ((PTRACE_EVENT_FORK << 8) | SIGTRAP): + pprintw(pan, "child fork()'d to: %lu\n", msg); + break; + case ((PTRACE_EVENT_CLONE << 8) | SIGTRAP): + pprintw(pan, "child clone()'d to: %lu\n", msg); + break; + case ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP): + pprintw(pan, "finished vfork\n"); + break; + case ((PTRACE_EVENT_EXEC << 8) | SIGTRAP): + pprintw(pan, "child exec()'d from: %lu\n", msg); + break; + case ((PTRACE_EVENT_EXIT << 8) | SIGTRAP): + pprintw(pan, "exiting with code: %lu\n", msg); + break; + case ((PTRACE_EVENT_STOP << 8) | SIGTRAP): + pprintw(pan, "child stopped\n"); + break; + case ((PTRACE_EVENT_SECCOMP << 8) | SIGTRAP): + pprintw(pan, "child seccomp event\n"); + break; + + case (SIGTRAP | 0x80): + ptrace(PTRACE_GET_SYSCALL_INFO, dbg.id, sizeof(info), &info); + pprintw(pan, "child entering syscall: %llu (%llx, %llx, %llx, %llx, %llx, %llx)\n", + info.entry.nr, + info.entry.args[0], + info.entry.args[1], + info.entry.args[2], + info.entry.args[3], + info.entry.args[4], + info.entry.args[5]); + break; + + case SIGSTOP: + case SIGTSTP: + case SIGTTIN: + case SIGTTOU: + pprintw(pan, "child group-stopped\n"); + break; + + default: + pprintw(pan, "received signal: %s\n", strsignal(WSTOPSIG(status))); + break; + } + } else { + pprintw(pan, "child stop event unrecognized\n"); + } + } +} + +static void list_breakpoints(struct tracee *dbg, PANEL *pan) { + struct list *breaks = &dbg->breaks; + if (breaks->head != breaks->end) { + pprintw(pan, "---\n"); + + for (struct breakpoint *bp=breaks->head; bp!=breaks->end; bp=bp->next) { + pprintw(pan, "0x%lx ", bp->address); + } + pprintw(pan, "\n"); + } +} + +static void describe_states(struct tracee *dbg, PANEL *pan) { + struct list *states = &dbg->states; + for (struct state *s = states->head; s != states->end; s = s->next) { + pprintw(pan, "%c", (s == dbg->state ? '#' : '-')); + } + pprintw(pan, "\n"); +} + +static void dump_registers(struct tracee *dbg, PANEL *pan) { + struct user_regs_struct *regs = &dbg->state->regs; + pprintw(pan, "rax = 0x%016llx\n", regs->rax); + pprintw(pan, "rbx = 0x%016llx\n", regs->rbx); + pprintw(pan, "rcx = 0x%016llx\n", regs->rcx); + pprintw(pan, "rdx = 0x%016llx\n", regs->rdx); + pprintw(pan, "rdi = 0x%016llx\n", regs->rdi); + pprintw(pan, "rsi = 0x%016llx\n", regs->rsi); + pprintw(pan, "rsp = 0x%016llx\n", regs->rsp); + pprintw(pan, "rbp = 0x%016llx\n", regs->rbp); + pprintw(pan, "rip = 0x%016llx\n", regs->rip); +} + +static void dump_stack(struct tracee *dbg, PANEL *pan) { + unsigned long sp = dbg->state->regs.rsp; + for (size_t i = 0; i < 16; i++, sp += 8) { + unsigned long word = *(unsigned long *)deref(dbg, sp,sizeof(unsigned long)); + pprintw(pan, "0x%lx:\t0x%lx\n", sp, word); + } +} + +static void disasm(struct tracee *dbg, PANEL *pan) { + csh handle; + cs_insn *insn; + + if (cs_open(CS_ARCH_X86, CS_MODE_64, &handle) != CS_ERR_OK) { + perror("capstone open"); + } else { + uint64_t address = dbg->state->regs.rip; + size_t codez = 128; + const uint8_t *code = deref(dbg, address, codez); + insn = cs_malloc(handle); + + for (size_t i = 0; i < 16; i++) { + if (!cs_disasm_iter(handle, &code, &codez, &address, insn)) { + break; + } + pprintw(pan, "0x%"PRIx64":\t%s %s\n", insn->address, insn->mnemonic, insn->op_str); + } + + cs_free(insn, 1); + cs_close(&handle); + } +} + +static void info_update(struct tracee *dbg, PANEL *pan) { + pclear(pan); + pprintw(pan, "PID: %li\n", (long)dbg->id); + if (!dbg->stopped) { + pprintw(pan, "Process is running...\n"); + } else { + describe_status(dbg, pan); + list_breakpoints(dbg, pan); + describe_states(dbg, pan); + dump_registers(dbg, pan); + pprintw(pan, "---\n"); + dump_stack(dbg, pan); + pprintw(pan, "---\n"); + disasm(dbg, pan); + } +} + static void layout(void) { int w = COLS/2; - reset_panel(left, LINES, w, 0, 0); - reset_panel(right, LINES, COLS-w, 0, w); - //reset_panel(left, 0, 0, 0, 0); - //reset_panel(right, 0, 0, 0, 0); + reset_panel(left, LINES-1, w, 0, 0); + reset_panel(right, LINES-1, COLS-w, 0, w); } -static void dofork(struct console *cons) { - if (fork() == 0) { - console_configslave(cons); - close_range(STDERR_FILENO+1, ~0U, CLOSE_RANGE_UNSHARE); - execvp(SPLOITCMD[0], SPLOITCMD); - exit(1); +int main(int argc, char **argv) { + if (argc < 3) { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; } -} -int main(void) { cursinit(); - left = newpan(0, 0, 0, 0); right = newpan(0, 0, 0, 0); layout(); - console_init(&p1); - console_init(&p2); - dofork(&p1); - dofork(&p2); + console_init(&cons); + + argv[argc] = NULL; + struct tracee dbg; + if (dbg_new_process(&dbg, argv+2, &cons)) { + pprintw(right, "Failed to start child\n"); + } + + unsigned long stop = strtoul(argv[1], NULL, 0); + if (stop != 0) { + struct breakpoint *b = xmalloc(sizeof(*b)); + b->address = stop; + b->stack = 0; + b->enabled = -1; + b->active = 0; + list_insert(dbg.breaks.end, b); + dbg_cont(&dbg, PTRACE_CONT); + } int quit = 0; while (!quit) { - console_update(&p1, left); - console_update(&p2, right); + //if (dbg.stopped == NULL) { + dbg_wait(&dbg); + //} + console_update(&cons, right); + info_update(&dbg, left); cursupdate(); + int ch = getch(); if (mode == 0) { switch (ch) { - case 'q': - quit = 1; - break; case KEY_RESIZE: layout(); break; - case KEY_F(1): + case 'q': + quit = 1; + break; + case 'i': mode = 1; - console_enter(&p1, left); + console_enter(&cons, right); break; - case KEY_F(2): - mode = 2; - console_enter(&p2, right); + case 'j': + if (dbg.stopped) { + if (dbg.state != dbg.states.tail) { + dbg.state = dbg.state->next; + } else { + dbg_stepover(&dbg); + } + } break; - } - } else if (mode == 1) { - switch (ch) { - case KEY_RESIZE: - layout(); + case 'k': + if (dbg.stopped) { + if (dbg.state != dbg.states.head) { + dbg.state = dbg.state->prev; + } + } break; - case 0x1b: - mode = 0; - console_leave(&p1, left); + case 'l': + if (dbg.stopped) { + if (dbg.state != dbg.states.tail) { + //dbg.state = dbg.state->next; + } else { + dbg_stepin(&dbg); + } + } break; - case ERR: + //case 'h': + // if (dbg.stopped) { + // dbg_stepout(&dbg); + // } + // break; + case 'g': + if (dbg.stopped) { + dbg.state = dbg.states.head; + } break; - default: - console_input(&p1, ch); + case 'G': + if (dbg.stopped) { + dbg.state = dbg.states.tail; + } + break; + case 's': + if (dbg.stopped) { + dbg_cont(&dbg, PTRACE_SYSCALL); + } + break; + case 'c': + if (dbg.stopped) { + dbg_cont(&dbg, PTRACE_CONT); + } + break; + case 'p': + if (!dbg.stopped) { + tgkill(dbg.id, dbg.id, SIGSTOP); + } + break; + case ':': + mvprintw(LINES-1, 0, ":"); + curs_set(TRUE); + echo(); + timeout(-1); + char cmd[128] = {0}; + getstr(cmd); + curs_set(FALSE); + noecho(); + timeout(25); + clear(); + refresh(); + + char *t = cmd; + struct breakpoint *b = xmalloc(sizeof(*b)); + b->enabled = 1; + b->active = 0; + if (t[0] == '!') { + b->enabled = -1; + t++; + } + b->address = strtoul(t, NULL, 0); + b->stack = 0; + list_insert(dbg.breaks.end, b); break; } } else { @@ -89,12 +315,12 @@ int main(void) { break; case 0x1b: mode = 0; - console_leave(&p2, right); + console_leave(&cons, right); break; case ERR: break; default: - console_input(&p2, ch); + console_input(&cons, ch); break; } } -- cgit v1.2.3