Public release 0.1

This commit is contained in:
Adam Macdonald
2025-01-31 18:28:43 +00:00
commit 984b49d683
19 changed files with 1538 additions and 0 deletions

461
donIV/src/inject.cpp Normal file
View File

@@ -0,0 +1,461 @@
#include "inject.hpp"
#include "process.hpp"
#include "ptrace.hpp"
#include "syscalls.hpp"
#include <algorithm>
#include <array>
#include <bit>
#include <cassert>
#include <cerrno>
#include <csignal>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <format>
#include <optional>
#include <print>
#include <sys/ptrace.h>
#include <system_error>
#include <variant>
#include <vector>
extern "C"
{
#include <dlfcn.h>
#include <elf.h>
#include <sys/mman.h>
#include <sys/signal.h>
#include <sys/syscall.h>
#include <sys/user.h>
#include <unistd.h>
#include <linux/ptrace.h> // Must be included after <sys/ptrace.h>
}
#include "dlopen_shellcode.hpp"
using namespace std::string_view_literals;
static constexpr const std::string_view LIBC_SO_NAME = "libc.so.6"sv;
static constexpr const char DLOPEN_SYMBOL_NAME[7] = "dlopen";
static constexpr const std::array<std::uint8_t, 2> SYSCALL_ENCODING {0x0f, 0x05};
std::optional<void*> resolve_remote_dlopen(const process& process, std::uintptr_t libc_base_addr) {
void* ret = nullptr;
std::array<char, EI_NIDENT> ident_buf {};
auto maybe_read = process.read_memory(reinterpret_cast<void*>(libc_base_addr), ident_buf.data(), ident_buf.size());
if (!maybe_read.has_value()) {
std::println(stderr,
"[inject] Failed to read e_ident from target libc: {} ({})",
maybe_read.error().message(),
maybe_read.error().value());
return std::nullopt;
}
if (std::memcmp(ident_buf.data(), ELFMAG, SELFMAG) != 0) {
std::println(stderr, "[inject] e_ident did not match");
std::for_each(ident_buf.cend(), ident_buf.cend(), [](const char c) {
std::print("> {:#04x}", c);
});
return std::nullopt;
}
if (ident_buf[4] != ELFCLASS64) {
std::println(stderr, "[inject] Only supporting ELFCLASS64 for now. Bailing.");
return std::nullopt;
}
Elf64_Ehdr ehdr {};
maybe_read = process.read_memory(reinterpret_cast<void*>(libc_base_addr), &ehdr, sizeof(ehdr));
if (!maybe_read.has_value()) {
std::println(stderr,
"[inject] Failed to read ELF header from target libc: {} ({})",
maybe_read.error().message(),
maybe_read.error().value());
return std::nullopt;
}
if (!ehdr.e_phoff) {
std::println(stderr, "[inject] Program header offset is null");
return std::nullopt;
}
for (std::uint32_t i = 0; i < ehdr.e_phnum; ++i) {
Elf64_Phdr phdr {};
maybe_read = process.read_memory(
reinterpret_cast<void*>((libc_base_addr + ehdr.e_phoff) + (i * ehdr.e_phentsize)), &phdr, sizeof(phdr));
if (!maybe_read.has_value()) {
std::println(stderr,
"[inject] Failed to read program header from target libc: {} ({})",
maybe_read.error().message(),
maybe_read.error().value());
return std::nullopt;
}
if (phdr.p_type != PT_DYNAMIC)
continue;
std::uint64_t str_tab_addr = 0;
std::uint64_t str_tab_size = 0;
std::uint64_t sym_tab_addr = 0;
std::uint64_t sym_tab_ent_size = 0;
const std::uint64_t dyn_start_addr = libc_base_addr + phdr.p_vaddr;
const std::uint64_t dyn_end_addr = dyn_start_addr + phdr.p_memsz;
for (std::uint64_t j = dyn_start_addr; j < dyn_end_addr; j += sizeof(Elf64_Dyn)) {
Elf64_Dyn dyn {};
maybe_read = process.read_memory(reinterpret_cast<void*>(j), &dyn, sizeof(dyn));
if (!maybe_read.has_value()) {
std::println(stderr,
"[inject] Failed to read dynamic linking information from target libc: {} ({})",
maybe_read.error().message(),
maybe_read.error().value());
return std::nullopt;
}
if (dyn.d_tag == DT_STRTAB)
str_tab_addr = dyn.d_un.d_ptr;
else if (dyn.d_tag == DT_STRSZ)
str_tab_size = dyn.d_un.d_val;
else if (dyn.d_tag == DT_SYMTAB)
sym_tab_addr = dyn.d_un.d_ptr;
else if (dyn.d_tag == DT_SYMENT)
sym_tab_ent_size = dyn.d_un.d_val;
}
if (!str_tab_addr || !str_tab_size || !sym_tab_addr || !sym_tab_ent_size) {
std::println(stderr, "[inject] Failed to find runtime symbol & string table in libc");
return std::nullopt;
}
// Relevant to the cur_addr < str_tab_addr comparison in the loop below
if (sym_tab_addr >= str_tab_addr) {
std::println(stderr, "[inject] Just let the developer know 🙄");
return std::nullopt;
}
for (std::uint64_t cur_addr = sym_tab_addr; cur_addr < str_tab_addr; cur_addr += sizeof(Elf64_Sym)) {
Elf64_Sym sym {};
maybe_read = process.read_memory(reinterpret_cast<void*>(cur_addr), &sym, sizeof(sym));
if (!maybe_read.has_value()) {
std::println(stderr,
"[inject] Failed to read symbol from target libc: {} ({})",
maybe_read.error().message(),
maybe_read.error().value());
return std::nullopt;
}
if (sym.st_name == 0 || sym.st_value == 0)
continue;
std::array<char, sizeof(DLOPEN_SYMBOL_NAME)> name_buf {};
maybe_read = process.read_memory(
reinterpret_cast<void*>(str_tab_addr + sym.st_name), name_buf.data(), sizeof(name_buf));
if (!maybe_read.has_value()) {
std::println(stderr,
"[inject] Failed to read symbol name from target libc: {} ({})",
maybe_read.error().message(),
maybe_read.error().value());
return std::nullopt;
}
if (std::strcmp(name_buf.data(), DLOPEN_SYMBOL_NAME) != 0)
continue;
ret = reinterpret_cast<void*>(libc_base_addr + sym.st_value);
break;
}
if (ret != nullptr)
break;
}
if (ret == nullptr)
return std::nullopt;
return ret;
}
bool inject_library(const std::filesystem::path& library_path, const process& process) {
// Attach
// Note to self:
// ptrace goes: op, pid, addr, data
// Meaning of addr and data changes depending on the ptrace request
// See: man 2 ptrace
const process::pid_t pid = process.pid();
const std::string path_str = library_path.string();
const auto attached = ptrace_cpp::attach_to_process(pid);
if (!attached) {
std::println(stderr,
"[ptrace] Failed to attach to remote process: {} ({})",
attached.error().message(),
attached.error().value());
return false;
}
const auto first_wait = ptrace_cpp::wait_on_process(pid);
if (std::get_if<ptrace_cpp::sig_stop_t>(&first_wait) == nullptr &&
std::get_if<ptrace_cpp::syscall_info_t>(&first_wait) == nullptr) {
std::println(stderr, "[inject] Remote process did not stop by signal or syscall");
ptrace_cpp::debug::print_wait_status(first_wait);
return false;
}
ptrace_cpp::debug::print_wait_status(first_wait);
// Find a SYSCALL instruction (0x0f05) to jump from for use with mmap() & munmap()
const auto libc_mem_regions = process.get_library_memory_regions(LIBC_SO_NAME);
if (libc_mem_regions.empty()) {
std::println(stderr, "[inject] Failed to get libc's memory map");
return false;
}
const auto exec_region_it =
std::find_if(libc_mem_regions.cbegin(), libc_mem_regions.cend(), [](const memory_region& r) {
return r.execute;
});
const auto syscall_instn_locs =
process.find_bytes_in_memory(reinterpret_cast<void*>(exec_region_it->start_addr),
(exec_region_it->end_addr - exec_region_it->start_addr),
SYSCALL_ENCODING);
if (!syscall_instn_locs) {
std::println(stderr,
"[inject] Failed to search for byte sequence in memory: {} ({})",
syscall_instn_locs.error().message(),
syscall_instn_locs.error().value());
return false;
}
if (syscall_instn_locs.value().empty()) {
std::println(stderr, "[inject] Did not find any SYSCALL instructions (0x0F05) sequences in memory");
return false;
}
const auto& syscall_locs_vec = syscall_instn_locs.value();
// Store signal & execution state
const auto orig_regs = ptrace_cpp::get_regs(pid);
if (!orig_regs) {
std::println(stderr, "[inject] Failed to get remote process' register state");
return false;
}
const auto set_opts = ptrace_cpp::set_options(pid, PTRACE_O_TRACESYSGOOD);
if (!set_opts) {
std::println(stderr,
"[ptrace] Failed to set ptrace options: {} ({})",
set_opts.error().message(),
set_opts.error().value());
return false;
}
const auto target_loaded_libs = process.get_loaded_libraries();
const auto target_libc_it = std::find_if(target_loaded_libs.cbegin(), target_loaded_libs.cend(), [](const auto& a) {
return a.first.contains(LIBC_SO_NAME);
});
if (target_libc_it == target_loaded_libs.cend()) {
std::println(stderr, "[inject] Failed to find libc in memory regions map");
return false;
}
const auto dlopen = resolve_remote_dlopen(process, target_libc_it->second[0].start_addr);
if (!dlopen.has_value()) {
std::println(stderr, "[inject] Failed to resolve dlopen() in remote process' libc");
return false;
}
std::println("[inject] Resolved dlopen() in remote process: {:#x}",
reinterpret_cast<std::uint64_t>(dlopen.value()));
// Call mmap()
const auto shcode_alloc_size = std::bit_ceil(bin::dlopen_shellcode.size());
const auto path_str_alloc_size = std::bit_ceil(path_str.size() + 1);
const auto write_buf_size = shcode_alloc_size + path_str_alloc_size;
user_regs_struct mmap_regs = orig_regs.value();
mmap_regs.rax = syscalls::x64::mmap; // mmap syscall number (x86_64)
mmap_regs.rdi = 0x0; // addr, NULL for any address
mmap_regs.rsi = write_buf_size; // length
mmap_regs.rdx = PROT_READ | PROT_WRITE | PROT_EXEC; // prot, all perms
mmap_regs.r10 = MAP_PRIVATE | MAP_ANONYMOUS; // flags
mmap_regs.r8 = -1; // fd, -1 seems to be "ignore" (?)
mmap_regs.r9 = 0; // offset
mmap_regs.rip = syscall_locs_vec[0];
if (!ptrace_cpp::set_regs(pid, mmap_regs)) {
std::println(stderr, "[ptrace] Failed to set register state for mmap() syscall");
return false;
}
std::println("[inject] Calling mmap({:#x}, {}, {:#x}, {:#x}, {:#x}, {})",
mmap_regs.rdi,
mmap_regs.rsi,
mmap_regs.rdx,
mmap_regs.r10,
mmap_regs.r8,
mmap_regs.r9);
bool seen_mmap_entry = false;
bool seen_mmap_exit = false;
std::uintptr_t remote_alloc_addr = 0;
while (!(seen_mmap_entry && seen_mmap_exit)) {
const auto cont = ptrace_cpp::continue_to_next_syscall(pid);
if (!cont) {
std::println(stderr,
"[ptrace] Failed to continue process execution: {} ({})",
cont.error().message(),
cont.error().value());
return false;
}
const auto mmap_wait = ptrace_cpp::wait_on_process(pid);
ptrace_cpp::debug::print_wait_status(mmap_wait);
if (std::get_if<ptrace_cpp::syscall_stop_t>(&mmap_wait) == nullptr)
continue;
const auto& sys_stop = std::get<ptrace_cpp::syscall_stop_t>(mmap_wait);
if (sys_stop.op == PTRACE_SYSCALL_INFO_ENTRY) {
seen_mmap_entry = (sys_stop.entry.nr == syscalls::x64::mmap);
} else if (sys_stop.op == PTRACE_SYSCALL_INFO_EXIT) {
if (sys_stop.exit.is_error) {
std::println(stderr, "[inject] mmap() syscall failed");
return false;
}
remote_alloc_addr = sys_stop.exit.rval;
seen_mmap_exit = seen_mmap_entry;
}
}
std::println("[inject] Allocated memory in remote process at {:#x}", remote_alloc_addr);
std::vector<std::uint8_t> write_buf(write_buf_size);
std::copy(bin::dlopen_shellcode.cbegin(), bin::dlopen_shellcode.cend(), write_buf.begin());
const auto strcopy_end_it = std::copy(path_str.cbegin(), path_str.cend(), write_buf.begin() + shcode_alloc_size);
*strcopy_end_it = '\0'; // Null terminator
const auto remote_lib_path_addr = remote_alloc_addr + shcode_alloc_size;
std::println("[inject] Writing shellcode & library path to {:#x} and {:#x}, total {} bytes",
remote_alloc_addr,
remote_lib_path_addr,
write_buf.size());
process.write_memory((void*) remote_alloc_addr, (void*) &write_buf[0], write_buf.size()).value();
// Call the shellcode similarly to mmap
user_regs_struct shellcode_regs = orig_regs.value();
shellcode_regs.rip = remote_alloc_addr;
shellcode_regs.rdi = reinterpret_cast<std::uintptr_t>(dlopen.value());
shellcode_regs.rsi = remote_lib_path_addr;
if (!ptrace_cpp::set_regs(pid, shellcode_regs)) {
std::println(stderr, "[ptrace] Failed to set register state for shellcode");
return false;
}
std::println("[inject] Running shellcode ...");
bool shellcode_success = false;
while (!shellcode_success) {
const auto cont = ptrace_cpp::continue_to_next_syscall(pid);
if (!cont) {
std::println(stderr,
"[ptrace] Failed to continue process execution: {} ({})",
cont.error().message(),
cont.error().value());
return false;
}
const auto shellcode_wait = ptrace_cpp::wait_on_process(pid);
ptrace_cpp::debug::print_wait_status(shellcode_wait);
if (std::get_if<ptrace_cpp::syscall_stop_t>(&shellcode_wait)) {
const auto sys_stop = std::get<ptrace_cpp::syscall_stop_t>(shellcode_wait);
if (sys_stop.op == PTRACE_SYSCALL_INFO_ENTRY) {
shellcode_success = (sys_stop.entry.nr == syscalls::x64::kill);
}
}
}
// Zero out the old memory
std::fill(write_buf.begin(), write_buf.end(), '\0');
process.write_memory((void*) remote_alloc_addr, &write_buf[0], write_buf_size).value();
std::println("[inject] Zeroed memory in remote process");
// Call munmap()
// We end the shellcode on a syscall-enter-stop to kill(), swap orig_rax and wait for syscall-exit-stop
user_regs_struct munmap_regs = orig_regs.value();
munmap_regs.orig_rax = syscalls::x64::munmap; // munmap syscall number (x86_64)
munmap_regs.rdi = remote_alloc_addr; // addr
munmap_regs.rsi = write_buf_size; // length
std::println("[inject] Calling munmap({:#x}, {})", munmap_regs.rdi, munmap_regs.rsi);
if (!ptrace_cpp::set_regs(pid, munmap_regs)) {
std::println(stderr, "[ptrace] Failed to set register state for munmap() call");
return false;
}
bool seen_munmap_exit = false;
while (!seen_munmap_exit) {
const auto cont = ptrace_cpp::continue_to_next_syscall(pid);
if (!cont) {
std::println(stderr,
"[ptrace] Failed to continue process execution: {} ({})",
cont.error().message(),
cont.error().value());
return false;
}
const auto munmap_wait = ptrace_cpp::wait_on_process(pid);
ptrace_cpp::debug::print_wait_status(munmap_wait);
if (std::get_if<ptrace_cpp::syscall_stop_t>(&munmap_wait) == nullptr)
continue;
const auto sys_stop = std::get<ptrace_cpp::syscall_stop_t>(munmap_wait);
if (sys_stop.op == PTRACE_SYSCALL_INFO_EXIT) {
seen_munmap_exit = true;
if (sys_stop.exit.is_error) {
std::println(stderr, "[inject] WARNING: munmap() call failed");
}
}
}
std::println("[inject] Unmapped memory in remote process");
// Restore regs and signal
ptrace_cpp::set_regs(pid, orig_regs.value()).value();
std::println("[inject] Restoring original register state");
// Detach
const auto detached = ptrace_cpp::detach_from_process(pid);
if (!detached) {
std::println(stderr,
"[ptrace] Remote process failed to detach: {} ({})",
detached.error().message(),
detached.error().value());
return false;
}
std::println("[inject] Detached from remote process");
return shellcode_success;
}

