From c19a4874368898e6d280eee567a57a9c4dde5c6a Mon Sep 17 00:00:00 2001 From: York Jasper Niebuhr Date: Thu, 11 Sep 2025 15:48:14 +0200 Subject: [PATCH] Rewire as library --- .gitignore | 3 +- CMakeLists.txt | 22 ++ Makefile | 21 -- cli/CMakeLists.txt | 4 + {src => cli}/cli.cpp | 0 {include => cli}/cli.hpp | 0 {src => cli}/dl.cpp | 0 {include => cli}/dl.hpp | 0 {src => cli}/install.cpp | 0 {include => cli}/install.hpp | 0 cli/main.cpp | 224 ++++++++++++ example/CMakeLists.txt | 3 + {wirekits/example => example}/README.md | 0 {wirekits/example => example}/main.cpp | 0 include/interface.hpp | 21 +- include/interface_types.hpp | 12 + include/wirekit.hpp | 11 +- src/CMakeLists.txt | 2 + {include => src}/environment.hpp | 2 +- src/execution.cpp | 286 ++++++++++++++++ src/interface.cpp | 88 ----- src/main.cpp | 434 ------------------------ 22 files changed, 571 insertions(+), 562 deletions(-) create mode 100644 CMakeLists.txt delete mode 100644 Makefile create mode 100644 cli/CMakeLists.txt rename {src => cli}/cli.cpp (100%) rename {include => cli}/cli.hpp (100%) rename {src => cli}/dl.cpp (100%) rename {include => cli}/dl.hpp (100%) rename {src => cli}/install.cpp (100%) rename {include => cli}/install.hpp (100%) create mode 100644 cli/main.cpp create mode 100644 example/CMakeLists.txt rename {wirekits/example => example}/README.md (100%) rename {wirekits/example => example}/main.cpp (100%) create mode 100644 include/interface_types.hpp create mode 100644 src/CMakeLists.txt rename {include => src}/environment.hpp (98%) create mode 100644 src/execution.cpp delete mode 100644 src/interface.cpp delete mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore index 95841ca..567609b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -rewire -*.so +build/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..41e9219 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,22 @@ +cmake_minimum_required(VERSION 3.28) +project(Rewire LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Make rewire core library +add_library(rewire SHARED "") +target_include_directories(rewire PUBLIC include) +add_subdirectory(src) + +# Building the cli tool and example wirekit is optional +option(REWIRE_BUILD_CLI "Build rewire cli" OFF) +option(REWIRE_BUILD_EXAMPLE "Build rewire example wirekit" OFF) + +if (REWIRE_BUILD_CLI OR (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)) + add_subdirectory(cli) +endif() + +if (REWIRE_BUILD_EXAMPLE OR (CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)) + add_subdirectory(example) +endif() diff --git a/Makefile b/Makefile deleted file mode 100644 index a8d86b1..0000000 --- a/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -REWIRE_SRC_DIR := src -REWIRE_INCLUDE_DIR := include -REWIRE_SRC := $(wildcard $(REWIRE_SRC_DIR)/*.cpp) -REWIRE_CC := g++ -REWIRE_CFLAGS := -std=c++20 -I $(REWIRE_INCLUDE_DIR) -Wall -fPIC -fvisibility=hidden -rdynamic - -WIREKIT_CC := g++ -WIREKIT_CFLAGS := -std=c++20 -I $(REWIRE_INCLUDE_DIR) -Wall -fno-rtti -Wno-literal-suffix -fPIC -shared - -all: rewire wirekits - -rewire: $(REWIRE_SRC) - $(REWIRE_CC) $(REWIRE_CFLAGS) -o $@ $(REWIRE_SRC) - -wirekits: example.so - -example.so: $(wildcard wirekits/example/*.cpp) - $(WIREKIT_CC) $(WIREKIT_CFLAGS) -o $@ $^ - -clean: - rm -f rewire example.so darkrose.so diff --git a/cli/CMakeLists.txt b/cli/CMakeLists.txt new file mode 100644 index 0000000..de84b0c --- /dev/null +++ b/cli/CMakeLists.txt @@ -0,0 +1,4 @@ +add_executable(rewire_cli main.cpp dl.cpp install.cpp cli.cpp) +set_target_properties(rewire_cli PROPERTIES OUTPUT_NAME "rewire") +target_compile_options(rewire_cli PRIVATE -Wall -fPIC -rdynamic) +target_link_libraries(rewire_cli PRIVATE rewire) diff --git a/src/cli.cpp b/cli/cli.cpp similarity index 100% rename from src/cli.cpp rename to cli/cli.cpp diff --git a/include/cli.hpp b/cli/cli.hpp similarity index 100% rename from include/cli.hpp rename to cli/cli.hpp diff --git a/src/dl.cpp b/cli/dl.cpp similarity index 100% rename from src/dl.cpp rename to cli/dl.cpp diff --git a/include/dl.hpp b/cli/dl.hpp similarity index 100% rename from include/dl.hpp rename to cli/dl.hpp diff --git a/src/install.cpp b/cli/install.cpp similarity index 100% rename from src/install.cpp rename to cli/install.cpp diff --git a/include/install.hpp b/cli/install.hpp similarity index 100% rename from include/install.hpp rename to cli/install.hpp diff --git a/cli/main.cpp b/cli/main.cpp new file mode 100644 index 0000000..9e78e45 --- /dev/null +++ b/cli/main.cpp @@ -0,0 +1,224 @@ +#include +#include +#include +#include + +#include "cli.hpp" +#include "dl.hpp" +#include "install.hpp" +#include "interface.hpp" + +int launch_help(int argc, char** argv) { + std::cout << "Use rewire via one of the following commands:" << std::endl; + + std::cout << " \"rewire help\" -> You appear to already know what it does ;)" << std::endl; + + std::cout << " \"rewire install \" -> Installs a wirekit to execute programs with" << std::endl; + std::cout << " -> The shared object to be installed as wirekit" << std::endl; + std::cout << " -> (Optional) The name via which the wirekit is supposed to be used" << std::endl; + std::cout << " -> Defaults to \"wirekit\" when installing \"wirekit.so\"" << std::endl; + + std::cout << " \"rewire uninstall \" -> Removes a wirekit from the system" << std::endl; + std::cout << " -> The name of the wirekit to be uninstalled" << std::endl; + + std::cout << " \"rewire list\" -> Lists all installed wirekits" << std::endl; + + std::cout << " \"rewire \"" << std::endl; + std::cout << " -> The wirekit to use for execution" << std::endl; + std::cout << " -> Can either be installed or in current working directory" << std::endl; + std::cout << " -> (Optional) A command to be executed" << std::endl; + std::cout << " -> Acts as rewired shell if no command is given" << std::endl; + + return 0; +} + +int launch_install(int argc, char** argv) { + if (argc < 3 || argc > 4) { + std::cout << "Invalid usage, try \"rewire help\" for more information!" << std::endl; + return 1; + } + + InstallManager im; + if (!im) { + std::cout << "Unable to manage installations!" << std::endl; + return 1; + } + + auto kitcheck = [](const std::filesystem::path& kit) -> bool { + DL tmp_dl { kit }; + if (!tmp_dl) { + std::cout << "Failed to check wirekit requirements for installation!" << std::endl; + return false; + } + + if (!tmp_dl.resolve("wirekit_prepare")) { + std::cout << "Wirekit must implement \"wirekit_prepare\" to be installed!" << std::endl; + return false; + } + + if (!tmp_dl.resolve("wirekit_command_start")) { + std::cout << "Wirekit must implement \"wirekit_command_start\" to be installed!" << std::endl; + return false; + } + + return true; + }; + + if (!im.install(argv[2], (argc == 4 ? argv[3] : nullptr), kitcheck)) { + std::cout << "Failed to install wirekit!" << std::endl; + return 1; + } + + std::cout << "Successfully installed wirekit!" << std::endl; + return 0; +} + +int launch_uninstall(int argc, char** argv) { + if (argc != 3) { + std::cout << "Invalid usage, try \"rewire help\" for more information!" << std::endl; + return 1; + } + + InstallManager im; + if (!im) { + std::cout << "Unable to manage installations!" << std::endl; + return 1; + } + + if (!im.uninstall(argv[2])) { + std::cout << "Failed to uninstall wirekit!" << std::endl; + return 1; + } + + std::cout << "Successfully uninstalled wirekit!" << std::endl; + return 0; +} + +int launch_list(int argc, char** argv) { + if (argc != 2) { + std::cout << "Invalid usage, try \"rewire help\" for more information!" << std::endl; + return 1; + } + + InstallManager im; + if (!im) { + std::cout << "Unable to access installations!" << std::endl; + return 1; + } + + auto wirekits = im.installs(); + if (wirekits.empty()) { + std::cout << "Currently, no wirekits are installed!" << std::endl; + } else { + std::cout << "Currently, the following wirekits are installed:" << std::endl; + for (const std::string& kit : wirekits) + std::cout << " \"" << kit << "\"" << std::endl; + } + + return 0; +} + +int launch_run(int argc, char** argv) { + if (argc < 2) { + std::cout << "Invalid usage, try \"rewire help\" for more information!" << std::endl; + return 1; + } + + InstallManager im; + if (!im) { + std::cout << "Unable to access installations!" << std::endl; + return 1; + } + + std::filesystem::path wirekit_path; + if (!im.get(argv[1], wirekit_path)) { + std::cout << "No wirekit named \"" << argv[1] << "\" is installed!" << std::endl; + return 1; + } + + DL wirekit { wirekit_path }; + if (!wirekit) { + std::cout << "Unable to load wirekit!" << std::endl; + return 1; + } + + // Collect wirekit control function handles + KitInterface ki; + ki.prepare = (err_t(*)())wirekit.resolve("wirekit_prepare"); + ki.command_start = (err_t(*)(int, const char* const*))wirekit.resolve("wirekit_command_start"); + ki.command_exit = (void(*)())wirekit.resolve("wirekit_command_exit"); + ki.subject_start = (void(*)())wirekit.resolve("wirekit_subject_start"); + ki.subject_exit = (void(*)())wirekit.resolve("wirekit_subject_exit"); + + // Initialize rewire (internally prepares wirekit) + if (!rewire_init(ki)) { + std::cout << "Rewire and wirekit preparation failed!" << std::endl; + return 1; + } + + // Execute commands in rewired syscall environment + if (argc > 2) { + rewire_run(argc - 2, (const char**)(argv + 2)); + } else { + // Shell mode + while (true) { + std::cout << "> "; + + std::string command; + std::getline(std::cin, command); + + // Split command at whitespaces + std::stringstream command_stream { command }; + std::vector command_split; // No need to reserve, presumably not many elements + + std::string part; + while (command_stream >> std::quoted(part)) + command_split.emplace_back(std::move(part)); + + if (command_split.empty()) + continue; + + if (command_split[0] == "exit") + break; + + // Compile argv (cstr pointer array) + std::vector command_split_cstr; + command_split_cstr.resize(command_split.size() + 1, nullptr); // +1 for nullptr termination + + for (std::size_t i = 0; i < command_split.size(); i++) + command_split_cstr[i] = command_split[i].c_str(); + + // Execute command + rewire_run((int)command_split_cstr.size() - 1, command_split_cstr.data()); + } + } + + return 0; +} + +int main(int argc, char** argv) { + if (argc < 2) { + std::cout << "Invalid usage, try \"rewire help\"!" << std::endl; + return 1; + } + + LAUNCH_MODE lm = launch_mode(argv[1]); + + switch (lm) { + case LAUNCH_MODE::HELP: + return launch_help(argc, argv); + case LAUNCH_MODE::INSTALL: + return launch_install(argc, argv); + case LAUNCH_MODE::UNINSTALL: + return launch_uninstall(argc, argv); + case LAUNCH_MODE::LIST: + return launch_list(argc, argv); + case LAUNCH_MODE::RUN: + return launch_run(argc, argv); + default: + std::cout << "Invalid launch mode!" << std::endl; + return 1; + } + + return 0; +} diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt new file mode 100644 index 0000000..3eca124 --- /dev/null +++ b/example/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(example SHARED main.cpp) +target_include_directories(example PRIVATE $) +target_compile_options(example PRIVATE -Wall -fno-rtti -Wno-literal-suffix -fPIC) diff --git a/wirekits/example/README.md b/example/README.md similarity index 100% rename from wirekits/example/README.md rename to example/README.md diff --git a/wirekits/example/main.cpp b/example/main.cpp similarity index 100% rename from wirekits/example/main.cpp rename to example/main.cpp diff --git a/include/interface.hpp b/include/interface.hpp index 06d3577..5856faf 100644 --- a/include/interface.hpp +++ b/include/interface.hpp @@ -1,12 +1,19 @@ #pragma once -#include "wirekit.hpp" +#include "interface_types.hpp" -extern err_t(*wirekit_prepare_handle)(); -extern err_t(*wirekit_command_start_handle)(int, const char* const*); -extern void(*wirekit_command_exit_handle)(); -extern void(*wirekit_subject_start_handle)(); -extern void(*wirekit_subject_exit_handle)(); +struct KitInterface { + err_t(*prepare)() = nullptr; + err_t(*command_start)(int, const char* const*) = nullptr; + void(*command_exit)() = nullptr; + void(*subject_start)() = nullptr; + void(*subject_exit)() = nullptr; +}; + +bool rewire_init(const KitInterface& iface); +bool rewire_run(int argc, const char* const* argv); + +extern "C" { // Exposure to wirekit is achieved by adding these functions to rewire's symbol tables #define EXPOSED __attribute__((visibility("default"))) @@ -19,3 +26,5 @@ EXPOSED err_t rewire_subject_id(pid_t* pid); EXPOSED err_t rewire_subject_get_regs(user_regs_struct* regs); EXPOSED err_t rewire_subject_set_regs(const user_regs_struct* regs); EXPOSED err_t rewire_subject_load_cstr(const char* subject_addr, char* buf, uint32_t* read, uint32_t n); + +} diff --git a/include/interface_types.hpp b/include/interface_types.hpp new file mode 100644 index 0000000..ec2a42e --- /dev/null +++ b/include/interface_types.hpp @@ -0,0 +1,12 @@ +#pragma once +#include +#include +#include + +using reg_t = decltype(user_regs_struct::orig_rax); +using hook_t = void(*)(); +using err_t = uint8_t; + +constexpr err_t REWIRE_SUCCESS = 0; +constexpr err_t REWIRE_FAILURE = 1; +constexpr err_t REWIRE_FAILURE_NOCTX = 2; diff --git a/include/wirekit.hpp b/include/wirekit.hpp index 17a18a7..ef34265 100644 --- a/include/wirekit.hpp +++ b/include/wirekit.hpp @@ -1,15 +1,6 @@ #pragma once -#include -#include -#include -using reg_t = decltype(user_regs_struct::orig_rax); -using hook_t = void(*)(); -using err_t = uint8_t; - -constexpr err_t REWIRE_SUCCESS = 0; -constexpr err_t REWIRE_FAILURE = 1; -constexpr err_t REWIRE_FAILURE_NOCTX = 2; +#include "interface_types.hpp" extern "C" { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..55445d5 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,2 @@ +target_sources(rewire PRIVATE environment.cpp execution.cpp) +target_include_directories(rewire PRIVATE .) # Internal headers diff --git a/include/environment.hpp b/src/environment.hpp similarity index 98% rename from include/environment.hpp rename to src/environment.hpp index 0639138..0f91f73 100644 --- a/include/environment.hpp +++ b/src/environment.hpp @@ -4,7 +4,7 @@ #include #include -#include "wirekit.hpp" +#include "interface_types.hpp" struct TraceContext { enum class EXECUTION_MODE { STARTING, USER, KERNEL /* Currently executing a system call */ }; diff --git a/src/execution.cpp b/src/execution.cpp new file mode 100644 index 0000000..4a6a5f8 --- /dev/null +++ b/src/execution.cpp @@ -0,0 +1,286 @@ +#include "interface.hpp" +#include "environment.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +bool initialized = false; +KitInterface kit; + +bool rewire_init(const KitInterface& iface) { + initialized = false; + + if (!iface.prepare || !iface.command_start) + return false; + + kit = iface; + + if (kit.prepare() != REWIRE_SUCCESS) + return false; + + return initialized = true; +} + +pid_t dispatch_tracee(int argc, const char* const* argv) { + pid_t child = fork(); + + if (child != 0) + return child; // Either -1 on error or child pid + + ptrace(PTRACE_TRACEME, 0, 0, 0); + execvp(argv[0], (char* const*)argv); // Removing const here is in child process + // Note -> Tracee stops with SIGTRAP immediately after execvp! + + // Child should never return from execvp + while (true) exit(1); +} + +class KillWrapper { + pid_t m_pid; +public: + KillWrapper(const KillWrapper& other) = delete; + KillWrapper(KillWrapper&& other) = delete; + KillWrapper& operator=(const KillWrapper& other) = delete; + KillWrapper& operator=(KillWrapper&& other) = delete; + + KillWrapper(pid_t pid) : m_pid{ pid } {} + + ~KillWrapper() { + if (m_pid != -1) + kill(m_pid, SIGKILL); + } + + void release() { + m_pid = -1; + } +}; + +#define FORCEQUIT { std::cerr << "Forcefully quitting!" << std::endl; while (true) exit(1); } + +bool rewire_run(int argc, const char* const* argv) { + if (!initialized) + return false; + + EnvironmentScope es { ENV }; // Make sure environment is cleared after each command + + // Notify wirekit about command start + if (kit.command_start(argc, argv) != REWIRE_SUCCESS) + return false; + + // Dispatch main command process + pid_t main_pid = dispatch_tracee(argc, argv); + if (main_pid == -1) + return false; + + KillWrapper kw { main_pid }; // Make sure it gets whiped until in main trace loop + + int exec_status; + if (waitpid(main_pid, &exec_status, 0) != main_pid || !WIFSTOPPED(exec_status) || WSTOPSIG(exec_status) != SIGTRAP) + return false; + + /* + PTRACE_O_EXITKILL -> Kill tracees when tracer exits + PTRACE_O_TRACESYSGOOD -> SIGTRAP signals caused by system call entry/exit stops have 0x80 bit set + PTRACE_O_TRACEFORK, PTRACE_O_TRACEVFORK, PTRACE_O_TRACECLONE -> Automatically trace new children (inherit options and mode) + */ + if (ptrace(PTRACE_SETOPTIONS, main_pid, PTRACE_O_EXITKILL, PTRACE_O_TRACESYSGOOD, + PTRACE_O_TRACEFORK, PTRACE_O_TRACEVFORK, PTRACE_O_TRACECLONE) != 0) + return false; + + // Let first thread actually start running (until syscall entry) + if (ptrace(PTRACE_SYSCALL, main_pid, 0, 0) != 0) + return false; + + // Inform wirekit about first thread and initialize its trace context + ENV.contexts().add_context(main_pid); + ENV.contexts().context(main_pid)->mode = TraceContext::EXECUTION_MODE::USER; + + ENV.contexts().context_set_active(main_pid); + + if (kit.subject_start) + kit.subject_start(); + + ENV.contexts().context_clear_active(); + + // Start tracer loop + kw.release(); + + int status; + pid_t pid; + + while ((pid = waitpid(-1, &status, __WALL)) != -1 || errno != ECHILD /* No (grand)children left */) { + if (pid == -1) + FORCEQUIT; + + /* + New processes and threads inherit PTRACE_SYSCALL execution mode from parent and thus end up + here when they perform their first syscall. Start logging context at this point. + */ + if (!ENV.contexts().context(pid)) { + ENV.contexts().add_context(pid); + ENV.contexts().context(pid)->mode = TraceContext::EXECUTION_MODE::USER; + + ENV.contexts().context_set_active(pid); + + if (kit.subject_start) + kit.subject_start(); + + ENV.contexts().context_clear_active(); + } + + // Activate context (definitely exists now) + ENV.contexts().context_set_active(pid); + + if (WIFEXITED(status)) { + // A child exited -> notify wirekit and stop tracing it + if (kit.subject_exit) + kit.subject_exit(); + + ENV.contexts().delete_context(pid); + } else if (WIFSTOPPED(status)) { + int sig = WSTOPSIG(status); + + if (sig == SIGTRAP) { + int event = (unsigned)status >> 16; + + if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK || event == PTRACE_EVENT_CLONE) { + ; // New thread but nothing to do -> tracer attaches on first syscall + } else { + FORCEQUIT; + } + } else if (sig == (SIGTRAP | 0x80) /* 0x80 thanks to PTRACE_O_TRACESYSGOOD */) { + // Tracee was trapped on either syscall entry or exit + TraceContext* ctx = ENV.contexts().context_get_active(); + + if (ptrace(PTRACE_GETREGS, pid, 0, &ctx->regs) != 0) + FORCEQUIT; + + ctx->regs_dirty = false; + + reg_t syscall = ctx->regs.orig_rax; + + if (ctx->mode == TraceContext::EXECUTION_MODE::USER) + ENV.entry_hook(syscall) || ENV.default_entry_hook(); // Short-circuit guaranteed by C++ standard ;) + else if (ctx->mode == TraceContext::EXECUTION_MODE::KERNEL) + ENV.exit_hook(syscall) || ENV.default_exit_hook(); + + if (ctx->regs_dirty) { + if (ptrace(PTRACE_SETREGS, pid, NULL, &ctx->regs) != 0) + FORCEQUIT; + } + + ctx->regs_dirty = false; + + auto progress_mode = [](TraceContext::EXECUTION_MODE m) { + switch (m) { + case TraceContext::EXECUTION_MODE::STARTING: + return TraceContext::EXECUTION_MODE::USER; + case TraceContext::EXECUTION_MODE::USER: + return TraceContext::EXECUTION_MODE::KERNEL; + case TraceContext::EXECUTION_MODE::KERNEL: + return TraceContext::EXECUTION_MODE::USER; + } + + return TraceContext::EXECUTION_MODE::STARTING; + }; + + ctx->mode = progress_mode(ctx->mode); + } + + // Let tracee continue until next system call event (entry or exit) + if (ptrace(PTRACE_SYSCALL, pid, 0, 0) != 0) + FORCEQUIT; + } // else { Weird signal is happening, should be irrelevant to wirekit though! } + + // Deactivate current context + ENV.contexts().context_clear_active(); + } + + // Notify wirekit that command has finished executing (no threads are left) + if (kit.command_exit) + kit.command_exit(); + + return true; +} + +err_t rewire_syscall_hook(reg_t syscall, hook_t entry, hook_t exit) { + ENV.register_hooks(syscall, entry, exit); + return REWIRE_SUCCESS; +} + +err_t rewire_syscall_unhook(reg_t syscall) { + rewire_syscall_hook(syscall, nullptr, nullptr); + return REWIRE_SUCCESS; +} + +err_t rewire_syscall_hook_default(hook_t entry, hook_t exit) { + ENV.register_default_hooks(entry, exit); + return REWIRE_SUCCESS; +} + +err_t rewire_syscall_unhook_default() { + rewire_syscall_hook_default(nullptr, nullptr); + return REWIRE_SUCCESS; +} + +err_t rewire_subject_id(pid_t* pid) { + const TraceContext* ctx = ENV.contexts().context_get_active(); + if (!ctx) + return REWIRE_FAILURE_NOCTX; + + *pid = ctx->pid; + return REWIRE_SUCCESS; +} + +err_t rewire_subject_get_regs(user_regs_struct* regs) { + const TraceContext* ctx = ENV.contexts().context_get_active(); + if (!ctx) + return REWIRE_FAILURE_NOCTX; + + std::memcpy(regs, &ctx->regs, sizeof(ctx->regs)); + return REWIRE_SUCCESS; +} + +err_t rewire_subject_set_regs(const user_regs_struct* regs) { + TraceContext* ctx = ENV.contexts().context_get_active(); + if (!ctx) + return REWIRE_FAILURE_NOCTX; + + std::memcpy(&ctx->regs, regs, sizeof(ctx->regs)); + ctx->regs_dirty = true; + return REWIRE_SUCCESS; +} + +err_t rewire_subject_load_cstr(const char* subject_addr, char* buf, uint32_t* read, uint32_t n) { + const TraceContext* ctx = ENV.contexts().context_get_active(); + if (!ctx) + return REWIRE_FAILURE_NOCTX; + + *read = 0; + while (*read < n) { + errno = 0; + long word = ptrace(PTRACE_PEEKDATA, ctx->pid, (char*)subject_addr, NULL); + if (errno == -1l && errno != 0) // errno differentiates successful -1 word from error + return REWIRE_FAILURE; + + for (unsigned i = 0; i < sizeof(long) && *read < n; i++) { + char c = *(reinterpret_cast(&word) + i); // Unlike bit operations, this is independent of endianness + *(buf++) = c; + (*read)++; + + if (c == 0) + return REWIRE_SUCCESS; // Encountered terminating null byte + } + + subject_addr += sizeof(long); + } + + return REWIRE_SUCCESS; +} + diff --git a/src/interface.cpp b/src/interface.cpp deleted file mode 100644 index 85386bd..0000000 --- a/src/interface.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "interface.hpp" -#include "environment.hpp" - -#include -#include -#include - -err_t(*wirekit_prepare_handle)() = nullptr; -err_t(*wirekit_command_start_handle)(int, const char* const*) = nullptr; -void(*wirekit_command_exit_handle)() = nullptr; -void(*wirekit_subject_start_handle)() = nullptr; -void(*wirekit_subject_exit_handle)() = nullptr; - -err_t rewire_syscall_hook(reg_t syscall, hook_t entry, hook_t exit) { - ENV.register_hooks(syscall, entry, exit); - return REWIRE_SUCCESS; -} - -err_t rewire_syscall_unhook(reg_t syscall) { - rewire_syscall_hook(syscall, nullptr, nullptr); - return REWIRE_SUCCESS; -} - -err_t rewire_syscall_hook_default(hook_t entry, hook_t exit) { - ENV.register_default_hooks(entry, exit); - return REWIRE_SUCCESS; -} - -err_t rewire_syscall_unhook_default() { - rewire_syscall_hook_default(nullptr, nullptr); - return REWIRE_SUCCESS; -} - -err_t rewire_subject_id(pid_t* pid) { - const TraceContext* ctx = ENV.contexts().context_get_active(); - if (!ctx) - return REWIRE_FAILURE_NOCTX; - - *pid = ctx->pid; - return REWIRE_SUCCESS; -} - -err_t rewire_subject_get_regs(user_regs_struct* regs) { - const TraceContext* ctx = ENV.contexts().context_get_active(); - if (!ctx) - return REWIRE_FAILURE_NOCTX; - - std::memcpy(regs, &ctx->regs, sizeof(ctx->regs)); - return REWIRE_SUCCESS; -} - -err_t rewire_subject_set_regs(const user_regs_struct* regs) { - TraceContext* ctx = ENV.contexts().context_get_active(); - if (!ctx) - return REWIRE_FAILURE_NOCTX; - - std::memcpy(&ctx->regs, regs, sizeof(ctx->regs)); - ctx->regs_dirty = true; - return REWIRE_SUCCESS; -} - -err_t rewire_subject_load_cstr(const char* subject_addr, char* buf, uint32_t* read, uint32_t n) { - const TraceContext* ctx = ENV.contexts().context_get_active(); - if (!ctx) - return REWIRE_FAILURE_NOCTX; - - *read = 0; - while (*read < n) { - errno = 0; - long word = ptrace(PTRACE_PEEKDATA, ctx->pid, (char*)subject_addr, NULL); // TODO - if (errno == -1l && errno != 0) // errno differentiates successful -1 word from error - return REWIRE_FAILURE; - - for (unsigned i = 0; i < sizeof(long) && *read < n; i++) { - char c = *(reinterpret_cast(&word) + i); // Unlike bit operations, this is independent of endianness - *(buf++) = c; - (*read)++; - - if (c == 0) - return REWIRE_SUCCESS; // Encountered terminating null byte - } - - subject_addr += sizeof(long); - } - - return REWIRE_SUCCESS; -} - diff --git a/src/main.cpp b/src/main.cpp deleted file mode 100644 index c7295e9..0000000 --- a/src/main.cpp +++ /dev/null @@ -1,434 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "cli.hpp" -#include "dl.hpp" -#include "install.hpp" -#include "interface.hpp" -#include "environment.hpp" - -#define FORCEQUIT { std::cout << "Forcefully quitting!" << std::endl; while (true) exit(1); } - -int launch_help(int argc, char** argv) { - std::cout << "Use rewire via one of the following commands:" << std::endl; - - std::cout << " \"rewire help\" -> You appear to already know what it does ;)" << std::endl; - - std::cout << " \"rewire install \" -> Installs a wirekit to execute programs with" << std::endl; - std::cout << " -> The shared object to be installed as wirekit" << std::endl; - std::cout << " -> (Optional) The name via which the wirekit is supposed to be used" << std::endl; - std::cout << " -> Defaults to \"wirekit\" when installing \"wirekit.so\"" << std::endl; - - std::cout << " \"rewire uninstall \" -> Removes a wirekit from the system" << std::endl; - std::cout << " -> The name of the wirekit to be uninstalled" << std::endl; - - std::cout << " \"rewire list\" -> Lists all installed wirekits" << std::endl; - - std::cout << " \"rewire \"" << std::endl; - std::cout << " -> The wirekit to use for execution" << std::endl; - std::cout << " -> Can either be installed or in current working directory" << std::endl; - std::cout << " -> (Optional) A command to be executed" << std::endl; - std::cout << " -> Acts as rewired shell if no command is given" << std::endl; - - return 0; -} - -int launch_install(int argc, char** argv) { - if (argc < 3 || argc > 4) { - std::cout << "Invalid usage, try \"rewire help\" for more information!" << std::endl; - return 1; - } - - InstallManager im; - if (!im) { - std::cout << "Unable to manage installations!" << std::endl; - return 1; - } - - auto kitcheck = [](const std::filesystem::path& kit) -> bool { - DL tmp_dl { kit }; - if (!tmp_dl) { - std::cout << "Failed to check wirekit requirements for installation!" << std::endl; - return false; - } - - if (!tmp_dl.resolve("wirekit_prepare")) { - std::cout << "Wirekit must implement \"wirekit_prepare\" to be installed!" << std::endl; - return false; - } - - if (!tmp_dl.resolve("wirekit_command_start")) { - std::cout << "Wirekit must implement \"wirekit_command_start\" to be installed!" << std::endl; - return false; - } - - return true; - }; - - if (!im.install(argv[2], (argc == 4 ? argv[3] : nullptr), kitcheck)) { - std::cout << "Failed to install wirekit!" << std::endl; - return 1; - } - - std::cout << "Successfully installed wirekit!" << std::endl; - return 0; -} - -int launch_uninstall(int argc, char** argv) { - if (argc != 3) { - std::cout << "Invalid usage, try \"rewire help\" for more information!" << std::endl; - return 1; - } - - InstallManager im; - if (!im) { - std::cout << "Unable to manage installations!" << std::endl; - return 1; - } - - if (!im.uninstall(argv[2])) { - std::cout << "Failed to uninstall wirekit!" << std::endl; - return 1; - } - - std::cout << "Successfully uninstalled wirekit!" << std::endl; - return 0; -} - -int launch_list(int argc, char** argv) { - if (argc != 2) { - std::cout << "Invalid usage, try \"rewire help\" for more information!" << std::endl; - return 1; - } - - InstallManager im; - if (!im) { - std::cout << "Unable to access installations!" << std::endl; - return 1; - } - - auto wirekits = im.installs(); - if (wirekits.empty()) { - std::cout << "Currently, no wirekits are installed!" << std::endl; - } else { - std::cout << "Currently, the following wirekits are installed:" << std::endl; - for (const std::string& kit : wirekits) - std::cout << " \"" << kit << "\"" << std::endl; - } - - return 0; -} - -pid_t dispatch_tracee(int argc, const char* const* argv) { - pid_t child = fork(); - - if (child != 0) - return child; // Either -1 on error or child pid - - ptrace(PTRACE_TRACEME, 0, 0, 0); - execvp(argv[0], (char* const*)argv); // Removing const here is in child process - // Note -> Tracee stops with SIGTRAP immediately after execvp! - - // Child should never return from execvp - while (true) exit(1); -} - -class KillWrapper { - pid_t m_pid; -public: - KillWrapper(const KillWrapper& other) = delete; - KillWrapper(KillWrapper&& other) = delete; - KillWrapper& operator=(const KillWrapper& other) = delete; - KillWrapper& operator=(KillWrapper&& other) = delete; - - KillWrapper(pid_t pid) : m_pid{ pid } {} - - ~KillWrapper() { - if (m_pid != -1) - kill(m_pid, SIGKILL); - } - - void release() { - m_pid = -1; - } -}; - -void run_command(int argc, const char* const* argv) { - EnvironmentScope es { ENV }; // Make sure environment is cleared after each command - - // Notify wirekit about command start - if (wirekit_command_start_handle(argc, argv) != REWIRE_SUCCESS) { - std::cout << "Wirekit prohibited command execution!" << std::endl; - return; - } - - // Dispatch main command process - pid_t main_pid = dispatch_tracee(argc, argv); - if (main_pid == -1) { - std::cout << "Failed to start command main process!" << std::endl; - return; - } - - KillWrapper kw { main_pid }; // Make sure it gets whiped until in main trace loop - - int exec_status; - if (waitpid(main_pid, &exec_status, 0) != main_pid || !WIFSTOPPED(exec_status) || WSTOPSIG(exec_status) != SIGTRAP) { - std::cout << "Execvp failed to run the command!" << std::endl; - return; - } - - /* - PTRACE_O_EXITKILL -> Kill tracees when tracer exits - PTRACE_O_TRACESYSGOOD -> SIGTRAP signals caused by system call entry/exit stops have 0x80 bit set - PTRACE_O_TRACEFORK, PTRACE_O_TRACEVFORK, PTRACE_O_TRACECLONE -> Automatically trace new children (inherit options and mode) - */ - if (ptrace(PTRACE_SETOPTIONS, main_pid, PTRACE_O_EXITKILL, PTRACE_O_TRACESYSGOOD, - PTRACE_O_TRACEFORK, PTRACE_O_TRACEVFORK, PTRACE_O_TRACECLONE) != 0) { - std::cout << "Tracer failed to set trace options of command main process!" << std::endl; - return; - } - - // Let first thread actually start running (until syscall entry) - if (ptrace(PTRACE_SYSCALL, main_pid, 0, 0) != 0) { - std::cout << "Failed to start command main process execution!" << std::endl; - return; - } - - // Inform wirekit about first thread and initialize its trace context - ENV.contexts().add_context(main_pid); - ENV.contexts().context(main_pid)->mode = TraceContext::EXECUTION_MODE::USER; - - ENV.contexts().context_set_active(main_pid); - - if (wirekit_subject_start_handle) - wirekit_subject_start_handle(); - - ENV.contexts().context_clear_active(); - - // Start tracer loop - kw.release(); - - int status; - pid_t pid; - - while ((pid = waitpid(-1, &status, __WALL)) != -1 || errno != ECHILD /* No (grand)children left */) { - if (pid == -1) { - std::cout << "Tracer interrupted by signal (errno=" << errno << ")!" << std::endl; - FORCEQUIT; - } - - /* - New processes and threads inherit PTRACE_SYSCALL execution mode from parent and thus end up - here when they perform their first syscall. Start logging context at this point. - */ - if (!ENV.contexts().context(pid)) { - ENV.contexts().add_context(pid); - ENV.contexts().context(pid)->mode = TraceContext::EXECUTION_MODE::USER; - - ENV.contexts().context_set_active(pid); - - if (wirekit_subject_start_handle) - wirekit_subject_start_handle(); - - ENV.contexts().context_clear_active(); - } - - // Activate context (definitely exists now) - ENV.contexts().context_set_active(pid); - - if (WIFEXITED(status)) { - // A child exited -> notify wirekit and stop tracing it - if (wirekit_subject_exit_handle) - wirekit_subject_exit_handle(); - - ENV.contexts().delete_context(pid); - } else if (WIFSTOPPED(status)) { - int sig = WSTOPSIG(status); - - if (sig == SIGTRAP) { - int event = (unsigned)status >> 16; - - if (event == PTRACE_EVENT_FORK || event == PTRACE_EVENT_VFORK || event == PTRACE_EVENT_CLONE) { - ; // New thread but nothing to do -> tracer attaches on first syscall - } else { - std::cout << "Received non-syscall SIGTRAP without thread creation event!" << std::endl; - FORCEQUIT; - } - } else if (sig == (SIGTRAP | 0x80) /* 0x80 thanks to PTRACE_O_TRACESYSGOOD */) { - // Tracee was trapped on either syscall entry or exit - TraceContext* ctx = ENV.contexts().context_get_active(); - - if (ptrace(PTRACE_GETREGS, pid, 0, &ctx->regs) != 0) { - std::cout << "Failed to read tracee registers!" << std::endl; - FORCEQUIT; - } - - ctx->regs_dirty = false; - - reg_t syscall = ctx->regs.orig_rax; - - if (ctx->mode == TraceContext::EXECUTION_MODE::USER) - ENV.entry_hook(syscall) || ENV.default_entry_hook(); // Short-circuit guaranteed by C++ standard ;) - else if (ctx->mode == TraceContext::EXECUTION_MODE::KERNEL) - ENV.exit_hook(syscall) || ENV.default_exit_hook(); - - if (ctx->regs_dirty) { - if (ptrace(PTRACE_SETREGS, pid, NULL, &ctx->regs) != 0) { - std::cout << "Failed to write tracee registers!" << std::endl; - FORCEQUIT; - } - } - - ctx->regs_dirty = false; - - auto progress_mode = [](TraceContext::EXECUTION_MODE m) { - switch (m) { - case TraceContext::EXECUTION_MODE::STARTING: - return TraceContext::EXECUTION_MODE::USER; - case TraceContext::EXECUTION_MODE::USER: - return TraceContext::EXECUTION_MODE::KERNEL; - case TraceContext::EXECUTION_MODE::KERNEL: - return TraceContext::EXECUTION_MODE::USER; - } - - return TraceContext::EXECUTION_MODE::STARTING; - }; - - ctx->mode = progress_mode(ctx->mode); - } - - // Let tracee continue until next system call event (entry or exit) - if (ptrace(PTRACE_SYSCALL, pid, 0, 0) != 0) { - std::cout << "Failed to continue command execution after SIGTRAP!" << std::endl; - FORCEQUIT; - } - } // else { Weird signal is happening, should be irrelevant to wirekit though! } - - // Deactivate current context - ENV.contexts().context_clear_active(); - } - - // Notify wirekit that command has finished executing (no threads are left) - if (wirekit_command_exit_handle) - wirekit_command_exit_handle(); -} - -int launch_run(int argc, char** argv) { - if (argc < 2) { - std::cout << "Invalid usage, try \"rewire help\" for more information!" << std::endl; - return 1; - } - - InstallManager im; - if (!im) { - std::cout << "Unable to access installations!" << std::endl; - return 1; - } - - std::filesystem::path wirekit_path; - if (!im.get(argv[1], wirekit_path)) { - std::cout << "No wirekit named \"" << argv[1] << "\" is installed!" << std::endl; - return 1; - } - - DL wirekit { wirekit_path }; - if (!wirekit) { - std::cout << "Unable to load wirekit!" << std::endl; - return 1; - } - - // Collect wirekit control function handles - if (!(wirekit_prepare_handle = (err_t(*)())wirekit.resolve("wirekit_prepare"))) { - std::cout << "Wirekit is missing \"wirekit_prepare\" implementation!" << std::endl; - return 1; - } - - if (!(wirekit_command_start_handle = (err_t(*)(int, const char* const*))wirekit.resolve("wirekit_command_start"))) { - std::cout << "Wirekit is missing \"wirekit_command_start\" implementation!" << std::endl; - return 1; - } - - wirekit_command_exit_handle = (void(*)())wirekit.resolve("wirekit_command_exit"); - wirekit_subject_start_handle = (void(*)())wirekit.resolve("wirekit_subject_start"); - wirekit_subject_exit_handle = (void(*)())wirekit.resolve("wirekit_subject_exit"); - - // Prepare wirekit - err_t prepare_err; - if ((prepare_err = wirekit_prepare_handle()) != REWIRE_SUCCESS) { - std::cout << "Wirekit preparation failed (wirekit_prepare returned " << prepare_err << ")!" << std::endl; - return 1; - } - - // Execute commands in rewired syscall environment - if (argc > 2) { - run_command(argc - 2, (const char**)(argv + 2)); // Execute a single command - } else { - // Shell mode - while (true) { - std::cout << "> "; - - std::string command; - std::getline(std::cin, command); - - // Split command at whitespaces - std::stringstream command_stream { command }; - std::vector command_split; // No need to reserve, presumably not many elements - - std::string part; - while (command_stream >> std::quoted(part)) - command_split.emplace_back(std::move(part)); - - if (command_split.empty()) - continue; - - if (command_split[0] == "exit") - break; - - // Compile argv (cstr pointer array) - std::vector command_split_cstr; - command_split_cstr.resize(command_split.size() + 1, nullptr); // +1 for nullptr termination - - for (std::size_t i = 0; i < command_split.size(); i++) - command_split_cstr[i] = command_split[i].c_str(); - - // Execute command - run_command((int)command_split_cstr.size() - 1, command_split_cstr.data()); - } - } - - return 0; -} - -int main(int argc, char** argv) { - if (argc < 2) { - std::cout << "Invalid usage, try \"rewire help\"!" << std::endl; - return 1; - } - - LAUNCH_MODE lm = launch_mode(argv[1]); - - switch (lm) { - case LAUNCH_MODE::HELP: - return launch_help(argc, argv); - case LAUNCH_MODE::INSTALL: - return launch_install(argc, argv); - case LAUNCH_MODE::UNINSTALL: - return launch_uninstall(argc, argv); - case LAUNCH_MODE::LIST: - return launch_list(argc, argv); - case LAUNCH_MODE::RUN: - return launch_run(argc, argv); - default: - std::cout << "Invalid launch mode!" << std::endl; - return 1; - } - - return 0; -}