Finished tracer logic and command environment data
This commit is contained in:
parent
528eccc626
commit
9d3166a657
88
include/environment.hpp
Normal file
88
include/environment.hpp
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <memory>
|
||||||
|
#include <sys/user.h>
|
||||||
|
|
||||||
|
#include "wirekit.hpp"
|
||||||
|
|
||||||
|
struct TraceContext {
|
||||||
|
enum class EXECUTION_MODE { STARTING, USER, KERNEL /* Currently executing a system call */ };
|
||||||
|
|
||||||
|
pid_t pid;
|
||||||
|
EXECUTION_MODE mode;
|
||||||
|
user_regs_struct regs;
|
||||||
|
bool regs_dirty;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TraceContextCollection {
|
||||||
|
std::unordered_map<pid_t, std::unique_ptr<TraceContext>> m_contexts;
|
||||||
|
pid_t m_active;
|
||||||
|
public:
|
||||||
|
TraceContextCollection(const TraceContextCollection& other) = delete;
|
||||||
|
TraceContextCollection(TraceContextCollection&& other);
|
||||||
|
TraceContextCollection& operator=(const TraceContextCollection& other) = delete;
|
||||||
|
TraceContextCollection& operator=(TraceContextCollection&& other);
|
||||||
|
|
||||||
|
TraceContextCollection();
|
||||||
|
~TraceContextCollection();
|
||||||
|
|
||||||
|
bool add_context(pid_t pid);
|
||||||
|
void delete_context(pid_t pid);
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
TraceContext* context(pid_t pid);
|
||||||
|
const TraceContext* context(pid_t pid) const;
|
||||||
|
|
||||||
|
void context_set_active(pid_t pid);
|
||||||
|
void context_clear_active();
|
||||||
|
TraceContext* context_get_active();
|
||||||
|
const TraceContext* context_get_active() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Environment {
|
||||||
|
struct HookPair {
|
||||||
|
hook_t entry = nullptr;
|
||||||
|
hook_t exit = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unordered_map<reg_t, HookPair> m_hooks;
|
||||||
|
HookPair m_default_hooks;
|
||||||
|
TraceContextCollection m_trace_contexts;
|
||||||
|
public:
|
||||||
|
Environment(const Environment& other) = delete;
|
||||||
|
Environment(Environment&& other) = delete;
|
||||||
|
Environment& operator=(const Environment& other) = delete;
|
||||||
|
Environment& operator=(Environment&& other) = delete;
|
||||||
|
|
||||||
|
Environment();
|
||||||
|
~Environment();
|
||||||
|
|
||||||
|
void register_hooks(reg_t syscall, hook_t entry, hook_t exit);
|
||||||
|
void register_default_hooks(hook_t entry, hook_t exit);
|
||||||
|
|
||||||
|
bool entry_hook(reg_t syscall) const;
|
||||||
|
bool exit_hook(reg_t syscall) const;
|
||||||
|
bool default_entry_hook() const;
|
||||||
|
bool default_exit_hook() const;
|
||||||
|
|
||||||
|
TraceContextCollection& contexts();
|
||||||
|
const TraceContextCollection& contexts() const;
|
||||||
|
|
||||||
|
void clear();
|
||||||
|
};
|
||||||
|
|
||||||
|
// An EnvironmentScope calls Environment::clear() when destructed
|
||||||
|
class EnvironmentScope {
|
||||||
|
Environment& m_environment;
|
||||||
|
public:
|
||||||
|
EnvironmentScope(const EnvironmentScope& other) = delete;
|
||||||
|
EnvironmentScope(EnvironmentScope&& other) = delete;
|
||||||
|
EnvironmentScope& operator=(const EnvironmentScope& other) = delete;
|
||||||
|
EnvironmentScope& operator=(EnvironmentScope&& other) = delete;
|
||||||
|
|
||||||
|
EnvironmentScope(Environment& environment);
|
||||||
|
~EnvironmentScope();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Environment ENV;
|
||||||
21
include/interface.hpp
Normal file
21
include/interface.hpp
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "wirekit.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)();
|
||||||
|
|
||||||
|
// Exposure to wirekit is achieved by adding these functions to rewire's symbol tables
|
||||||
|
#define EXPOSED __attribute__((visibility("default")))
|
||||||
|
|
||||||
|
EXPOSED err_t rewire_syscall_hook(reg_t syscall, hook_t entry, hook_t exit);
|
||||||
|
EXPOSED err_t rewire_syscall_unhook(reg_t syscall);
|
||||||
|
EXPOSED err_t rewire_syscall_hook_default(hook_t entry, hook_t exit);
|
||||||
|
EXPOSED err_t rewire_syscall_unhook_default();
|
||||||
|
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);
|
||||||
33
include/wirekit.hpp
Normal file
33
include/wirekit.hpp
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#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;
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
// Exposed rewire functions - to be used by wirekit
|
||||||
|
err_t rewire_syscall_hook(reg_t syscall, hook_t entry, hook_t exit);
|
||||||
|
err_t rewire_syscall_unhook(reg_t syscall); // Alias for rewire_syscall_hook(syscall, nullptr, nullptr)
|
||||||
|
err_t rewire_syscall_hook_default(hook_t entry, hook_t exit);
|
||||||
|
err_t rewire_syscall_unhook_default(); // Alias for rewire_syscall_hook_default(nullptr, nullptr);
|
||||||
|
err_t rewire_subject_id(pid_t* pid);
|
||||||
|
err_t rewire_subject_get_regs(user_regs_struct* regs);
|
||||||
|
err_t rewire_subject_set_regs(const user_regs_struct* regs);
|
||||||
|
err_t rewire_subject_load_cstr(const char* subject_addr, char* buf, uint32_t* read, uint32_t n);
|
||||||
|
|
||||||
|
// Wirekit control functions - called by rewire for setup
|
||||||
|
err_t wirekit_prepare(); // NOCTX - Called when wirekit is loaded
|
||||||
|
err_t wirekit_command_start(int argc, const char* const* argv); // NOCTX - Called before command is executed
|
||||||
|
void wirekit_command_exit(); // Optional, NOCTX - Called after command finished executing
|
||||||
|
void wirekit_subject_start(); // Optional - Called when a new thread starts working on the current command
|
||||||
|
void wirekit_subject_exit(); // Optional - Called when a thread stops working on the current command
|
||||||
|
|
||||||
|
}
|
||||||
182
src/environment.cpp
Normal file
182
src/environment.cpp
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
#include "environment.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
Environment ENV;
|
||||||
|
|
||||||
|
// Trace context collection
|
||||||
|
|
||||||
|
TraceContextCollection::TraceContextCollection(TraceContextCollection&& other) {
|
||||||
|
m_contexts = std::move(other.m_contexts);
|
||||||
|
m_active = other.m_active;
|
||||||
|
other.context_clear_active();
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceContextCollection& TraceContextCollection::operator=(TraceContextCollection&& other) {
|
||||||
|
if (this == &other)
|
||||||
|
return *this;
|
||||||
|
|
||||||
|
m_contexts = std::move(other.m_contexts);
|
||||||
|
m_active = other.m_active;
|
||||||
|
other.context_clear_active();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceContextCollection::TraceContextCollection() {}
|
||||||
|
|
||||||
|
TraceContextCollection::~TraceContextCollection() {}
|
||||||
|
|
||||||
|
bool TraceContextCollection::add_context(pid_t pid) {
|
||||||
|
if (m_contexts.contains(pid))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::unique_ptr<TraceContext> new_context = std::make_unique<TraceContext>();
|
||||||
|
if (!new_context)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
new_context->pid = pid;
|
||||||
|
new_context->mode = TraceContext::EXECUTION_MODE::STARTING;
|
||||||
|
std::memset(&new_context->regs, 0, sizeof(new_context->regs));
|
||||||
|
new_context->regs_dirty = false;
|
||||||
|
|
||||||
|
m_contexts.emplace(pid, std::move(new_context));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceContextCollection::delete_context(pid_t pid) {
|
||||||
|
if (m_contexts.contains(pid))
|
||||||
|
m_contexts.erase(pid);
|
||||||
|
if (m_active == pid)
|
||||||
|
context_clear_active();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceContextCollection::clear() {
|
||||||
|
m_contexts.clear();
|
||||||
|
context_clear_active();
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceContext* TraceContextCollection::context(pid_t pid) {
|
||||||
|
if (!m_contexts.contains(pid))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return m_contexts.at(pid).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const TraceContext* TraceContextCollection::context(pid_t pid) const {
|
||||||
|
if (!m_contexts.contains(pid))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return m_contexts.at(pid).get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceContextCollection::context_set_active(pid_t pid) {
|
||||||
|
if (m_contexts.contains(pid))
|
||||||
|
m_active = pid;
|
||||||
|
else
|
||||||
|
context_clear_active();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TraceContextCollection::context_clear_active() {
|
||||||
|
m_active = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceContext* TraceContextCollection::context_get_active() {
|
||||||
|
return context(m_active);
|
||||||
|
}
|
||||||
|
|
||||||
|
const TraceContext* TraceContextCollection::context_get_active() const {
|
||||||
|
return context(m_active);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Environment
|
||||||
|
|
||||||
|
Environment::Environment() {
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
Environment::~Environment() {}
|
||||||
|
|
||||||
|
void Environment::register_hooks(reg_t syscall, hook_t entry, hook_t exit) {
|
||||||
|
if (!entry && !exit) {
|
||||||
|
if (m_hooks.contains(syscall))
|
||||||
|
m_hooks.erase(syscall);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_hooks.contains(syscall))
|
||||||
|
m_hooks.emplace(syscall, HookPair{});
|
||||||
|
|
||||||
|
HookPair& hooks = m_hooks.at(syscall);
|
||||||
|
hooks.entry = entry;
|
||||||
|
hooks.exit = exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Environment::register_default_hooks(hook_t entry, hook_t exit) {
|
||||||
|
m_default_hooks.entry = entry;
|
||||||
|
m_default_hooks.exit = exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Environment::entry_hook(reg_t syscall) const {
|
||||||
|
if (!m_hooks.contains(syscall))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const HookPair& hooks = m_hooks.at(syscall);
|
||||||
|
if (!hooks.entry)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
hooks.entry();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Environment::exit_hook(reg_t syscall) const {
|
||||||
|
if (!m_hooks.contains(syscall))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const HookPair& hooks = m_hooks.at(syscall);
|
||||||
|
if (!hooks.exit)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
hooks.exit();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Environment::default_entry_hook() const {
|
||||||
|
if (!m_default_hooks.entry)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_default_hooks.entry();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Environment::default_exit_hook() const {
|
||||||
|
if (!m_default_hooks.exit)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_default_hooks.exit();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TraceContextCollection& Environment::contexts() {
|
||||||
|
return m_trace_contexts;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TraceContextCollection& Environment::contexts() const {
|
||||||
|
return m_trace_contexts;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Environment::clear() {
|
||||||
|
m_hooks.clear();
|
||||||
|
m_default_hooks.entry = nullptr;
|
||||||
|
m_default_hooks.exit = nullptr;
|
||||||
|
m_trace_contexts.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnvironmentScope to clear Environment on scope exit
|
||||||
|
|
||||||
|
EnvironmentScope::EnvironmentScope(Environment& environment) : m_environment{ environment } {}
|
||||||
|
|
||||||
|
EnvironmentScope::~EnvironmentScope() {
|
||||||
|
m_environment.clear();
|
||||||
|
}
|
||||||
|
|
||||||
88
src/interface.cpp
Normal file
88
src/interface.cpp
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
#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;
|
||||||
|
}
|
||||||
|
|
||||||
271
src/main.cpp
271
src/main.cpp
@ -1,9 +1,20 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <filesystem>
|
#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 "cli.hpp"
|
||||||
#include "dl.hpp"
|
#include "dl.hpp"
|
||||||
#include "install.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) {
|
int launch_help(int argc, char** argv) {
|
||||||
std::cout << "Use rewire via one of the following commands:" << std::endl;
|
std::cout << "Use rewire via one of the following commands:" << std::endl;
|
||||||
@ -95,6 +106,200 @@ int launch_list(int argc, char** argv) {
|
|||||||
return 0;
|
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) {
|
int launch_run(int argc, char** argv) {
|
||||||
if (argc < 2) {
|
if (argc < 2) {
|
||||||
std::cout << "Invalid usage, try \"rewire help\" for more information!" << std::endl;
|
std::cout << "Invalid usage, try \"rewire help\" for more information!" << std::endl;
|
||||||
@ -119,16 +324,64 @@ int launch_run(int argc, char** argv) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Collect wirekit control function handles
|
||||||
TODO
|
if (!(wirekit_prepare_handle = (err_t(*)())wirekit.resolve("wirekit_prepare"))) {
|
||||||
environment.hpp/cpp
|
std::cout << "Wirekit is missing \"wirekit_prepare\" implementation!" << std::endl;
|
||||||
class Environment -> kit handles, hooks, trace contexts
|
return 1;
|
||||||
class CommandScope -> hooks and trace contexts can only be added while scope holds environment, all cleared on scope exit
|
}
|
||||||
class TraceContext -> execution data on individual tracee threads
|
|
||||||
class TraceContextCollection -> management of all trace contexts and selection of active context
|
|
||||||
*/
|
|
||||||
|
|
||||||
// TODO -> Execute single command (argc > 2) or shell
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user