diff --git a/.clang-tidy b/.clang-tidy index d21c33a..2731957 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -35,7 +35,9 @@ Checks: > -readability-braces-around-statements, -google-readability-braces-around-statements, -misc-include-cleaner, - -cppcoreguidelines-non-private-member-variables-in-classes + -cppcoreguidelines-non-private-member-variables-in-classes, + -bugprone-bitwise-pointer-cast, + -performance-no-int-to-ptr WarningsAsErrors: '' diff --git a/src/cmd/environment.hpp b/src/cmd/environment.hpp index 5cd8d1c..4631ed9 100644 --- a/src/cmd/environment.hpp +++ b/src/cmd/environment.hpp @@ -18,6 +18,7 @@ class Environment { this->define("breakpoint", std::make_shared(BreakpointFn())); this->define("run", std::make_shared(RunFn())); this->define("resume", std::make_shared(ContinueFn())); + this->define("target", std::make_shared(TargetFn())); } public: diff --git a/src/cmd/stdlib.hpp b/src/cmd/stdlib.hpp index 5a66565..907c579 100644 --- a/src/cmd/stdlib.hpp +++ b/src/cmd/stdlib.hpp @@ -11,11 +11,13 @@ #include "callable.hpp" #include "cmd/object.hpp" +#include "cmd/util.hpp" #include "core/target.hpp" #include "error.hpp" #include "expected.hpp" #include "subcommand.hpp" #include "token_type.hpp" +#include "typedefs.hpp" class LenFn : public Callable { public: @@ -48,8 +50,6 @@ class PrintFn : public Callable { class BreakpointFn : public Callable { private: - using sv = std::string_view; - static Object rmOrToggleBreakpoint(const std::vector& args, bool toggle = false) { Expected addrRes = strToAddr(args[0]); @@ -62,13 +62,13 @@ class BreakpointFn : public Callable { if (!bps.contains(addr)) return std::format("No breakpoint at {}", addr); if (toggle) { - i32 res = target->disableBreakpoint(addr, true); + const i32 res = target->disableBreakpoint(addr, true); if (res != 0) return "Error toggling breakpoint"; return std::format("Toggled breakpoint at {}, now {}", addr, bps[addr].enabled); } - i32 res = target->disableBreakpoint(addr, false); + const i32 res = target->disableBreakpoint(addr, false); if (res != 0) return "Error disabling breakpoint"; return std::format("Removed breakpoint at {}", addr); } @@ -92,9 +92,9 @@ class BreakpointFn : public Callable { FnPtr set = [](const std::vector& args) -> Object { Expected addrRes = strToAddr(args[0]); if (!addrRes) return addrRes.error(); - u64 addr = *addrRes; + const u64 addr = *addrRes; - i32 bpRes = Context::getTarget()->setBreakpoint(addr); + const i32 bpRes = Context::getTarget()->setBreakpoint(addr); if (bpRes != 0) return "Error setting breakpoint!"; else @@ -115,24 +115,6 @@ class BreakpointFn : public Callable { {sv("toggle"), toggle}}, "breakpoint"}; - static std::vector convertToStr(const std::vector& vec) { - std::vector res{}; - res.reserve(vec.size()); - - for (const auto& x : vec) { - const auto* const str = std::get_if(&x); - if (str == nullptr) { - CmdError::error(TokenType::IDENTIFIER, - "Please provide all arguments as strings", - CmdErrorType::RUNTIME_ERROR); - return {}; // Return empty on error, not partial - } - res.emplace_back(*str); - } - - return res; - } - public: [[nodiscard]] int arity() const override { return 1; } [[nodiscard]] std::string str() const override { @@ -144,8 +126,8 @@ class BreakpointFn : public Callable { CoreError::error("Target is not running!"); return std::monostate{}; } - std::vector convertedArgs = BreakpointFn::convertToStr(args); - if (convertedArgs.size() < 1) return std::monostate{}; + std::vector convertedArgs = detail::convertToStr(args); + if (convertedArgs.empty()) return std::monostate{}; const std::string subcmd = convertedArgs.front(); convertedArgs.erase(convertedArgs.begin()); return m_subcmds.exec(subcmd, convertedArgs); @@ -209,7 +191,7 @@ class RunFn : public Callable { else return "Unable to start target (are you running with sudo?)\n"; - i32 res = m_target->attach(); + const i32 res = m_target->attach(); if (res != 0) return "Could not attach to target!\n"; m_target->setTargetState(TargetState::RUNNING); // TODO: Reset this when target exits @@ -238,4 +220,46 @@ class ContinueFn : public Callable { } }; +class TargetFn : public Callable { + private: + FnPtr info = [](const std::vector& args) -> Object { +#pragma unused(args) + auto& target = Context::getTarget(); + if (!target) return "Target not set!"; + return target->getInfo(); + }; + + FnPtr set = [](const std::vector& args) -> Object { + auto& target = Context::getTarget(); + if (target) { + if (target->getTargetState() == TargetState::RUNNING || + target->getTargetState() == TargetState::STOPPED) + return "Cannot set new target whilst one is running!"; + } + + if (Target::isFileValid(args[0])) { + Context::setTarget(Target::create(args[0])); + return std::format("Target: {}", args[0]); + } + + return std::format("{} is not a valid target path!", args[0]); + }; + + SubcommandHandler m_subcmds{{{sv("info"), info}, {sv("set"), set}}, "target"}; + + public: + [[nodiscard]] int arity() const override { return 1; } + [[nodiscard]] std::string str() const override { + return ""; + } + + Object call(std::vector args) override { + std::vector convertedArgs = detail::convertToStr(args); + if (convertedArgs.empty()) return std::monostate{}; + const std::string subcmd = convertedArgs.front(); + convertedArgs.erase(convertedArgs.begin()); + return m_subcmds.exec(subcmd, convertedArgs); + } +}; + #endif diff --git a/src/cmd/util.cpp b/src/cmd/util.cpp index 1616c5a..fd33691 100644 --- a/src/cmd/util.cpp +++ b/src/cmd/util.cpp @@ -46,3 +46,21 @@ double detail::parseNumber(const std::string& str) { std::format("Cannot parse {} as number\n", str)); } } + +std::vector detail::convertToStr(const std::vector& vec) { + std::vector res{}; + res.reserve(vec.size()); + + for (const auto& x : vec) { + const auto* const str = std::get_if(&x); + if (str == nullptr) { + CmdError::error(TokenType::IDENTIFIER, + "Please provide all arguments as strings", + CmdErrorType::RUNTIME_ERROR); + return {}; // Return empty on error, not partial + } + res.emplace_back(*str); + } + + return res; +} diff --git a/src/cmd/util.hpp b/src/cmd/util.hpp index 57c9e1d..4810fb2 100644 --- a/src/cmd/util.hpp +++ b/src/cmd/util.hpp @@ -2,13 +2,16 @@ #define UTIL_H #include +#include +#include "cmd/object.hpp" #include "token_type.hpp" namespace detail { bool isop(char c); TokenType ctoop(char c); double parseNumber(const std::string& str); +std::vector convertToStr(const std::vector& vec); } // namespace detail #endif diff --git a/src/core/macho/macho.cpp b/src/core/macho/macho.cpp index bed2d19..4557ee4 100644 --- a/src/core/macho/macho.cpp +++ b/src/core/macho/macho.cpp @@ -57,9 +57,9 @@ extern "C" { // Adapted from https://lowlevelbits.org/parsing-mach-o-files/ void Macho::readMagic() { - u32 magic = 0; + const u32 magic = 0; m_file.seekg(0, std::ios::beg); - m_file.read(std::bit_cast(&magic), sizeof(uint32_t)); + m_file.read(std::bit_cast(&magic), sizeof(u32)); m_magic = magic; } @@ -82,16 +82,16 @@ void Macho::dumpHeader(int offset) { loadCmdsOffset += headerSize; std::cout << Macho::cpuTypeName(header.cputype) << '\n'; } else { - int headerSize = sizeof(struct mach_header); + const int headerSize = sizeof(struct mach_header); auto header = loadBytesAndMaybeSwap(offset); } dumpSegmentCommands(loadCmdsOffset, ncmds); } -void Macho::dumpSegmentCommands(int offset, uint32_t ncmds) { +void Macho::dumpSegmentCommands(int offset, u32 ncmds) { u32 actualOffset = offset; - for (int i = 0; i < ncmds; i++) { + for (u32 i = 0; i < ncmds; i++) { auto cmd = loadBytesAndMaybeSwap(actualOffset); if (cmd.cmd == LC_SEGMENT_64) { auto segment = loadBytesAndMaybeSwap(actualOffset); @@ -115,7 +115,7 @@ std::string Macho::cpuTypeName(cpu_type_t cpuType) { return "unknown"; } -void Macho::dumpSections(uint32_t offset, uint32_t end) { +void Macho::dumpSections(u32 offset, u32 end) { u32 actualOffset = offset; while (actualOffset != end) { auto section = loadBytesAndMaybeSwap(actualOffset); @@ -154,7 +154,7 @@ i32 Macho::launch(CStringArray& argList) { m_pid = pid; // TODO: This only works with sudo even when codesigned, why? - kern_return_t kr = task_for_pid(mach_task_self(), pid, &this->m_task); + const kern_return_t kr = task_for_pid(mach_task_self(), pid, &this->m_task); if (kr != KERN_SUCCESS) { CoreError::error(mach_error_string(kr)); } @@ -163,7 +163,7 @@ i32 Macho::launch(CStringArray& argList) { i32 Macho::attach() { // http://uninformed.org/index.cgi?v=4&a=3&p=14 - i32 res = this->setupExceptionPorts(); + const i32 res = this->setupExceptionPorts(); ptrace(PT_ATTACHEXC, m_pid, nullptr, 0); this->readAslrSlide(); return res; @@ -171,7 +171,7 @@ i32 Macho::attach() { // TODO: Make an overload with u32 for 32bit systems i32 Macho::setBreakpoint(u64 addr) { - u64 actual = addr + m_aslr_slide; + const u64 actual = addr + m_aslr_slide; auto sz = static_cast(sizeof(u32)); vm_offset_t origBuf = 0; kern_return_t kr = mach_vm_read(m_task, actual, sizeof(u32), &origBuf, &sz); @@ -181,7 +181,7 @@ i32 Macho::setBreakpoint(u64 addr) { return -1; } - u32 origIns = *reinterpret_cast(origBuf); + const u32 origIns = *reinterpret_cast(origBuf); mach_vm_deallocate(mach_task_self(), origBuf, sizeof(u32)); m_breakpoints[addr] = {.orig_ins = origIns, .enabled = true}; @@ -321,6 +321,8 @@ kern_return_t catch_mach_exception_raise_state( const thread_state_t oldState, // NOLINT(misc-misplaced-const) mach_msg_type_number_t oldStateCnt, thread_state_t newState, mach_msg_type_number_t* newStateCnt) { +#pragma unused(flavour) +#pragma unused(oldState) #pragma unused(excPort) #pragma unused(exc) #pragma unused(code) @@ -335,9 +337,10 @@ kern_return_t catch_mach_exception_raise_state( kern_return_t catch_mach_exception_raise_state_identity( mach_port_t excPort, mach_port_t thread, mach_port_t task, exception_type_t exc, mach_exception_data_t code, - mach_msg_type_number_t codeCnt, int* flavour, thread_state_t oldState, - mach_msg_type_number_t oldStateCnt, thread_state_t newState, - mach_msg_type_number_t* newStateCnt) { + mach_msg_type_number_t codeCnt, + int* flavour, // NOLINT(readability-non-const-parameter) + thread_state_t oldState, mach_msg_type_number_t oldStateCnt, + thread_state_t newState, mach_msg_type_number_t* newStateCnt) { #pragma unused(excPort) #pragma unused(task) #pragma unused(code) @@ -376,19 +379,19 @@ kern_return_t catch_mach_exception_raise_state_identity( } // extern "C" -mach_port_t Macho::threadSelect() { +mach_port_t Macho::threadSelect() const { std::cout << "TASK: " << m_task << '\n'; thread_act_array_t acts{}; mach_msg_type_number_t numThreads = 0; - kern_return_t kr = task_threads(m_task, &acts, &numThreads); + const kern_return_t kr = task_threads(m_task, &acts, &numThreads); if (kr != KERN_SUCCESS) { std::cout << "task_threads fail!\n"; CoreError::error(mach_error_string(kr)); } - thread_act_t mainThread = acts[0]; // Cleanup + const thread_act_t mainThread = acts[0]; // Cleanup vm_deallocate(mach_task_self(), reinterpret_cast(acts), numThreads * sizeof(thread_act_t)); @@ -546,12 +549,12 @@ void Macho::readAslrSlideFromRegions() { if (kr != KERN_SUCCESS) break; // Look for executable regions that could contain the main binary - if (info.protection & VM_PROT_EXECUTE) { + if ((info.protection & VM_PROT_EXECUTE) != 0) { vm_offset_t headerBuf{}; auto sz = static_cast(sizeof(u32)); kr = mach_vm_read(m_task, addr, sizeof(u32), &headerBuf, &sz); if (kr == KERN_SUCCESS) { - u32 magic = *reinterpret_cast(headerBuf); + const u32 magic = *reinterpret_cast(headerBuf); mach_vm_deallocate(mach_task_self(), headerBuf, sizeof(u32)); if (magic == MH_MAGIC_64) { m_aslr_slide = addr - 0x100000000; @@ -568,7 +571,7 @@ void Macho::readAslrSlideFromRegions() { u64& Macho::getAslrSlide() { return m_aslr_slide; } i32 Macho::restorePrevIns(u64 k) { - u64 addr = k + m_aslr_slide; + const u64 addr = k + m_aslr_slide; kern_return_t kr = mach_vm_protect(m_task, addr, sizeof(u32), FALSE, @@ -605,7 +608,7 @@ i32 Macho::disableBreakpoint(u64 addr, bool remove) { if (!bp.enabled) return 0; - i32 res = this->restorePrevIns(addr); + const i32 res = this->restorePrevIns(addr); if (res != 0) return 1; if (remove) diff --git a/src/core/macho/macho.hpp b/src/core/macho/macho.hpp index 1a1e752..ef8a3c5 100644 --- a/src/core/macho/macho.hpp +++ b/src/core/macho/macho.hpp @@ -21,7 +21,7 @@ struct CpuTypeNames { const char* cpu_name; }; -class Macho : public Target { +class Macho final : public Target { public: explicit Macho(std::ifstream f, std::string filePath); @@ -49,7 +49,6 @@ class Macho : public Target { u32 m_magic = 0; mach_port_t m_exc_port = 0; mach_port_t m_thread_port = 0; - bool m_is_64 = false; bool m_is_swap = false; static constexpr std::array CPU_TYPE_NAMES = { {{.cpu_type = CPU_TYPE_I386, .cpu_name = "i386"}, @@ -63,7 +62,7 @@ class Macho : public Target { void maybeSwapBytes(); template - T loadBytesAndMaybeSwap(uint32_t offset) { + T loadBytesAndMaybeSwap(u32 offset) { T buf; m_file.seekg(offset, std::ios::beg); m_file.read(std::bit_cast(&buf), sizeof(T)); @@ -71,12 +70,12 @@ class Macho : public Target { return buf; } - void dumpSegmentCommands(int offset, uint32_t ncmds); + void dumpSegmentCommands(int offset, u32 ncmds); static std::string cpuTypeName(cpu_type_t cpuType); - void dumpSections(uint32_t offset, uint32_t end); + void dumpSections(u32 offset, u32 end); i32 setupExceptionPorts(); void readAslrSlideFromRegions(); - mach_port_t threadSelect(); // TODO: doesn't work (yet) + mach_port_t threadSelect() const; // TODO: doesn't work (yet) }; #endif // CAESAR_MACHO_HPP diff --git a/src/core/target.cpp b/src/core/target.cpp index b1896d9..f4c6691 100644 --- a/src/core/target.cpp +++ b/src/core/target.cpp @@ -35,7 +35,7 @@ bool Target::isFileValid(const std::string& filePath) { std::byte{}}), PlatformType::WIN}}}; - u32 magicRead = 0; + const u32 magicRead = 0; std::ifstream f{filePath}; f.seekg(0, std::ios::beg); f.read(std::bit_cast(&magicRead), sizeof(u32)); @@ -56,3 +56,12 @@ void Target::startEventLoop() { std::map& Target::getRegisteredBreakpoints() { return m_breakpoints; } + +std::string Target::getInfo() { + std::string res{}; + + res += std::format("Target: {} \n", m_file_path); + res += std::format("Type: {}, Architecure: {}", getTargetType(), + m_is_64 ? "64-bit" : "32-bit"); + return res; +} diff --git a/src/core/target.hpp b/src/core/target.hpp index bf6f87d..de89dad 100644 --- a/src/core/target.hpp +++ b/src/core/target.hpp @@ -32,6 +32,7 @@ class Target { std::map m_breakpoints; i32 m_pid = 0; std::atomic m_state = TargetState::STOPPED; + bool m_is_64 = false; explicit Target(std::ifstream f, std::string filePath) : m_file(std::move(f)), m_file_path(std::move(filePath)) {} @@ -59,6 +60,7 @@ class Target { i32 pid() const { return m_pid; } void startEventLoop(); std::map& getRegisteredBreakpoints(); + std::string getInfo(); static bool isFileValid(const std::string& filePath); static std::unique_ptr create(const std::string& path); diff --git a/src/core/util.hpp b/src/core/util.hpp index 1b2a18a..ad7a9d7 100644 --- a/src/core/util.hpp +++ b/src/core/util.hpp @@ -20,10 +20,10 @@ template struct SwapDescriptor { static void swap(T* ptr) { - static_assert(sizeof(T) % sizeof(uint32_t) == 0, + static_assert(sizeof(T) % sizeof(u32) == 0, "Struct size must be multiple of 4 bytes"); - auto* fields = reinterpret_cast(ptr); + auto* fields = reinterpret_cast(ptr); constexpr size_t numFields = sizeof(T) / sizeof(u32); // TODO: Compiler builtin, make this more platform agnostic @@ -33,14 +33,14 @@ struct SwapDescriptor { } }; -#ifdef __APPLE__ - struct FieldInfo { size_t offset; size_t size; bool swap; }; +#ifdef __APPLE__ + template <> struct SwapDescriptor { static void swap(segment_command_64* segment) { @@ -82,10 +82,10 @@ struct SwapDescriptor { for (const auto& f : fields) { if (f.swap) { if (f.size == 4) { - auto* ptr = reinterpret_cast(base + f.offset); + auto* ptr = reinterpret_cast(base + f.offset); *ptr = __builtin_bswap32(*ptr); } else if (f.size == 8) { - auto* ptr = reinterpret_cast(base + f.offset); + auto* ptr = reinterpret_cast(base + f.offset); *ptr = __builtin_bswap64(*ptr); } } @@ -115,10 +115,10 @@ struct SwapDescriptor { for (const auto& f : fields) { if (f.swap) { if (f.size == 4) { - auto* ptr = reinterpret_cast(base + f.offset); + auto* ptr = reinterpret_cast(base + f.offset); *ptr = __builtin_bswap32(*ptr); } else if (f.size == 8) { - auto* ptr = reinterpret_cast(base + f.offset); + auto* ptr = reinterpret_cast(base + f.offset); *ptr = __builtin_bswap64(*ptr); } } @@ -129,9 +129,9 @@ struct SwapDescriptor { #endif enum class PlatformType : std::uint8_t { MACH, LINUX, WIN, UNSUPPORTED }; -consteval static PlatformType getPlatform() { +constexpr static PlatformType getPlatform() { PlatformType t = PlatformType::UNSUPPORTED; -#if defined(__APPLE__) +#ifdef __APPLE__ t = PlatformType::MACH; #elif defined(__linux__) t = PlatformType::LINUX; @@ -179,4 +179,16 @@ inline Expected strToAddr(const std::string& addr) { } } +constexpr std::string_view getTargetType() { + constexpr PlatformType p = getPlatform(); + switch (p) { + case PlatformType::MACH: + return "Mach-O"; + case PlatformType::LINUX: + return "ELF"; + case PlatformType::WIN: + return "PE"; + } +} + #endif diff --git a/src/typedefs.hpp b/src/typedefs.hpp index 3a5de84..0cac917 100644 --- a/src/typedefs.hpp +++ b/src/typedefs.hpp @@ -11,5 +11,6 @@ using i64 = std::int64_t; using u64 = std::uint64_t; using MagicBytes = std::array; using FnPtr = Object (*)(const std::vector&); +using sv = std::string_view; #endif