From ca1f23b7db14dc158d6a95efb79e651b21d09daa Mon Sep 17 00:00:00 2001 From: cursey Date: Sun, 4 Feb 2024 14:45:29 -0800 Subject: [PATCH] Os(Linux): Initial Linux implementation of the OS abstraction layer --- CMakeLists.txt | 1 + include/safetyhook/utility.hpp | 12 ++ src/allocator.cpp | 13 +-- src/os.linux.cpp | 193 +++++++++++++++++++++++++++++++++ 4 files changed, 207 insertions(+), 12 deletions(-) create mode 100644 src/os.linux.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 212f9b6..19bf98d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -93,6 +93,7 @@ set(safetyhook_SOURCES "src/easy.cpp" "src/inline_hook.cpp" "src/mid_hook.cpp" + "src/os.linux.cpp" "src/os.windows.cpp" "src/utility.cpp" "src/vmt_hook.cpp" diff --git a/include/safetyhook/utility.hpp b/include/safetyhook/utility.hpp index 933b338..06ae1c0 100644 --- a/include/safetyhook/utility.hpp +++ b/include/safetyhook/utility.hpp @@ -35,4 +35,16 @@ class UnprotectMemory { }; [[nodiscard]] std::optional unprotect(uint8_t* address, size_t size); + +template constexpr T align_up(T address, size_t align) { + const auto unaligned_address = (uintptr_t)address; + const auto aligned_address = (unaligned_address + align - 1) & ~(align - 1); + return (T)aligned_address; +} + +template constexpr T align_down(T address, size_t align) { + const auto unaligned_address = (uintptr_t)address; + const auto aligned_address = unaligned_address & ~(align - 1); + return (T)aligned_address; +} } // namespace safetyhook diff --git a/src/allocator.cpp b/src/allocator.cpp index 3b313b0..eaa3d84 100644 --- a/src/allocator.cpp +++ b/src/allocator.cpp @@ -3,22 +3,11 @@ #include #include "safetyhook/os.hpp" +#include "safetyhook/utility.hpp" #include "safetyhook/allocator.hpp" namespace safetyhook { -template constexpr T align_up(T address, size_t align) { - const auto unaligned_address = (uintptr_t)address; - const auto aligned_address = (unaligned_address + align - 1) & ~(align - 1); - return (T)aligned_address; -} - -template constexpr T align_down(T address, size_t align) { - const auto unaligned_address = (uintptr_t)address; - const auto aligned_address = unaligned_address & ~(align - 1); - return (T)aligned_address; -} - Allocation::Allocation(Allocation&& other) noexcept { *this = std::move(other); } diff --git a/src/os.linux.cpp b/src/os.linux.cpp new file mode 100644 index 0000000..a22bc38 --- /dev/null +++ b/src/os.linux.cpp @@ -0,0 +1,193 @@ +#include "safetyhook/common.hpp" + +#if SAFETYHOOK_OS_LINUX + +#include +#include + +#include "safetyhook/utility.hpp" + +#include "safetyhook/os.hpp" + +namespace safetyhook { +std::expected vm_allocate(uint8_t* address, size_t size, VmAccess access) { + int prot = 0; + int flags = MAP_PRIVATE | MAP_ANONYMOUS; + + if (access == VM_ACCESS_R) { + prot = PROT_READ; + } else if (access == VM_ACCESS_RW) { + prot = PROT_READ | PROT_WRITE; + } else if (access == VM_ACCESS_RX) { + prot = PROT_READ | PROT_EXEC; + } else if (access == VM_ACCESS_RWX) { + prot = PROT_READ | PROT_WRITE | PROT_EXEC; + } else { + return std::unexpected{OsError::FAILED_TO_ALLOCATE}; + } + + auto* result = mmap(address, size, prot, flags, -1, 0); + + if (result == MAP_FAILED) { + return std::unexpected{OsError::FAILED_TO_ALLOCATE}; + } + + return static_cast(result); +} + +void vm_free(uint8_t* address) { + munmap(address, 0); +} + +std::expected vm_protect(uint8_t* address, size_t size, VmAccess access) { + int prot = 0; + + if (access == VM_ACCESS_R) { + prot = PROT_READ; + } else if (access == VM_ACCESS_RW) { + prot = PROT_READ | PROT_WRITE; + } else if (access == VM_ACCESS_RX) { + prot = PROT_READ | PROT_EXEC; + } else if (access == VM_ACCESS_RWX) { + prot = PROT_READ | PROT_WRITE | PROT_EXEC; + } else { + return std::unexpected{OsError::FAILED_TO_PROTECT}; + } + + return vm_protect(address, size, prot); +} + +std::expected vm_protect(uint8_t* address, size_t size, uint32_t protect) { + auto mbi = vm_query(address); + + if (!mbi.has_value()) { + return std::unexpected{OsError::FAILED_TO_PROTECT}; + } + + uint32_t old_protect = 0; + + if (mbi->access.read) { + old_protect |= PROT_READ; + } + + if (mbi->access.write) { + old_protect |= PROT_WRITE; + } + + if (mbi->access.execute) { + old_protect |= PROT_EXEC; + } + + auto* addr = align_down(address, static_cast(sysconf(_SC_PAGESIZE))); + + if (mprotect(addr, size, static_cast(protect)) == -1) { + return std::unexpected{OsError::FAILED_TO_PROTECT}; + } + + return old_protect; +} + +std::expected vm_query(uint8_t* address) { + auto* maps = fopen("/proc/self/maps", "r"); + + if (maps == nullptr) { + return std::unexpected{OsError::FAILED_TO_QUERY}; + } + + char line[512]; + unsigned long start; + unsigned long end; + char perms[5]; + unsigned long offset; + int dev_major; + int dev_minor; + unsigned long inode; + char path[256]; + unsigned long last_end = 0x1000; // Track the end address of the last mapping. + auto addr = reinterpret_cast(address); + std::optional info = std::nullopt; + + while (fgets(line, sizeof(line), maps) != nullptr) { + path[0] = '\0'; + + sscanf(line, "%lx-%lx %4s %lx %x:%x %lu %255[^\n]", &start, &end, perms, &offset, &dev_major, &dev_minor, + &inode, path); + + if (last_end < start && addr >= last_end && addr < start) { + info = { + .address = reinterpret_cast(last_end), + .size = start - last_end, + .access = VmAccess{}, + .is_free = true, + }; + + break; + } + + if (addr >= start && addr < end) { + info = { + .address = reinterpret_cast(start), + .size = end - start, + .access = VmAccess{}, + .is_free = false, + }; + + if (perms[0] == 'r') { + info->access.read = true; + } + + if (perms[1] == 'w') { + info->access.write = true; + } + + if (perms[2] == 'x') { + info->access.execute = true; + } + + break; + } + } + + fclose(maps); + + if (!info.has_value()) { + return std::unexpected{OsError::FAILED_TO_QUERY}; + } + + return info.value(); +} + +bool vm_is_readable(uint8_t* address, [[maybe_unused]] size_t size) { + return vm_query(address).value_or(VmBasicInfo{}).access.read; +} + +bool vm_is_writable(uint8_t* address, [[maybe_unused]] size_t size) { + return vm_query(address).value_or(VmBasicInfo{}).access.write; +} + +bool vm_is_executable(uint8_t* address) { + return vm_query(address).value_or(VmBasicInfo{}).access.execute; +} + +SystemInfo system_info() { + auto page_size = static_cast(sysconf(_SC_PAGESIZE)); + + return { + .page_size = page_size, + .allocation_granularity = page_size, + .min_address = reinterpret_cast(0x10000), + .max_address = reinterpret_cast(1ull << 47), + }; +} + +void execute_while_frozen(const std::function& run_fn, + [[maybe_unused]] const std::function& visit_fn) { + run_fn(); +} + +void fix_ip([[maybe_unused]] ThreadContext ctx, [[maybe_unused]] uint8_t* old_ip, [[maybe_unused]] uint8_t* new_ip) { +} + +} // namespace safetyhook + +#endif \ No newline at end of file