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

91
.clang-format Normal file
View File

@ -0,0 +1,91 @@
---
Language: Cpp
BasedOnStyle: Microsoft
# Includes
SortIncludes: CaseInsensitive
IncludeCategories:
# Windows.h comes first to avoid some wonky redefinitions
- Regex: "Windows.h"
Priority: 10000
CaseSensitive: false
# Alignment
AlignAfterOpenBracket: Align
AlignArrayOfStructures: Right
AlignConsecutiveAssignments:
Enabled: true
AcrossEmptyLines: false
AcrossComments: true
AlignCompound: true
AlignFunctionPointers: true
PadOperators: true
AlignConsecutiveDeclarations: true
AlignConsecutiveMacros: true
AlignEscapedNewlines: Left
AlignOperands: AlignAfterOperator
AlignTrailingComments: Always
# Single-line blocks
AllowShortBlocksOnASingleLine: Empty
#AllowShortEnumsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: Empty
AllowShortLoopsOnASingleLine: false
# ???
BinPackArguments: false
BinPackParameters: false
# ???
BitFieldColonSpacing: Both
BreakBeforeBraces: Custom
BraceWrapping:
AfterCaseLabel: true
AfterClass: false
AfterControlStatement: Never
AfterEnum: false
AfterFunction: false
AfterNamespace: true
AfterStruct: false
AfterUnion: false
AfterExternBlock: true
BeforeCatch: false
BeforeElse: false
BeforeLambdaBody: false
BeforeWhile: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
# idk
BreakAfterAttributes: Always
BreakBeforeConceptDeclarations: Allowed
BreakBeforeInlineASMColon: OnlyMultiline
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
BreakStringLiterals: true
PackConstructorInitializers: NextLineOnly # Could change to CurrentLine or NextLine
# idk
ColumnLimit: 120
IndentWidth: 4
TabWidth: 4
InsertNewlineAtEOF: true
# KeepEmptyLines:
# AtEndOfFile: false
# AtStartOfBlock: false
# AtStartOfFile: false
LineEnding: DeriveLF
MaxEmptyLinesToKeep: 1
NamespaceIndentation: Inner
PointerAlignment: Left
QualifierAlignment: Left
# ReflowComments: true
SpaceAfterCStyleCast: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: true
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false

2
.clang-tidy Normal file
View File

@ -0,0 +1,2 @@
Checks: '-*,cppcoreguidelines-*,performance-*,bugprone-*,clang-diagnostic-*,clang-analyzer-*'
WarningsAsErrors: ''

29
.gitignore vendored Normal file
View File

@ -0,0 +1,29 @@
# Visual Studio Code
.vscode/
# IntelliJ IDEs
.idea/
# CMake
build/
target/
out/
# clangd
.cache/
# Build artefacts
*.exe
*.dll
*.lib
*.pdb
*.dylib
*.so
*.o
*.a
# Binaries
# *.bin
# Miscellaneous stuff
.directory

28
CMakeLists.txt Normal file
View File

@ -0,0 +1,28 @@
cmake_minimum_required(VERSION 3.20)
project(
doniv-cli
VERSION 0.1
DESCRIPTION "donIV injector"
LANGUAGES CXX
)
# Depends on donIV library
add_subdirectory("donIV/")
add_executable(
doniv-cli
src/main.cpp
src/cli.cpp
)
target_compile_features(doniv-cli PRIVATE cxx_std_23)
target_link_libraries(doniv-cli doniv ctre)
target_include_directories(doniv-cli PRIVATE "include/" ctre)
# Compile-Time Regular Expressions (CTRE)
FetchContent_Declare(
ctre
GIT_REPOSITORY https://github.com/hanickadot/compile-time-regular-expressions.git
GIT_TAG v3.9.0
)
FetchContent_MakeAvailable(ctre)

16
README.md Normal file
View File

@ -0,0 +1,16 @@
# donIV-cli
A shared library injector for Linux.
## Building
1. Change directory to the root directory of this project
2. `mkdir build`
3. `cmake -S . -B build/`
4. `cd build`
5. `make`
## References
- Man pages: ptrace(2), wait(2), syscalls(2), syscall(2)
- https://x64.syscall.sh/