188
donIV/src/process.cpp Normal file
View File

@@ -0,0 +1,188 @@
#include "process.hpp"
#include <ctre.hpp>
#include <algorithm>
#include <cctype>
#include <cstddef>
#include <cstdint>
#include <expected>
#include <filesystem>
#include <format>
#include <fstream>
#include <iterator>
#include <optional>
#include <print>
#include <string>
#include <system_error>
#include <unordered_map>
#include <utility>
extern "C"
{
#include <signal.h>
#include <sys/types.h>
#include <sys/uio.h>
}
std::expected<std::size_t, std::error_code> process::read_memory(void* target_addr,
void* local_addr,
std::size_t len) const noexcept {
const iovec local {.iov_base = local_addr, .iov_len = len};
const iovec remote {.iov_base = target_addr, .iov_len = len};
const auto n_bytes_read = process_vm_readv(m_process_id, &local, 1, &remote, 1, 0);
if (n_bytes_read <= 0) {
return std::unexpected(std::make_error_code(static_cast<std::errc>(errno)));
}
return n_bytes_read;
}
std::expected<std::size_t, std::error_code> process::write_memory(void* target_addr,
void* local_addr,
std::size_t len) const noexcept {
const iovec local {.iov_base = local_addr, .iov_len = len};
const iovec remote {.iov_base = target_addr, .iov_len = len};
const auto n_bytes_written = process_vm_writev(m_process_id, &local, 1, &remote, 1, 0);
if (n_bytes_written <= 0) {
return std::unexpected(std::make_error_code(static_cast<std::errc>(errno)));
}
return n_bytes_written;
}
std::expected<std::vector<std::uintptr_t>, std::error_code> process::find_bytes_in_memory(
void* start_addr, std::size_t len, std::span<const std::uint8_t> bytes) const noexcept {
static constexpr const std::size_t max_fetch_size = 0x1000;
const std::size_t fetch_size = (len < max_fetch_size) ? len : max_fetch_size;
std::vector<std::uintptr_t> ret_vec;
std::vector<std::uint8_t> buf(fetch_size);
for (std::size_t offset = 0; offset < len; offset += fetch_size) {
const auto read_addr = reinterpret_cast<std::uintptr_t>(start_addr) + offset;
const auto bytes_read = read_memory(reinterpret_cast<void*>(read_addr), &buf[0], fetch_size);
if (!bytes_read) {
return std::unexpected(bytes_read.error());
}
auto it = buf.cbegin();
while (it != buf.cend()) {
it = std::search(it, buf.cend(), bytes.cbegin(), bytes.cend());
if (it == buf.cend())
continue;
ret_vec.push_back(read_addr + std::distance(buf.cbegin(), it));
std::advance(it, bytes.size());
}
}
return ret_vec;
}
std::expected<void, std::error_code> process::send_signal(int signum) const noexcept {
if (kill(m_process_id, signum) < 0) {
return std::unexpected(std::make_error_code(static_cast<std::errc>(errno)));
}
return {};
}
std::optional<std::filesystem::path> process::get_exe_path() const {
std::filesystem::path proc_exe_path(std::format("/proc/{}/exe", m_process_id));
try {
const auto exists = std::filesystem::exists(proc_exe_path);
const auto is_symlink = std::filesystem::is_symlink(proc_exe_path);
if (exists && is_symlink) {
return std::filesystem::read_symlink(proc_exe_path);
} else {
return std::nullopt;
}
} catch (const std::filesystem::filesystem_error& ex) {
return std::nullopt;
}
};
std::vector<memory_region> process::get_memory_regions() const {
std::vector<memory_region> ret_vec;
const std::filesystem::path proc_maps_path(std::format("/proc/{}/maps", m_process_id));
std::ifstream stream(proc_maps_path, std::ios::in);
static constexpr auto regex_string = ctll::fixed_string {
R"(^([0-9a-fA-F]+)\-([0-9a-fA-F]+)\s([r|w|x|s|p|\-]{4})\s([0-9a-fA-F]+)\s([0-9a-fA-F]{2}):(["
"0-9a-fA-F]{2})\s(\d+)\s+(.*)$)"};
std::string curr_line;
while (!stream.eof() && !stream.bad()) {
std::getline(stream, curr_line);
if (curr_line.empty())
continue;
const auto match = ctre::match<regex_string>(curr_line);
const auto start_addr = std::stoull(match.get<1>().str(), 0, 16);
const auto end_addr = std::stoull(match.get<2>().str(), 0, 16);
const auto& permissions = match.get<3>().str();
const auto offset = std::stoull(match.get<4>().str(), 0, 16);
const std::uint8_t dev_major = stoul(match.get<5>().str(), 0, 16);
const std::uint8_t dev_minor = stoul(match.get<6>().str(), 0, 16);
const auto inode = stoull(match.get<7>().str(), 0, 10);
const auto path_name = match.get<8>().str();
ret_vec.emplace_back(start_addr,
end_addr,
offset,
path_name,
dev_major,
dev_minor,
inode,
permissions[0] == 'r',
permissions[1] == 'w',
permissions[2] == 'x',
permissions[3] == 'p',
permissions[3] == 's');
}
return ret_vec;
}
std::vector<memory_region> process::get_library_memory_regions(std::string_view lib_name) const {
const auto memory_regions = get_memory_regions();
std::vector<memory_region> lib_mem_regions;
std::copy_if(memory_regions.cbegin(),
memory_regions.cend(),
std::back_inserter(lib_mem_regions),
[&lib_name](const auto& x) {
return x.path_name.contains(lib_name);
});
return lib_mem_regions;
}
process::lib_map_t process::get_loaded_libraries() const {
process::lib_map_t ret_map {};
const auto memory_regions = get_memory_regions();
ret_map.reserve(memory_regions.size());
for (const auto& region : memory_regions) {
if (region.path_name.empty())
continue;
if (ret_map.contains(region.path_name)) {
auto& it = ret_map.at(region.path_name);
it.push_back(region);
} else {
std::vector<memory_region> new_v {};
auto inserted = ret_map.insert({region.path_name, std::move(new_v)});
if (inserted.second)
inserted.first->second.push_back(region);
}
}
return ret_map;
}

