summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMalfurious <m@lfurio.us>2023-07-08 11:27:56 -0400
committerMalfurious <m@lfurio.us>2023-07-08 11:27:56 -0400
commit1b5f8d2e5a118a80a4373a7be1ca4e4eceebf7be (patch)
tree62ee155841728954887285e97f04d069ecdf39a4
parentc29bf2efbdc4f4186f3fe571601b4d1acac4b321 (diff)
downloadmisplays-1b5f8d2e5a118a80a4373a7be1ca4e4eceebf7be.tar.gz
misplays-1b5f8d2e5a118a80a4373a7be1ca4e4eceebf7be.zip
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 <m@lfurio.us>
-rw-r--r--CMakeLists.txt2
-rw-r--r--debugger.c282
-rw-r--r--debugger.h55
-rw-r--r--misplays.c320
4 files changed, 612 insertions, 47 deletions
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 <stdio.h>
+#include <stdlib.h>
+#include <sys/ptrace.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+
+#include <capstone/capstone.h>
+
+/* #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, &regs);
+ }
+
+ 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, &regs);
+ 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 <sys/user.h>
+#include <unistd.h>
+
+#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 <signal.h>
#include <stdlib.h>
-#include <sys/syscall.h>
+#include <string.h>
+#include <sys/ptrace.h>
+#include <linux/ptrace.h>
#include <unistd.h>
+#include <capstone/capstone.h>
+
#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 <stop> <command>\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;
}
}