rizin/librz/io/p/io_debug.c
2022-08-22 13:50:39 +02:00

576 lines
14 KiB
C

// SPDX-FileCopyrightText: 2007-2020 pancake <pancake@nopcode.org>
// SPDX-License-Identifier: LGPL-3.0-only
#include <errno.h>
#include <rz_io.h>
#include <rz_lib.h>
#include <rz_util.h>
#include <rz_cons.h>
#include <rz_core.h>
#include <rz_socket.h>
#include <rz_debug.h> /* only used for BSD PTRACE redefinitions */
#include <string.h>
#define USE_RARUN 0
#if __linux__ || __APPLE__ || __WINDOWS__ || __NetBSD__ || __KFBSD__ || __OpenBSD__
#define DEBUGGER_SUPPORTED 1
#else
#define DEBUGGER_SUPPORTED 0
#endif
#if DEBUGGER && DEBUGGER_SUPPORTED
#define MAGIC_EXIT 123
#include <signal.h>
#if __UNIX__
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#endif
#if __APPLE__
#if !__POWERPC__
#include <spawn.h>
#endif
#include <sys/types.h>
#include <sys/wait.h>
#include <mach/exception_types.h>
#include <mach/mach_init.h>
#include <mach/mach_port.h>
#include <mach/mach_traps.h>
#include <mach/task.h>
#include <mach/task_info.h>
#include <mach/thread_act.h>
#include <mach/thread_info.h>
#include <mach/vm_map.h>
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
#endif
#if __WINDOWS__
#include <rz_windows.h>
#include <w32dbg_wrap.h>
#endif
/*
* Creates a new process and returns the result:
* -1 : error
* 0 : ok
*/
#if __WINDOWS__
typedef struct {
HANDLE hnd;
ut64 winbase;
} RzIOW32;
static int setup_tokens(void) {
HANDLE tok = NULL;
TOKEN_PRIVILEGES tp;
DWORD err = -1;
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &tok)) {
goto err_enable;
}
tp.PrivilegeCount = 1;
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tp.Privileges[0].Luid)) {
goto err_enable;
}
// tp.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0;
tp.Privileges[0].Attributes = 0; // SE_PRIVILEGE_ENABLED;
if (!AdjustTokenPrivileges(tok, 0, &tp, sizeof(tp), NULL, NULL)) {
goto err_enable;
}
err = 0;
err_enable:
if (tok) {
CloseHandle(tok);
}
if (err) {
rz_sys_perror("setup_tokens");
}
return err;
}
struct __createprocess_params {
LPCTSTR appname;
LPTSTR cmdline;
PROCESS_INFORMATION *pi;
DWORD flags;
};
static int __createprocess_wrap(void *params) {
STARTUPINFO si = { 0 };
// TODO: Add DEBUG_PROCESS to support child process debugging
struct __createprocess_params *p = params;
return CreateProcess(p->appname, p->cmdline, NULL, NULL, FALSE,
p->flags, NULL, NULL, &si, p->pi);
}
static int fork_and_ptraceme(RzIO *io, int bits, const char *cmd) {
RzCore *core = io->corebind.core;
PROCESS_INFORMATION pi;
STARTUPINFO si = { 0 };
si.cb = sizeof(si);
int pid, tid;
if (!*cmd) {
return -1;
}
setup_tokens();
char *_cmd = io->args ? rz_str_appendf(strdup(cmd), " %s", io->args) : strdup(cmd);
char **argv = rz_str_argv(_cmd, NULL);
char *cmdline = NULL;
// We need to build a command line with quoted argument and escaped quotes
int i = 0;
while (argv[i]) {
rz_str_arg_unescape(argv[i]);
cmdline = rz_str_appendf(cmdline, "\"%s\" ", argv[i]);
i++;
}
LPTSTR appname_ = rz_sys_conv_utf8_to_win(argv[0]);
LPTSTR cmdline_ = rz_sys_conv_utf8_to_win(cmdline);
DWORD flags = DEBUG_ONLY_THIS_PROCESS;
flags |= core->dbg->create_new_console ? CREATE_NEW_CONSOLE : 0;
free(cmdline);
struct __createprocess_params p = { appname_, cmdline_, &pi, flags };
W32DbgWInst *wrap = (W32DbgWInst *)rz_io_get_w32dbg_wrap(io);
wrap->params.type = W32_CALL_FUNC;
wrap->params.func.func = __createprocess_wrap;
wrap->params.func.user = &p;
w32dbg_wrap_wait_ret(wrap);
if (!w32dbgw_ret(wrap)) {
w32dbgw_err(wrap);
rz_sys_perror("fork_and_ptraceme/CreateProcess");
free(appname_);
free(cmdline_);
return -1;
}
CloseHandle(pi.hThread);
free(appname_);
free(cmdline_);
rz_str_argv_free(argv);
/* get process id and thread id */
pid = pi.dwProcessId;
tid = pi.dwThreadId;
eprintf("Spawned new process with pid %d, tid = %d\n", pid, tid);
RzCore *c = io->corebind.core;
c->dbg->plugin_data = wrap;
/* catch create process event */
int ret = c->dbg->cur->wait(c->dbg, pi.dwProcessId);
/* check if is a create process debug event */
if (ret != RZ_DEBUG_REASON_NEW_PID) {
TerminateProcess(pi.hProcess, 1);
core->dbg->cur->detach(core->dbg, wrap->pi.dwProcessId);
CloseHandle(pi.hProcess);
return -1;
}
CloseHandle(pi.hProcess);
return pid;
}
#else // windows
#if (__APPLE__ && __POWERPC__) || !__APPLE__
#if __APPLE__ || __BSD__
static void inferior_abort_handler(int pid) {
eprintf("Inferior received signal SIGABRT. Executing BKPT.\n");
}
#endif
static void trace_me(void) {
#if __APPLE__
rz_sys_signal(SIGTRAP, SIG_IGN); // NEED BY STEP
#endif
#if __APPLE__ || __BSD__
/* we can probably remove this #if..as long as PT_TRACE_ME is redefined for OSX in rz_debug.h */
rz_sys_signal(SIGABRT, inferior_abort_handler);
if (ptrace(PT_TRACE_ME, 0, 0, 0) != 0) {
rz_sys_perror("ptrace-traceme");
}
#if __APPLE__
ptrace(PT_SIGEXC, getpid(), NULL, 0);
#endif
#else
if (ptrace(PTRACE_TRACEME, 0, NULL, NULL) != 0) {
rz_sys_perror("ptrace-traceme");
exit(MAGIC_EXIT);
}
#endif
}
#endif
#if __APPLE__ && !__POWERPC__
static void handle_posix_error(int err) {
switch (err) {
case 0:
// eprintf ("Success\n");
break;
case 22:
eprintf("posix_spawnp: Invalid argument\n");
break;
case 86:
eprintf("Unsupported architecture. Please specify -b 32\n");
break;
default:
eprintf("posix_spawnp: unknown error %d\n", err);
perror("posix_spawnp");
break;
}
}
#endif
static RzRunProfile *_get_run_profile(RzIO *io, int bits, char **argv) {
int i;
RzRunProfile *rp = rz_run_new(NULL);
if (!rp) {
return NULL;
}
for (i = 0; argv[i]; i++) {
rp->_args[i] = argv[i];
}
rp->_args[i] = NULL;
if (!argv[0]) {
rz_run_free(rp);
return NULL;
}
rp->_program = strdup(argv[0]);
rp->_dodebug = true;
if (RZ_STR_ISNOTEMPTY(io->runprofile)) {
if (!rz_run_parsefile(rp, io->runprofile)) {
RZ_LOG_ERROR("io_debug: can't find profile '%s'\n", io->runprofile);
rz_run_free(rp);
return NULL;
}
if (strstr(io->runprofile, RZ_SYS_DIR ".rz-run.")) {
(void)rz_file_rm(io->runprofile);
}
} else if (RZ_STR_ISNOTEMPTY(io->envprofile) && !rz_run_parse(rp, io->envprofile)) {
RZ_LOG_ERROR("io_debug: can't parse default rz-run profile\n");
rz_run_free(rp);
return NULL;
}
rp->_bits = bits;
if (rz_run_config_env(rp)) {
RZ_LOG_ERROR("io_debug: can't config the environment.\n");
rz_run_free(rp);
return NULL;
}
return rp;
}
#if __APPLE__ && !__POWERPC__
static void handle_posix_redirection(RzRunProfile *rp, posix_spawn_file_actions_t *fileActions) {
const int mode = S_IRUSR | S_IWUSR;
if (rp->_stdin) {
posix_spawn_file_actions_addopen(fileActions, STDIN_FILENO, rp->_stdin, O_RDONLY, mode);
}
if (rp->_stdout) {
posix_spawn_file_actions_addopen(fileActions, STDOUT_FILENO, rp->_stdout, O_WRONLY, mode);
}
if (rp->_stderr) {
posix_spawn_file_actions_addopen(fileActions, STDERR_FILENO, rp->_stderr, O_WRONLY, mode);
}
}
// __UNIX__ (not windows)
static int fork_and_ptraceme_for_mac(RzIO *io, int bits, const char *cmd) {
pid_t p = -1;
char **argv;
posix_spawn_file_actions_t fileActions;
ut32 ps_flags = POSIX_SPAWN_SETSIGDEF | POSIX_SPAWN_SETSIGMASK;
sigset_t no_signals;
sigset_t all_signals;
size_t copied = 1;
cpu_type_t cpu = CPU_TYPE_ANY;
posix_spawnattr_t attr = { 0 };
posix_spawnattr_init(&attr);
sigemptyset(&no_signals);
sigfillset(&all_signals);
posix_spawnattr_setsigmask(&attr, &no_signals);
posix_spawnattr_setsigdefault(&attr, &all_signals);
posix_spawn_file_actions_init(&fileActions);
posix_spawn_file_actions_addinherit_np(&fileActions, STDIN_FILENO);
posix_spawn_file_actions_addinherit_np(&fileActions, STDOUT_FILENO);
posix_spawn_file_actions_addinherit_np(&fileActions, STDERR_FILENO);
ps_flags |= POSIX_SPAWN_CLOEXEC_DEFAULT;
ps_flags |= POSIX_SPAWN_START_SUSPENDED;
#define _POSIX_SPAWN_DISABLE_ASLR 0x0100
int ret;
argv = rz_str_argv(cmd, NULL);
if (!argv) {
posix_spawn_file_actions_destroy(&fileActions);
return -1;
}
RzRunProfile *rp = _get_run_profile(io, bits, argv);
if (!rp) {
rz_str_argv_free(argv);
posix_spawn_file_actions_destroy(&fileActions);
return -1;
}
handle_posix_redirection(rp, &fileActions);
if (rp->_args[0]) {
if (!rp->_aslr) {
ps_flags |= _POSIX_SPAWN_DISABLE_ASLR;
}
#if __x86_64__
if (rp->_bits == 32) {
cpu = CPU_TYPE_I386;
}
#endif
(void)posix_spawnattr_setflags(&attr, ps_flags);
posix_spawnattr_setbinpref_np(&attr, 1, &cpu, &copied);
ret = posix_spawnp(&p, rp->_args[0], &fileActions, &attr, rp->_args, NULL);
handle_posix_error(ret);
}
rz_str_argv_free(argv);
rz_run_free(rp);
posix_spawn_file_actions_destroy(&fileActions);
return p; // -1 ?
}
#endif // __APPLE__ && !__POWERPC__
#if (!(__APPLE__ && !__POWERPC__))
typedef struct fork_child_data_t {
RzIO *io;
int bits;
const char *cmd;
} fork_child_data;
static void fork_child_callback(void *user) {
fork_child_data *data = user;
char **argv = rz_str_argv(data->cmd, NULL);
if (!argv) {
exit(1);
}
rz_sys_clearenv();
RzRunProfile *rp = _get_run_profile(data->io, data->bits, argv);
if (!rp) {
rz_str_argv_free(argv);
exit(1);
}
trace_me();
rz_run_start(rp);
rz_run_free(rp);
rz_str_argv_free(argv);
exit(1);
}
static int fork_and_ptraceme_for_unix(RzIO *io, int bits, const char *cmd) {
int ret, status, child_pid;
void *bed = NULL;
fork_child_data child_data;
child_data.io = io;
child_data.bits = bits;
child_data.cmd = cmd;
child_pid = rz_io_ptrace_fork(io, fork_child_callback, &child_data);
switch (child_pid) {
case -1:
perror("fork_and_ptraceme");
break;
case 0:
return -1;
default:
/* XXX: clean this dirty code */
do {
ret = waitpid(child_pid, &status, WNOHANG);
if (ret == -1) {
perror("waitpid");
return -1;
}
bed = rz_cons_sleep_begin();
usleep(100000);
rz_cons_sleep_end(bed);
} while (ret != child_pid && !rz_cons_is_breaked());
if (WIFSTOPPED(status)) {
eprintf("Process with PID %d started...\n", (int)child_pid);
} else if (WEXITSTATUS(status) == MAGIC_EXIT) {
child_pid = -1;
} else if (rz_cons_is_breaked()) {
kill(child_pid, SIGSTOP);
} else {
eprintf("Killing child process %d due to an error\n", (int)child_pid);
kill(child_pid, SIGSTOP);
}
break;
}
return child_pid;
}
#endif
static int fork_and_ptraceme(RzIO *io, int bits, const char *cmd) {
// Before calling the platform implementation, append arguments to the command if they have been provided
char *_eff_cmd = io->args ? rz_str_appendf(strdup(cmd), " %s", io->args) : strdup(cmd);
int r = 0;
#if __APPLE__ && !__POWERPC__
r = fork_and_ptraceme_for_mac(io, bits, _eff_cmd);
#else
r = fork_and_ptraceme_for_unix(io, bits, _eff_cmd);
#endif
free(_eff_cmd);
return r;
}
#endif
static bool __plugin_open(RzIO *io, const char *file, bool many) {
if (!strncmp(file, "waitfor://", 10)) {
return true;
}
if (!strncmp(file, "pidof://", 8)) {
return true;
}
return (!strncmp(file, "dbg://", 6) && file[6]);
}
#include <rz_core.h>
static int get_pid_of(RzIO *io, const char *procname) {
RzCore *c = io->corebind.core;
if (c && c->dbg && c->dbg->cur) {
RzListIter *iter;
RzDebugPid *proc;
RzDebug *d = c->dbg;
RzList *pids = d->cur->pids(d, 0);
rz_list_foreach (pids, iter, proc) {
if (strstr(proc->path, procname)) {
eprintf("Matching PID %d %s\n", proc->pid, proc->path);
return proc->pid;
}
}
} else {
eprintf("Cannot enumerate processes\n");
}
return -1;
}
static RzIODesc *__open(RzIO *io, const char *file, int rw, int mode) {
RzIOPlugin *_plugin;
RzIODesc *ret = NULL;
char uri[128];
if (!strncmp(file, "waitfor://", 10)) {
const char *procname = file + 10;
eprintf("Waiting for %s\n", procname);
while (true) {
int target_pid = get_pid_of(io, procname);
if (target_pid != -1) {
snprintf(uri, sizeof(uri), "dbg://%d", target_pid);
file = uri;
break;
}
rz_sys_usleep(100);
}
} else if (!strncmp(file, "pidof://", 8)) {
const char *procname = file + 8;
int target_pid = get_pid_of(io, procname);
if (target_pid == -1) {
eprintf("Cannot find matching process for %s\n", file);
return NULL;
}
snprintf(uri, sizeof(uri), "dbg://%d", target_pid);
file = uri;
}
if (__plugin_open(io, file, 0)) {
const char *pidfile = file + 6;
char *endptr;
int pid = (int)strtol(pidfile, &endptr, 10);
if (endptr == pidfile || pid < 0) {
pid = -1;
}
if (pid == -1) {
pid = fork_and_ptraceme(io, io->bits, file + 6);
if (pid == -1) {
return NULL;
}
#if __WINDOWS__
sprintf(uri, "w32dbg://%d", pid);
_plugin = rz_io_plugin_resolve(io, (const char *)uri, false);
if (!_plugin || !_plugin->open) {
return NULL;
}
ret = _plugin->open(io, uri, rw, mode);
#elif __APPLE__
sprintf(uri, "smach://%d", pid); // s is for spawn
_plugin = rz_io_plugin_resolve(io, (const char *)uri + 1, false);
if (!_plugin || !_plugin->open || !_plugin->close) {
return NULL;
}
ret = _plugin->open(io, uri, rw, mode);
#else
// TODO: use io_procpid here? faster or what?
sprintf(uri, "ptrace://%d", pid);
_plugin = rz_io_plugin_resolve(io, (const char *)uri, false);
if (!_plugin || !_plugin->open) {
return NULL;
}
ret = _plugin->open(io, uri, rw, mode);
#endif
} else {
sprintf(uri, "attach://%d", pid);
_plugin = rz_io_plugin_resolve(io, (const char *)uri, false);
if (!_plugin || !_plugin->open) {
return NULL;
}
ret = _plugin->open(io, uri, rw, mode);
#if __WINDOWS__
if (ret) {
RzCore *c = io->corebind.core;
c->dbg->plugin_data = ret->data;
}
#endif
}
if (ret) {
ret->plugin = _plugin;
ret->referer = strdup(file); // kill this
}
}
return ret;
}
static int __close(RzIODesc *desc) {
int ret = -2;
eprintf("something went wrong\n");
if (desc) {
eprintf("trying to close %d with io_debug\n", desc->fd);
ret = -1;
}
rz_sys_backtrace();
return ret;
}
RzIOPlugin rz_io_plugin_debug = {
.name = "debug",
.desc = "Attach to native debugger instance",
.license = "LGPL3",
.uris = "dbg://,pidof://,waitfor://",
.author = "pancake",
.version = "0.2.0",
.open = __open,
.close = __close,
.check = __plugin_open,
.isdbg = true,
};
#else
RzIOPlugin rz_io_plugin_debug = {
.name = "debug",
.desc = "Debug a program or pid. (NOT SUPPORTED FOR THIS PLATFORM)",
};
#endif
#ifndef RZ_PLUGIN_INCORE
RZ_API RzLibStruct rizin_plugin = {
.type = RZ_LIB_TYPE_IO,
.data = &rz_io_plugin_debug,
.version = RZ_VERSION
};
#endif