diff --git a/CMakeLists.txt b/CMakeLists.txt index ad16d32..f2f0468 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -43,6 +43,7 @@ set(LIBHAT_SRC src/Scanner.cpp src/System.cpp + src/os/mac/MemoryProtector.cpp src/os/mac/Process.cpp src/os/linux/MemoryProtector.cpp diff --git a/README.md b/README.md index 4a0f73f..d29764b 100644 --- a/README.md +++ b/README.md @@ -106,13 +106,13 @@ Below is a summary of the current support for libhat's platform-dependent APIs: | | Windows | Linux | macOS | Android | |-----------------------------------|:-------:|:-----:|:-----:|:-------:| | `hat::get_system` | ✅ | ✅ | ✅ | ✅ | -| `hat::memory_protector` | ✅ | ✅ | | ✅ | +| `hat::memory_protector` | ✅ | ✅ | ✅ | ✅ | | `hp::get_process_module` | ✅ | ✅ | ✅ | ✅ | | `hp::get_module` | ✅ | ✅ | ✅ | ✅ | -| `hp::module_at` | ✅ | | | | -| `hp::is_readable` | ✅ | ✅ | | ✅ | -| `hp::is_writable` | ✅ | ✅ | | ✅ | -| `hp::is_executable` | ✅ | ✅ | | ✅ | +| `hp::module_at` | ✅ | ✅ | ✅ | ✅ | +| `hp::is_readable` | ✅ | ✅ | ✅ | ✅ | +| `hp::is_writable` | ✅ | ✅ | ✅ | ✅ | +| `hp::is_executable` | ✅ | ✅ | ✅ | ✅ | | `hp::module::get_module_data` | ✅ | ✅ | ✅ | ✅ | | `hp::module::get_executable_data` | ✅ | ✅ | ✅ | ✅ | | `hp::module::get_section_data` | ✅ | ✅ | ✅ | ✅ | diff --git a/src/os/linux/Process.cpp b/src/os/linux/Process.cpp index a9b0f1c..084675a 100644 --- a/src/os/linux/Process.cpp +++ b/src/os/linux/Process.cpp @@ -274,22 +274,11 @@ namespace hat::process { } std::optional module_at(void* address, [[maybe_unused]] const std::optional size) { - std::optional module{}; - auto callback = [&](const dl_phdr_info& info) { - if (std::bit_cast(info.dlpi_addr) == address) { - module = hat::process::module{std::bit_cast(info.dlpi_addr)}; - return 1; - } - return 0; - }; - - dl_iterate_phdr( - [](dl_phdr_info* info, size_t, void* data) { - return (*static_cast(data))(*info); - }, - &callback); - - return module; + Dl_info info{}; + if (dladdr(address, &info)) { + return hat::process::module{std::bit_cast(info.dli_fbase)}; + } + return std::nullopt; } bool is_readable(const std::span region) { diff --git a/src/os/mac/MemoryProtector.cpp b/src/os/mac/MemoryProtector.cpp new file mode 100644 index 0000000..493b7e3 --- /dev/null +++ b/src/os/mac/MemoryProtector.cpp @@ -0,0 +1,72 @@ +#include +#if defined(LIBHAT_MAC) && defined(LIBHAT_LP64) + +#include + +#include +#include +#include + +#include + +namespace hat { + + static std::optional get_page_prot(const uintptr_t address) { + mach_vm_address_t addr = static_cast(address); + mach_vm_size_t size{}; + vm_region_basic_info_64 info{}; + mach_msg_type_number_t infoCount = VM_REGION_BASIC_INFO_COUNT_64; + mach_port_t objectName{}; + + const auto result = mach_vm_region( + mach_task_self(), + &addr, + &size, + VM_REGION_BASIC_INFO_64, + reinterpret_cast(&info), + &infoCount, + &objectName + ); + + if (result != KERN_SUCCESS) { + return std::nullopt; + } + + return static_cast(info.protection); + } + + static vm_prot_t to_system_prot(const protection flags) { + vm_prot_t prot = 0; + if (static_cast(flags & protection::Read)) prot |= VM_PROT_READ; + if (static_cast(flags & protection::Write)) prot |= VM_PROT_WRITE; + if (static_cast(flags & protection::Execute)) prot |= VM_PROT_EXECUTE; + return prot; + } + + memory_protector::memory_protector(const uintptr_t address, const size_t size, const protection flags) : address(address), size(size) { + const auto oldProt = get_page_prot(address); + if (!oldProt) { + return; // Failure indicated via is_set() + } + + this->oldProtection = *oldProt; + this->set = KERN_SUCCESS == mach_vm_protect( + mach_task_self(), + static_cast(this->address), + static_cast(this->size), + false, + to_system_prot(flags) + ); + } + + void memory_protector::restore() { + mach_vm_protect( + mach_task_self(), + static_cast(this->address), + static_cast(this->size), + false, + static_cast(this->oldProtection) + ); + } +} +#endif diff --git a/src/os/mac/Process.cpp b/src/os/mac/Process.cpp index 48b6ae4..7cf1fea 100644 --- a/src/os/mac/Process.cpp +++ b/src/os/mac/Process.cpp @@ -5,6 +5,8 @@ #include #include +#include +#include #include #include @@ -91,6 +93,48 @@ namespace hat::process { }); } +#ifdef LIBHAT_LP64 + static bool regionHasFlags(const std::span region, const vm_prot_t flags) { + for (auto* addr = region.data(); addr < region.data() + region.size();) { + mach_vm_address_t vm_addr = std::bit_cast(addr); + mach_vm_size_t size{}; + vm_region_basic_info_64 info{}; + mach_msg_type_number_t infoCount = VM_REGION_BASIC_INFO_COUNT_64; + mach_port_t objectName{}; + + const auto result = mach_vm_region( + mach_task_self(), + &vm_addr, + &size, + VM_REGION_BASIC_INFO_64, + reinterpret_cast(&info), + &infoCount, + &objectName + ); + if (result != KERN_SUCCESS) { + return false; + } + if (!(info.protection & flags)) { + return false; + } + addr = std::bit_cast(vm_addr) + size; + } + return true; + } + + bool is_readable(const std::span region) { + return regionHasFlags(region, VM_PROT_READ); + } + + bool is_writable(const std::span region) { + return regionHasFlags(region, VM_PROT_WRITE); + } + + bool is_executable(const std::span region) { + return regionHasFlags(region, VM_PROT_EXECUTE); + } +#endif + hat::process::module get_process_module() { const uint32_t count = _dyld_image_count(); for (uint32_t i = 0; i != count; i++) { @@ -230,6 +274,14 @@ namespace hat::process { return {}; } + + std::optional module_at(void* address, [[maybe_unused]] const std::optional size) { + Dl_info info{}; + if (dladdr(address, &info)) { + return hat::process::module{std::bit_cast(info.dli_fbase)}; + } + return std::nullopt; + } } #endif