24
donIV/CMakeLists.txt Normal file
View File

@ -0,0 +1,24 @@
add_library(
doniv
STATIC
src/process.cpp
src/ptrace.cpp
src/inject.cpp
)
target_compile_features(doniv PRIVATE cxx_std_23)
target_include_directories(doniv PUBLIC "include/")
add_subdirectory("shellcode/")
add_dependencies(doniv shellcode)
include(FetchContent)
target_link_libraries(doniv PRIVATE ctre)
target_include_directories(doniv PRIVATE ctre ${SHELLCODE_INCLUDE_DIR})
# Compile-Time Regular Expressions (CTRE)
FetchContent_Declare(
ctre
GIT_REPOSITORY https://github.com/hanickadot/compile-time-regular-expressions.git
GIT_TAG v3.9.0
)
FetchContent_MakeAvailable(ctre)

7
donIV/include/inject.hpp Normal file
View File

@ -0,0 +1,7 @@
#pragma once
#include "process.hpp"
#include <filesystem>
bool inject_library(const std::filesystem::path& library_path, const process& process);

61
donIV/include/process.hpp Normal file
View File

@ -0,0 +1,61 @@
#pragma once
#include <cassert>
#include <cstdint>
#include <expected>
#include <filesystem>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
class memory_region {
public:
std::uintptr_t start_addr = 0x0;
std::uintptr_t end_addr = 0x0;
std::uintptr_t offset = 0x0;
std::string path_name;
std::uint8_t dev_major = 0;
std::uint8_t dev_minor = 0;
std::uint64_t inode = 0;
bool read = false;
bool write = false;
bool execute = false;
bool priv = false;
bool shared = false;
};
class process {
public:
using pid_t = std::uint32_t;
using lib_map_t = std::unordered_map<std::string, std::vector<memory_region>>;
static constexpr pid_t INVALID_PID = 0;
process(pid_t pid)
: m_process_id(pid) {};
public:
inline pid_t pid() const noexcept {
return m_process_id;
}
std::expected<std::size_t, std::error_code> read_memory(void* target_addr,
void* local_addr,
std::size_t len) const noexcept;
std::expected<std::size_t, std::error_code> write_memory(void* target_addr,
void* local_addr,
std::size_t len) const noexcept;
std::expected<std::vector<std::uintptr_t>, std::error_code> find_bytes_in_memory(
void* start_addr, std::size_t len, std::span<const std::uint8_t> bytes) const noexcept;
std::expected<void, std::error_code> send_signal(int signum) const noexcept;
std::optional<std::filesystem::path> get_exe_path() const;
std::vector<memory_region> get_memory_regions() const;
std::vector<memory_region> get_library_memory_regions(std::string_view lib_name) const;
lib_map_t get_loaded_libraries() const;
private:
pid_t m_process_id = INVALID_PID;
};

105
donIV/include/ptrace.hpp Normal file
View File