182
donIV/src/ptrace.cpp Normal file
View File

@@ -0,0 +1,182 @@
#include "ptrace.hpp"
#include <asm/ptrace-abi.h>
#include <bitset>
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <linux/ptrace.h>
#include <print>
#include <sys/ptrace.h>
#include <utility>
extern "C"
{
#include <sys/wait.h>
}
using pt_req = __ptrace_request;
using ptrace_cpp::int_expected_t;
using ptrace_cpp::regs_expected_t;
using ptrace_cpp::syscall_info_expected_t;
using ptrace_cpp::void_expected_t;
using ptrace_cpp::wait_status_type_t;
void_expected_t ptrace_cpp::attach_to_process(std::uint32_t pid) {
const auto err = ::ptrace(static_cast<pt_req>(PTRACE_ATTACH), pid, nullptr, nullptr);
if (err < 0) {
return std::unexpected(std::make_error_code(static_cast<std::errc>(errno)));
}
return {};
}
void_expected_t ptrace_cpp::detach_from_process(std::uint32_t pid) {
const auto err = ::ptrace(static_cast<pt_req>(PTRACE_DETACH), pid, nullptr, nullptr);
if (err < 0) {
return std::unexpected(std::make_error_code(static_cast<std::errc>(errno)));
}
return {};
}
void_expected_t ptrace_cpp::set_options(std::uint32_t pid, std::bitset<32> options) {
const auto err = ::ptrace(static_cast<pt_req>(PT_SETOPTIONS), pid, nullptr, options.to_ulong());
if (err < 0) {
return std::unexpected(std::make_error_code(static_cast<std::errc>(errno)));
}
return {};
}
regs_expected_t ptrace_cpp::get_regs(std::uint32_t pid) {
user_regs_struct regs {};
const auto err = ::ptrace(static_cast<pt_req>(PTRACE_GETREGS), pid, nullptr, &regs);
if (err < 0) {
return std::unexpected(std::make_error_code(static_cast<std::errc>(errno)));
}
return regs;
}
syscall_info_expected_t ptrace_cpp::get_syscall_info(std::uint32_t pid) {
ptrace_syscall_info syscall_info {};
const auto err = ::ptrace(static_cast<pt_req>(PTRACE_GET_SYSCALL_INFO), pid, sizeof(syscall_info), &syscall_info);
if (err < 0) {
return std::unexpected(std::make_error_code(static_cast<std::errc>(errno)));
}
return syscall_info;
}
void_expected_t ptrace_cpp::set_regs(std::uint32_t pid, const user_regs_t& regs) {
const auto err = ::ptrace(static_cast<pt_req>(PTRACE_SETREGS), pid, nullptr, &regs);
if (err < 0) {
return std::unexpected(std::make_error_code(static_cast<std::errc>(errno)));
}
return {};
}
wait_status_type_t ptrace_cpp::wait_on_process(std::uint32_t pid) {
int status = 0;
const auto err = ::waitpid(pid, &status, 0);
if (err < 0) {
return std::make_error_code(static_cast<std::errc>(errno));
}
if (WIFSTOPPED(status)) {
const int signal = ptrace_cpp::status::status_to_stop_sig(status);
if (ptrace_cpp::signal::is_syscall(signal)) {
auto syscall_info = ptrace_cpp::get_syscall_info(pid);
if (!syscall_info)
return std::make_error_code(static_cast<std::errc>(errno));
return ptrace_cpp::syscall_stop_t(syscall_info.value());
} else {
return ptrace_cpp::sig_stop_t(signal);
}
} else if (WIFEXITED(status)) {
return ptrace_cpp::exited_t(WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
return ptrace_cpp::term_by_t(WTERMSIG(status));
} else if (WIFCONTINUED(status)) {
return ptrace_cpp::continued_t();
} else {
return ptrace_cpp::continued_t();
}
return ptrace_cpp::continued_t();
}
void_expected_t ptrace_cpp::continue_execution(std::uint32_t pid, int sig_num) {
const auto err = ::ptrace(static_cast<pt_req>(PTRACE_CONT), pid, nullptr, sig_num);
if (err < 0) {
return std::unexpected(std::make_error_code(static_cast<std::errc>(errno)));
}
return {};
}
void_expected_t ptrace_cpp::continue_to_next_syscall(std::uint32_t pid, int sig_num) {
const auto err = ::ptrace(static_cast<pt_req>(PTRACE_SYSCALL), pid, nullptr, sig_num);
if (err < 0) {
return std::unexpected(std::make_error_code(static_cast<std::errc>(errno)));
}
return {};
}
void_expected_t ptrace_cpp::singlestep(std::uint32_t pid, int sig_num) {
const auto err = ::ptrace(static_cast<pt_req>(PTRACE_SINGLESTEP), pid, nullptr, sig_num);
if (err < 0) {
return std::unexpected(std::make_error_code(static_cast<std::errc>(errno)));
}
return {};
}
void ptrace_cpp::debug::print_user_regs(const user_regs_t& regs) {
std::println("[ptrace] -- RIP: {:#018x}", regs.rip);
std::println("[ptrace] -- RAX: {:#018x} (Original)", regs.orig_rax);
std::println("[ptrace] -- RAX: {:#018x}", regs.rax);
std::println("[ptrace] -- RBX: {:#018x}", regs.rbx);
std::println("[ptrace] -- RCX: {:#018x}", regs.rcx);
std::println("[ptrace] -- RDX: {:#018x}", regs.rdx);
std::println("[ptrace] -- RDI: {:#018x}", regs.rdi);
std::println("[ptrace] -- RSI: {:#018x}", regs.rsi);
return;
}
void ptrace_cpp::debug::print_wait_status(const wait_status_type_t& wait) {
std::visit(
[&wait](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, ptrace_cpp::sig_stop_t>) {
const auto sig_stop = std::get<ptrace_cpp::sig_stop_t>(wait);
std::println("[ptrace] -- Signal stop: {} ({})", strsignal(sig_stop.signal), sig_stop.signal);
} else if constexpr (std::is_same_v<T, ptrace_cpp::syscall_stop_t>) {
const auto sys_stop = std::get<ptrace_cpp::syscall_stop_t>(wait);
if (sys_stop.op == PTRACE_SYSCALL_INFO_ENTRY) {
std::println("[ptrace] -- Syscall entry: {}", sys_stop.entry.nr);
} else if (sys_stop.op == PTRACE_SYSCALL_INFO_EXIT) {
std::println("[ptrace] -- Syscall exit: {}, {}",
sys_stop.exit.is_error ? "error" : "success",
sys_stop.exit.rval);
}
} else if constexpr (std::is_same_v<T, ptrace_cpp::exited_t>)
std::println("[ptrace] -- Process exited: {}", std::get<ptrace_cpp::exited_t>(wait).exit_code);
else if constexpr (std::is_same_v<T, ptrace_cpp::term_by_t>)
std::println("[ptrace] -- Process terminated by signal: {}",
std::get<ptrace_cpp::term_by_t>(wait).term_sig);
else if constexpr (std::is_same_v<T, ptrace_cpp::continued_t>)
std::println("[ptrace] -- Continued ...");
else if constexpr (std::is_same_v<T, std::error_code>) {
const std::error_code& err = std::get<std::error_code>(wait);
std::println("[ptrace] -- Error: {} ({})", err.message(), err.value());
} else
static_assert(false, "non-exhaustive visitor!");
},
wait);
return;
}