From 461ae36cfd20bf30d635a29012a82a9d85c7eebe Mon Sep 17 00:00:00 2001 From: Brady Date: Mon, 6 Jul 2026 13:46:26 -0500 Subject: [PATCH 1/8] Initial implementation of `memory_protector` for macOS --- CMakeLists.txt | 1 + src/os/mac/MemoryProtector.cpp | 70 ++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 src/os/mac/MemoryProtector.cpp 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/src/os/mac/MemoryProtector.cpp b/src/os/mac/MemoryProtector.cpp new file mode 100644 index 0000000..5f7b9a5 --- /dev/null +++ b/src/os/mac/MemoryProtector.cpp @@ -0,0 +1,70 @@ +#include +#if defined(LIBHAT_MAC) && defined(LIBHAT_LP64) + +#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 From a11a4b1781aea88f4435ebd5806041689f1684ae Mon Sep 17 00:00:00 2001 From: Brady Date: Mon, 6 Jul 2026 13:51:37 -0500 Subject: [PATCH 2/8] Header hallucination --- src/os/mac/MemoryProtector.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/os/mac/MemoryProtector.cpp b/src/os/mac/MemoryProtector.cpp index 5f7b9a5..a7b21bf 100644 --- a/src/os/mac/MemoryProtector.cpp +++ b/src/os/mac/MemoryProtector.cpp @@ -4,7 +4,6 @@ #include #include -#include #include namespace hat { From ecaa0268573a384b1685b8e528b8c122855676da Mon Sep 17 00:00:00 2001 From: Brady Date: Mon, 6 Jul 2026 13:53:56 -0500 Subject: [PATCH 3/8] Missing `optional` include --- src/os/mac/MemoryProtector.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/os/mac/MemoryProtector.cpp b/src/os/mac/MemoryProtector.cpp index a7b21bf..8ded65a 100644 --- a/src/os/mac/MemoryProtector.cpp +++ b/src/os/mac/MemoryProtector.cpp @@ -6,6 +6,8 @@ #include #include +#include + namespace hat { static std::optional get_page_prot(const uintptr_t address) { From 49e6c9a3da8d9926564f82b9cc7bf6f27d1bb353 Mon Sep 17 00:00:00 2001 From: Brady Date: Mon, 6 Jul 2026 14:10:12 -0500 Subject: [PATCH 4/8] macOS: implement `is_readable/writable/executable` --- src/os/mac/Process.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/os/mac/Process.cpp b/src/os/mac/Process.cpp index 48b6ae4..c99b8b0 100644 --- a/src/os/mac/Process.cpp +++ b/src/os/mac/Process.cpp @@ -91,6 +91,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++) { From a62b3b6fbe6be3e026f3005cadc83059fb24f2fe Mon Sep 17 00:00:00 2001 From: Brady Date: Mon, 6 Jul 2026 14:11:46 -0500 Subject: [PATCH 5/8] Include fixups --- src/os/mac/MemoryProtector.cpp | 1 + src/os/mac/Process.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/os/mac/MemoryProtector.cpp b/src/os/mac/MemoryProtector.cpp index 8ded65a..493b7e3 100644 --- a/src/os/mac/MemoryProtector.cpp +++ b/src/os/mac/MemoryProtector.cpp @@ -5,6 +5,7 @@ #include #include +#include #include diff --git a/src/os/mac/Process.cpp b/src/os/mac/Process.cpp index c99b8b0..c1f110f 100644 --- a/src/os/mac/Process.cpp +++ b/src/os/mac/Process.cpp @@ -5,6 +5,8 @@ #include #include +#include +#include #include #include From fb49b38a3dc74b312c0bba42e9f9b20a11992a73 Mon Sep 17 00:00:00 2001 From: Brady Date: Mon, 6 Jul 2026 14:14:18 -0500 Subject: [PATCH 6/8] Update support table in README --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4a0f73f..5eeb65b 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::is_readable` | ✅ | ✅ | ✅ | ✅ | +| `hp::is_writable` | ✅ | ✅ | ✅ | ✅ | +| `hp::is_executable` | ✅ | ✅ | ✅ | ✅ | | `hp::module::get_module_data` | ✅ | ✅ | ✅ | ✅ | | `hp::module::get_executable_data` | ✅ | ✅ | ✅ | ✅ | | `hp::module::get_section_data` | ✅ | ✅ | ✅ | ✅ | From 998e8d3ed2075c0d39179a816bbf2f6cc6dad1a9 Mon Sep 17 00:00:00 2001 From: Brady Date: Mon, 6 Jul 2026 14:58:34 -0500 Subject: [PATCH 7/8] macOS: implement `module_at` --- README.md | 2 +- src/os/mac/Process.cpp | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5eeb65b..5f9f71d 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ Below is a summary of the current support for libhat's platform-dependent APIs: | `hat::memory_protector` | ✅ | ✅ | ✅ | ✅ | | `hp::get_process_module` | ✅ | ✅ | ✅ | ✅ | | `hp::get_module` | ✅ | ✅ | ✅ | ✅ | -| `hp::module_at` | ✅ | | | | +| `hp::module_at` | ✅ | | ✅ | | | `hp::is_readable` | ✅ | ✅ | ✅ | ✅ | | `hp::is_writable` | ✅ | ✅ | ✅ | ✅ | | `hp::is_executable` | ✅ | ✅ | ✅ | ✅ | diff --git a/src/os/mac/Process.cpp b/src/os/mac/Process.cpp index c1f110f..7cf1fea 100644 --- a/src/os/mac/Process.cpp +++ b/src/os/mac/Process.cpp @@ -274,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 From d6da89398e205eb9739442604061a9168e0fd16a Mon Sep 17 00:00:00 2001 From: Brady Date: Mon, 6 Jul 2026 14:59:20 -0500 Subject: [PATCH 8/8] linux: copy `dladdr` method for `module_at` --- README.md | 2 +- src/os/linux/Process.cpp | 21 +++++---------------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 5f9f71d..d29764b 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ Below is a summary of the current support for libhat's platform-dependent APIs: | `hat::memory_protector` | ✅ | ✅ | ✅ | ✅ | | `hp::get_process_module` | ✅ | ✅ | ✅ | ✅ | | `hp::get_module` | ✅ | ✅ | ✅ | ✅ | -| `hp::module_at` | ✅ | | ✅ | | +| `hp::module_at` | ✅ | ✅ | ✅ | ✅ | | `hp::is_readable` | ✅ | ✅ | ✅ | ✅ | | `hp::is_writable` | ✅ | ✅ | ✅ | ✅ | | `hp::is_executable` | ✅ | ✅ | ✅ | ✅ | 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) {