@ -0,0 +1,105 @@
#pragma once
#include <bitset>
#include <cstdint>
#include <cstdlib>
#include <expected>
#include <system_error>
#include <variant>
extern "C"
{
#include <sys/ptrace.h>
#include <sys/signal.h>
#include <sys/user.h>
#include <linux/ptrace.h> // Must be included after <sys/ptrace.h>
}
namespace ptrace_cpp
{
using void_expected_t = std::expected<void, std::error_code>;
using int_expected_t = std::expected<int, std::error_code>;
using syscall_info_t = ptrace_syscall_info;
using syscall_info_expected_t = std::expected<syscall_info_t, std::error_code>;
using user_regs_t = user_regs_struct;
using regs_expected_t = std::expected<user_regs_t, std::error_code>;
struct sig_stop_t {
int signal = 0;
};
struct exited_t {
int exit_code = 0;
};
struct term_by_t {
int term_sig = 0;
};
struct continued_t {};
using syscall_stop_t = syscall_info_t; // ptrace_syscall_info
using wait_status_type_t = std::variant<sig_stop_t, syscall_stop_t, exited_t, term_by_t, continued_t, std::error_code>;
constexpr int TRACESYSGOOD_TRAP_MASK = (SIGTRAP | 0x80);
void_expected_t attach_to_process(std::uint32_t pid);
void_expected_t detach_from_process(std::uint32_t pid);
void_expected_t set_options(std::uint32_t pid, std::bitset<32> options);
regs_expected_t get_regs(std::uint32_t pid);
syscall_info_expected_t get_syscall_info(std::uint32_t pid);
void_expected_t set_regs(std::uint32_t pid, const user_regs_t& regs);
wait_status_type_t wait_on_process(std::uint32_t pid);
void_expected_t continue_execution(std::uint32_t pid, int sig_num = 0);
void_expected_t continue_to_next_syscall(std::uint32_t pid, int sig_num = 0);
void_expected_t singlestep(std::uint32_t pid, int sig_num = 0);
} // namespace ptrace_cpp
namespace ptrace_cpp::status
{
inline int status_to_stop_sig(int status) noexcept {
return WSTOPSIG(status);
}
inline bool is_stopped(int status) noexcept {
return WIFSTOPPED(status);
}
inline bool is_exited(int status) noexcept {
return WIFEXITED(status);
}
inline bool is_signalled(int status) noexcept {
return WIFSIGNALED(status);
}
inline bool is_continued(int status) noexcept {
return WIFCONTINUED(status);
}
} // namespace ptrace_cpp::status
namespace ptrace_cpp::signal
{
inline bool is_syscall(int sig_num) noexcept {
return sig_num == TRACESYSGOOD_TRAP_MASK;
}
inline int unmask_sysgood(int sig_num) noexcept {
return sig_num & 0x7F;
}
} // namespace ptrace_cpp::signal
namespace ptrace_cpp::debug
{
void print_user_regs(const user_regs_t& regs);
void print_wait_status(const wait_status_type_t& wait);
} // namespace ptrace_cpp::debug

View File

@ -0,0 +1,11 @@
#pragma once
namespace syscalls
{
namespace x64
{
constexpr const unsigned int mmap = 9;
constexpr const unsigned int munmap = 11;
constexpr const unsigned int kill = 62;
} // namespace x64
} // namespace syscalls

View File

@ -0,0 +1,39 @@
set(
SHELLCODE_INCLUDE_DIR
"${CMAKE_CURRENT_BINARY_DIR}/shellcode/"
PARENT_SCOPE
)
set(
SHELLCODE_OUT_DIR
${CMAKE_CURRENT_BINARY_DIR}/shellcode
)
add_custom_command(
OUTPUT ${SHELLCODE_OUT_DIR}/dlopen_shellcode.o
COMMAND as --64 -o ${SHELLCODE_OUT_DIR}/dlopen_shellcode.o ./dlopen_shellcode.s
DEPENDS ./dlopen_shellcode.s
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Assemble dlopen() shellcode (x86_64)"
)
add_custom_command(
OUTPUT ${SHELLCODE_OUT_DIR}/dlopen_shellcode.bin
COMMAND objcopy -j .text -O binary ${SHELLCODE_OUT_DIR}/dlopen_shellcode.o ${SHELLCODE_OUT_DIR}/dlopen_shellcode.bin
DEPENDS ${SHELLCODE_OUT_DIR}/dlopen_shellcode.o
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Copy dlopen() shellcode to binary file (x86_64)"
)
add_custom_command(
OUTPUT ${SHELLCODE_OUT_DIR}/dlopen_shellcode.hpp
COMMAND python bin2cppheader.py ${SHELLCODE_OUT_DIR}/dlopen_shellcode.bin
DEPENDS ${SHELLCODE_OUT_DIR}/dlopen_shellcode.bin
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Convert dlopen() shellcode binary to C++ header (x86_64)"
)
add_custom_target(
shellcode
DEPENDS ${SHELLCODE_OUT_DIR}/dlopen_shellcode.hpp
)

View File

