Rewire as library

This commit is contained in:
York Jasper Niebuhr 2025-09-11 15:48:14 +02:00
parent 91410dd6a7
commit c19a487436
22 changed files with 571 additions and 562 deletions

3
.gitignore vendored
View File

@ -1,2 +1 @@
rewire
*.so
build/

22
CMakeLists.txt Normal file
View File

@ -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()

View File

@ -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

4
cli/CMakeLists.txt Normal file
View File

@ -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)

224
cli/main.cpp Normal file
View File

@ -0,0 +1,224 @@
#include <iostream>
#include <filesystem>
#include <vector>
#include <sstream>
#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 <wirekit.so> <name>\" -> Installs a wirekit to execute programs with" << std::endl;
std::cout << " <wirekit.so> -> The shared object to be installed as wirekit" << std::endl;
std::cout << " <name> -> (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 <name>\" -> Removes a wirekit from the system" << std::endl;
std::cout << " <name> -> The name of the wirekit to be uninstalled" << std::endl;
std::cout << " \"rewire list\" -> Lists all installed wirekits" << std::endl;
std::cout << " \"rewire <wirekit> <command+args>\"" << std::endl;
std::cout << " <wirekit> -> The wirekit to use for execution" << std::endl;
std::cout << " -> Can either be installed or in current working directory" << std::endl;
std::cout << " <command+args> -> (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<std::string> 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<const char*> 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;
}

3
example/CMakeLists.txt Normal file
View File

@ -0,0 +1,3 @@
add_library(example SHARED main.cpp)
target_include_directories(example PRIVATE $<TARGET_PROPERTY:rewire,INTERFACE_INCLUDE_DIRECTORIES>)
target_compile_options(example PRIVATE -Wall -fno-rtti -Wno-literal-suffix -fPIC)

View File

@ -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);
}

View File

@ -0,0 +1,12 @@
#pragma once
#include <sys/types.h>
#include <sys/user.h>
#include <cstdint>
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;

View File

@ -1,15 +1,6 @@
#pragma once
#include <sys/types.h>
#include <sys/user.h>
#include <cstdint>
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" {

2
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,2 @@
target_sources(rewire PRIVATE environment.cpp execution.cpp)
target_include_directories(rewire PRIVATE .) # Internal headers

View File

@ -4,7 +4,7 @@
#include <memory>
#include <sys/user.h>
#include "wirekit.hpp"
#include "interface_types.hpp"
struct TraceContext {
enum class EXECUTION_MODE { STARTING, USER, KERNEL /* Currently executing a system call */ };

286
src/execution.cpp Normal file
View File

@ -0,0 +1,286 @@
#include "interface.hpp"
#include "environment.hpp"
#include <iostream>
#include <cstring>
#include <sys/ptrace.h>
#include <errno.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
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<char*>(&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;
}

View File

@ -1,88 +0,0 @@
#include "interface.hpp"
#include "environment.hpp"
#include <cstring>
#include <sys/ptrace.h>
#include <errno.h>
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<char*>(&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;
}

View File

@ -1,434 +0,0 @@
#include <iostream>
#include <filesystem>
#include <vector>
#include <sstream>
#include <unistd.h>
#include <sys/ptrace.h>
#include <sys/wait.h>
#include <signal.h>
#include <errno.h>
#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 <wirekit.so> <name>\" -> Installs a wirekit to execute programs with" << std::endl;
std::cout << " <wirekit.so> -> The shared object to be installed as wirekit" << std::endl;
std::cout << " <name> -> (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 <name>\" -> Removes a wirekit from the system" << std::endl;
std::cout << " <name> -> The name of the wirekit to be uninstalled" << std::endl;
std::cout << " \"rewire list\" -> Lists all installed wirekits" << std::endl;
std::cout << " \"rewire <wirekit> <command+args>\"" << std::endl;
std::cout << " <wirekit> -> The wirekit to use for execution" << std::endl;
std::cout << " -> Can either be installed or in current working directory" << std::endl;
std::cout << " <command+args> -> (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<std::string> 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<const char*> 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;
}