@ -0,0 +1,71 @@
# bin2cppheader.py
# Convert a binary file to its static std::array representation as an
# includable header file in your C++ project.
# Author: Adam Macdonald [https://github.com/twokilohertz]
usage = "Usage: python bin2cppheader.py in_file [out_file]"
import sys
import mmap
from pathlib import Path
# Helper function for printing to stderr
def eprint(*args, **kwargs):
return print(*args, file=sys.stderr, **kwargs)
def read_bin(bin_path):
ret_buf = []
with open(bin_path, "rb") as f:
with mmap.mmap(f.fileno(), length=0, prot=mmap.PROT_READ) as mm:
ret_buf = mm.read()
return ret_buf
return ret_buf
def bin_to_header(data, symbol_name, namespace = "bin"):
ret_str = "#pragma once\n#include <array>\n#include <cstdint>\nnamespace " + namespace + " { constexpr const std::array<std::uint8_t, "+ str(len(data)) + "> " + symbol_name + " {"
for b in data:
ret_str += "{:#04x}".format(b) + ", "
ret_str = ret_str[:-2]
ret_str += "};}"
return ret_str
if __name__ == "__main__":
if len(sys.argv) < 2:
eprint(usage)
exit(1)
# Some validation on the input path
in_path = Path(sys.argv[1])
if not in_path.exists():
eprint("input path does not exist")
exit(1)
if not in_path.is_file():
eprint("input path is not a file")
exit(1)
# Read data & convert to a string of valid C++
data = read_bin(in_path)
header_str = bin_to_header(data, in_path.stem)
out_path = Path()
if len(sys.argv) == 3:
out_path = Path(sys.argv[2])
if not out_path.exists():
eprint("output path does not exist")
exit(1)
if not out_path.is_dir():
eprint("output path is not a directory")
exit(1)
else:
out_path = in_path.parents[0] / f"{in_path.stem}.hpp"
with open(out_path, "w") as f:
f.write(header_str)
exit(0)

View File

@ -0,0 +1,32 @@
# dlopen() shellcode
# - Calls dlopen() on our library filepath
# - Signature: void shellcode(void* dlopen_fn_ptr, char* lib_path_sz);
# dlopen_fn_ptr - RDI
# lib_path_sz - RSI
.org 0x0
.global _start
.section .text
_start:
movq %rdi, %r11 # Temporarily remove dlopen_fn_ptr
# Set up dlopen() call
movq %rsi, %rdi # Move path string ready for dlopen()
movq $2, %rsi # RTLD_NOW
call *%r11
# Check if dlopen() returned NULL
cmpq $0, %rax
jz set_sigusr2
# Prepare kill() syscall
set_sigusr1:
movq $10, %rsi # SIGUSR1 = 10
jmp signal_injector
set_sigusr2:
movq $12, %rsi # SIGUSR2 = 12
signal_injector:
movq $62, %rax # kill() syscall
movq $0, %rdi # PID, dummy value - the kill() syscall
syscall # is replaced with munmap() anyway

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

28
include/cli.hpp Normal file
View File

@ -0,0 +1,28 @@
#pragma once
#include "process.hpp"
#include <cstdint>
#include <expected>
#include <string_view>
enum class cli_parse_err : std::uint8_t {
missing_target_process,
missing_library_path,
invalid_argument,
not_enough_args,
};
using std::operator""sv;
class cli {
public:
static std::expected<cli, cli_parse_err> try_construct(int argc, char** argv);
static void print_usage();
static std::string_view cli_parse_err_str(cli_parse_err err);
public:
process::pid_t process_id = process::INVALID_PID;
std::filesystem::path library_path;
};

132
src/cli.cpp Normal file
View File

@ -0,0 +1,132 @@
#include "cli.hpp"
#include "process.hpp"
#include <algorithm>
#include <cassert>
#include <cctype>
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <expected>
#include <filesystem>
#include <format>
#include <limits>
#include <print>
#include <span>
#include <string_view>
using std::operator""sv;
void cli::print_usage() {
std::println("Usage: doniv-cli [-P process_name | -p process_id] -L library_path");
return;
}
std::string_view cli::cli_parse_err_str(cli_parse_err err) {
switch (err) {
case cli_parse_err::missing_target_process:
return "Missing target process identifier (process name/PID)"sv;
case cli_parse_err::missing_library_path:
return "Missing path to library to inject"sv;
case cli_parse_err::invalid_argument:
return "Invalid command line arguments"sv;
case cli_parse_err::not_enough_args:
return "Not enough command line parameters"sv;
default:
return ""sv;
}
}
std::expected<cli, cli_parse_err> cli::try_construct(int argc, char** argv) {
if (argc < 5) {
return std::unexpected(cli_parse_err::not_enough_args);
}
const std::span<char*> cli_args(argv, argc);
int proc_name_arg_idx = 0;
int proc_pid_arg_idx = 0;
int lib_path_arg_idx = 0;
for (int i = 0; i < cli_args.size(); ++i) {
const auto& arg = cli_args[i];
const int next_idx = (i + 1);
if (std::strcmp(arg, "-P") == 0) {
if (next_idx < cli_args.size()) {
proc_name_arg_idx = next_idx;
}
} else if (std::strcmp(arg, "-p") == 0) {
if (next_idx < cli_args.size()) {
proc_pid_arg_idx = next_idx;
}
} else if (std::strcmp(arg, "-L") == 0) {
if (next_idx < cli_args.size()) {
lib_path_arg_idx = next_idx;
}
}
}
if (!proc_name_arg_idx && !proc_pid_arg_idx) {
return std::unexpected(cli_parse_err::missing_target_process);
}
if (!lib_path_arg_idx) {
return std::unexpected(cli_parse_err::missing_library_path);
}
if (proc_pid_arg_idx) {
// Specify process by PID (takes priority over process name)
const auto& pid_str = cli_args[proc_pid_arg_idx];
const auto parsed_pid = std::strtoul(pid_str, nullptr, 0);
return cli {static_cast<process::pid_t>(parsed_pid), std::filesystem::path(cli_args[lib_path_arg_idx])};
} else {
// Specify process by process name
for (const auto& procfs_entry : std::filesystem::directory_iterator("/proc/")) {
// Not a directory
if (!procfs_entry.is_directory())
continue;
std::string_view dir_name(procfs_entry.path().filename().c_str());
const bool is_dir_name_numeric = std::all_of(dir_name.cbegin(), dir_name.cend(), [](const char c) {
return std::isdigit(c);
});
// Ensure this is a PID directory
if (!is_dir_name_numeric)
continue;
// Ensure "exe" file node exists
const auto proc_exe_path = procfs_entry.path() / "exe";
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) {
continue;
}
const auto bin_path = std::filesystem::read_symlink(proc_exe_path);
if (bin_path.filename().string().compare(cli_args[proc_name_arg_idx]) == 0) {
const auto pid = std::strtoul(procfs_entry.path().filename().c_str(), nullptr, 0);
if (pid == 0) {
return std::unexpected(cli_parse_err::invalid_argument);
}
if (pid > std::numeric_limits<process::pid_t>::max() || errno == ERANGE) {
return std::unexpected(cli_parse_err::invalid_argument);
}
return cli {static_cast<process::pid_t>(pid), std::filesystem::path(cli_args[lib_path_arg_idx])};
} else {
continue;
}
} catch (const std::filesystem::filesystem_error& ex) {
// Missing permissions (?)
continue;
}
}
}
return std::unexpected(cli_parse_err::missing_target_process);
}

31
src/main.cpp Normal file
View File

@ -0,0 +1,31 @@
#include "cli.hpp"
#include "inject.hpp"
#include "process.hpp"
#include <cstdlib>
#include <print>
int main(int argc, char* argv[]) {
const auto maybe_cli_args = cli::try_construct(argc, argv);
if (!maybe_cli_args.has_value()) {
const auto err = maybe_cli_args.error();
std::println(stderr, "{}", cli::cli_parse_err_str(err));
cli::print_usage();
return EXIT_FAILURE;
}
const auto& cli_args = maybe_cli_args.value();
process proc(cli_args.process_id);
std::println("[doniv-cli] Process ID: {}", proc.pid());
std::println("[doniv-cli] Binary path: {}", proc.get_exe_path()->c_str());
if (!inject_library(cli_args.library_path, proc)) {
std::println("[inject] Failed to inject");
return EXIT_FAILURE;
}
std::println("[inject] Successfully injected");
return EXIT_SUCCESS;
}