From 9fe4d2e4767f981b22b263d11733921456c80bc5 Mon Sep 17 00:00:00 2001 From: nexusverypro Date: Fri, 3 Jul 2026 23:39:52 +0100 Subject: [PATCH] fix: split files, add padding and return checking on disasm --- CMakeLists.txt | 2 + include/radhook/api.h | 1 + include/radhook/defines.h | 46 +++ include/radhook/private/disassemble.h | 57 ++++ include/radhook/private/memory.h | 102 ++++++ include/radhook/radhook.h | 43 +-- src/memory.cpp | 218 +++++++++++++ src/radhook.cpp | 432 +++----------------------- src/x64disasm.cpp | 163 ++++++++++ 9 files changed, 626 insertions(+), 438 deletions(-) create mode 100644 include/radhook/defines.h create mode 100644 include/radhook/private/disassemble.h create mode 100644 include/radhook/private/memory.h create mode 100644 src/memory.cpp create mode 100644 src/x64disasm.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a5461a1..3c8ea7e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,8 @@ endif() add_library(radhook src/radhook.cpp + src/memory.cpp + src/x64disasm.cpp ) target_include_directories(radhook diff --git a/include/radhook/api.h b/include/radhook/api.h index 159fd17..ff14786 100644 --- a/include/radhook/api.h +++ b/include/radhook/api.h @@ -41,6 +41,7 @@ enum class RadHookResult : int { InvalidDetour, InvalidHandle, TrampolineTooFar, + TargetTooSmall, Unknown, }; diff --git a/include/radhook/defines.h b/include/radhook/defines.h new file mode 100644 index 0000000..691510e --- /dev/null +++ b/include/radhook/defines.h @@ -0,0 +1,46 @@ +#ifndef __LIBRADHOOK_DEFINES_HPP__ +#define __LIBRADHOOK_DEFINES_HPP__ + +#include +#include + +#include + +// platform detection +#if !defined(RADIUM_PLATFORM_WINDOWS) && !defined(RADIUM_PLATFORM_ANDROID) +# if defined(_WIN32) || defined(_WIN64) +# define RADIUM_PLATFORM_WINDOWS 1 +# elif defined(__ANDROID__) +# define RADIUM_PLATFORM_ANDROID 1 +# else +# error "radhook: unsupported platform -- define RADIUM_PLATFORM_WINDOWS or RADIUM_PLATFORM_ANDROID" +# endif +#endif + +// architecture detection +#if defined(_M_X64) || defined(__x86_64__) +# define RADHOOK_ARCH_X64 1 +#elif defined(_M_ARM64) || defined(__aarch64__) +# define RADHOOK_ARCH_ARM64 1 +#else +# error "radhook: unsupported architecture -- need x86-64 or aarch64" +#endif + +// api macros +#if defined(_WIN32) +# if defined(RADHOOK_BUILD) +# define RADHOOK_API __declspec(dllexport) +# elif defined(RADHOOK_SHARED) +# define RADHOOK_API __declspec(dllimport) +# else +# define RADHOOK_API +# endif +#else +# if defined(__GNUC__) || defined(__clang__) +# define RADHOOK_API __attribute__((visibility("default"))) +# else +# define RADHOOK_API +# endif +#endif + +#endif // __LIBRADHOOK_DEFINES_HPP__ \ No newline at end of file diff --git a/include/radhook/private/disassemble.h b/include/radhook/private/disassemble.h new file mode 100644 index 0000000..3c6e7a1 --- /dev/null +++ b/include/radhook/private/disassemble.h @@ -0,0 +1,57 @@ +#ifndef __LIBRADHOOK_PRIVATE_DISASSEMBLE_H__ +#define __LIBRADHOOK_PRIVATE_DISASSEMBLE_H__ + +#include "radhook/defines.h" + +#if defined(RADIUM_PLATFORM_WINDOWS) +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include +#elif defined(RADIUM_PLATFORM_ANDROID) +# include +# include +#endif + +#if defined(RADHOOK_ARCH_X64) +struct X64Insn { + size_t length = 0; + bool ripRelative = false; + size_t dispOffset = 0; + bool relativeControlFlow = false; + bool isReturn = false; + bool isPadding = false; + bool valid = false; +}; + +X64Insn +DecodeX64( + const uint8_t* p, + size_t avail + ); + +constexpr size_t kX64StubLen = 14; // FF25 00000000 + imm64 +constexpr size_t kX64MaxStolen = 32; +constexpr size_t kX64MaxRipFixups = 8; + +enum class StolenBytesError { + None, + BadDecode, + RelativeControlFlow, + TooManyRipFixups, + ExceedsMaxStolen, + InsufficientLength, +}; + +StolenBytesError +BuildStolenBytesX64( + const uint8_t* target, + size_t minLen, + uint8_t* outBytes, + size_t& outLen, + size_t* outRipOffsets, + size_t& outRipCount + ); + +#endif // defined(RADHOOK_ARCH_X64) +#endif // __LIBRADHOOK_PRIVATE_DISASSEMBLE_H__ \ No newline at end of file diff --git a/include/radhook/private/memory.h b/include/radhook/private/memory.h new file mode 100644 index 0000000..f8777cd --- /dev/null +++ b/include/radhook/private/memory.h @@ -0,0 +1,102 @@ +#ifndef __LIBRADHOOK_PRIVATE_MEMORY_H__ +#define __LIBRADHOOK_PRIVATE_MEMORY_H__ + +#include "radhook/defines.h" + +#if defined(RADIUM_PLATFORM_WINDOWS) +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include +#elif defined(RADIUM_PLATFORM_ANDROID) +# include +# include +#endif + +#if defined(RADIUM_PLATFORM_WINDOWS) + +size_t +RadMemoryPageSize(); + +void* +RadAllocExec( + size_t size + ); + +void* +RadAllocExecNear( + void* target, + size_t size + ); + +void +RadFreeExec( + void* p, + size_t size + ); + +bool +RadMakeWritableExecutable( + void* addr, + size_t size, + unsigned long* oldProtect + ); + +void +RadRestoreMemoryProtection( + void* addr, + size_t size, + unsigned long oldProtect + ); + +void +RadFlushICache( + void* addr, + size_t size + ); + +#elif defined(RADIUM_PLATFORM_ANDROID) + +size_t +RadMemoryPageSize(); + +void* +RadAllocExec( + size_t size + ); + +void* +RadAllocExecNear( + void* target, + size_t size + ); + +void +RadFreeExec( + void* p, + size_t size + ); + +bool +RadMakeWritableExecutable( + void* addr, + size_t size, + unsigned long* oldProtect + ); + +void +RadRestoreMemoryProtection( + void* addr, + size_t size, + unsigned long oldProtect + ); + +void +RadFlushICache( + void* addr, + size_t size + ); + +#endif + +#endif // __LIBRADHOOK_PRIVATE_MEMORY_H__ \ No newline at end of file diff --git a/include/radhook/radhook.h b/include/radhook/radhook.h index 0bce871..2e48f02 100644 --- a/include/radhook/radhook.h +++ b/include/radhook/radhook.h @@ -1,48 +1,7 @@ #ifndef __LIBRADHOOK_HPP__ #define __LIBRADHOOK_HPP__ -#include -#include - -#include - -// platform detection -#if !defined(RADIUM_PLATFORM_WINDOWS) && !defined(RADIUM_PLATFORM_ANDROID) -# if defined(_WIN32) || defined(_WIN64) -# define RADIUM_PLATFORM_WINDOWS 1 -# elif defined(__ANDROID__) -# define RADIUM_PLATFORM_ANDROID 1 -# else -# error "radhook: unsupported platform -- define RADIUM_PLATFORM_WINDOWS or RADIUM_PLATFORM_ANDROID" -# endif -#endif - -// architecture detection -#if defined(_M_X64) || defined(__x86_64__) -# define RADHOOK_ARCH_X64 1 -#elif defined(_M_ARM64) || defined(__aarch64__) -# define RADHOOK_ARCH_ARM64 1 -#else -# error "radhook: unsupported architecture -- need x86-64 or aarch64" -#endif - -// api macros -#if defined(_WIN32) -# if defined(RADHOOK_BUILD) -# define RADHOOK_API __declspec(dllexport) -# elif defined(RADHOOK_SHARED) -# define RADHOOK_API __declspec(dllimport) -# else -# define RADHOOK_API -# endif -#else -# if defined(__GNUC__) || defined(__clang__) -# define RADHOOK_API __attribute__((visibility("default"))) -# else -# define RADHOOK_API -# endif -#endif - +#include "defines.h" #include "api.h" #endif // __LIBRADHOOK_HPP__ \ No newline at end of file diff --git a/src/memory.cpp b/src/memory.cpp new file mode 100644 index 0000000..4fe0899 --- /dev/null +++ b/src/memory.cpp @@ -0,0 +1,218 @@ +#include "radhook/private/memory.h" + +#if defined(RADIUM_PLATFORM_WINDOWS) +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include +#elif defined(RADIUM_PLATFORM_ANDROID) +# include +# include +#endif + +// platform memory backend +#if defined(RADIUM_PLATFORM_WINDOWS) + +size_t RadMemoryPageSize() { + static size_t sz = [] { + SYSTEM_INFO si; + GetSystemInfo(&si); + return static_cast(si.dwPageSize); + }(); + + return sz; +} + +void* RadAllocExec(size_t size) { + return VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); +} + +void RadFreeExec(void* p, size_t /*size*/) { + if (p) VirtualFree(p, 0, MEM_RELEASE); +} + +void* RadAllocExecNear(void* target, size_t size) { + SYSTEM_INFO si; + GetSystemInfo(&si); + const uintptr_t pageSize = si.dwPageSize; + const uintptr_t targetAddr = reinterpret_cast(target); + const uintptr_t margin = 0x7FFF0000ULL; // stay safely under INT32_MAX + + uintptr_t minAddr = reinterpret_cast(si.lpMinimumApplicationAddress); + uintptr_t maxAddr = reinterpret_cast(si.lpMaximumApplicationAddress); + if (targetAddr > margin && targetAddr - margin > minAddr) minAddr = targetAddr - margin; + if (targetAddr + margin < maxAddr) maxAddr = targetAddr + margin; + + uintptr_t alignedTarget = targetAddr & ~(pageSize - 1); + + // search backward from target + for (uintptr_t addr = alignedTarget; addr >= minAddr && addr != 0; ) { + MEMORY_BASIC_INFORMATION mbi; + if (VirtualQuery(reinterpret_cast(addr), &mbi, sizeof(mbi)) == 0) break; + uintptr_t regionBase = reinterpret_cast(mbi.BaseAddress); + if (mbi.State == MEM_FREE) { + uintptr_t allocBase = (regionBase + pageSize - 1) & ~(pageSize - 1); + if (allocBase >= minAddr && allocBase + size <= regionBase + mbi.RegionSize) { + void* p = VirtualAlloc(reinterpret_cast(allocBase), size, + MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (p) return p; + } + } + if (regionBase <= minAddr || regionBase < pageSize) break; + addr = regionBase - pageSize; + } + + // search forward from target + for (uintptr_t addr = alignedTarget; addr <= maxAddr; ) { + MEMORY_BASIC_INFORMATION mbi; + if (VirtualQuery(reinterpret_cast(addr), &mbi, sizeof(mbi)) == 0) break; + uintptr_t regionBase = reinterpret_cast(mbi.BaseAddress); + if (mbi.State == MEM_FREE) { + uintptr_t allocBase = (regionBase + pageSize - 1) & ~(pageSize - 1); + if (allocBase + size <= regionBase + mbi.RegionSize && allocBase + size <= maxAddr) { + void* p = VirtualAlloc( + reinterpret_cast(allocBase), size, + MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE + ); + if (p) return p; + } + } + if (mbi.RegionSize == 0) break; + addr = regionBase + mbi.RegionSize; + } + + return nullptr; +} + +bool RadMakeWritableExecutable(void* addr, size_t size, unsigned long* oldProtect) { + DWORD prev = 0; + if (!VirtualProtect(addr, size, PAGE_EXECUTE_READWRITE, &prev)) { + return false; + } + if (oldProtect) *oldProtect = prev; + return true; +} + +void RadRestoreMemoryProtection(void* addr, size_t size, unsigned long oldProtect) { + DWORD tmp; + VirtualProtect(addr, size, static_cast(oldProtect), &tmp); +} + +void RadFlushICache(void* addr, size_t size) { + FlushInstructionCache(GetCurrentProcess(), addr, size); +} + +#elif defined(RADIUM_PLATFORM_ANDROID) + +size_t RadMemoryPageSize() { + static size_t sz = static_cast(sysconf(_SC_PAGESIZE)); + return sz; +} + +void* RadAllocExec(size_t size) { + size_t pageSize = RadMemoryPageSize(); + size_t allocSize = (size + pageSize - 1) & ~(pageSize - 1); + void* p = mmap(nullptr, allocSize, PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + return (p == MAP_FAILED) ? nullptr : p; +} + +void RadFreeExec(void* p, size_t size) { + if (!p) return; + size_t pageSize = RadMemoryPageSize(); + size_t allocSize = (size + pageSize - 1) & ~(pageSize - 1); + munmap(p, allocSize); +} + +#ifndef MAP_FIXED_NOREPLACE +# define MAP_FIXED_NOREPLACE 0x100000 // linux >= 4.17 defined manually for older headers +#endif + +void* RadAllocExecNear(void* target, size_t size) { + size_t pageSize = RadMemoryPageSize(); + uintptr_t targetAddr = reinterpret_cast(target); + const uintptr_t margin = 0x7FFF0000ULL; + uintptr_t rangeLo = (targetAddr > margin) ? targetAddr - margin : pageSize; + uintptr_t rangeHi = targetAddr + margin; + + FILE* f = fopen("/proc/self/maps", "r"); + if (!f) return nullptr; + + struct Region { uintptr_t start, end; }; + std::vector regions; + char line[512]; + while (fgets(line, sizeof(line), f)) { + unsigned long long s = 0, e = 0; + if (sscanf(line, "%llx-%llx", &s, &e) == 2) { + regions.push_back({ static_cast(s), static_cast(e) }); + } + } + fclose(f); + std::sort(regions.begin(), regions.end(), + [](const Region& a, const Region& b) { return a.start < b.start; }); + + std::vector gaps; + uintptr_t prevEnd = rangeLo; + for (auto& r : regions) { + if (r.start >= rangeHi) break; + uintptr_t gapStart = prevEnd; + uintptr_t gapEnd = std::min(r.start, rangeHi); + if (gapEnd > gapStart) gaps.push_back({ gapStart, gapEnd }); + if (r.end > prevEnd) prevEnd = r.end; + } + if (prevEnd < rangeHi) gaps.push_back({ prevEnd, rangeHi }); + + size_t pageAlignedSize = (size + pageSize - 1) & ~(pageSize - 1); + + void* best = nullptr; + uintptr_t bestDist = static_cast(-1); + for (auto& g : gaps) { + uintptr_t alignedStart = (g.start + pageSize - 1) & ~(pageSize - 1); + uintptr_t alignedEnd = g.end & ~(pageSize - 1); + if (alignedEnd < alignedStart + pageAlignedSize) continue; + + uintptr_t lastValidStart = alignedEnd - pageAlignedSize; // already page-aligned + uintptr_t candidate; + if (targetAddr < alignedStart) candidate = alignedStart; + else if (targetAddr > lastValidStart) candidate = lastValidStart; + else candidate = targetAddr & ~(pageSize - 1); + + // clamp + if (candidate > lastValidStart) candidate = lastValidStart; + if (candidate < alignedStart) candidate = alignedStart; + + uintptr_t dist = (candidate > targetAddr) ? (candidate - targetAddr) : (targetAddr - candidate); + if (dist < bestDist) { + bestDist = dist; + best = reinterpret_cast(candidate); + } + } + if (!best) return nullptr; + + void* p = mmap(best, pageAlignedSize, PROT_READ | PROT_WRITE | PROT_EXEC, + MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0); + if (p == MAP_FAILED) return nullptr; + return p; +} + +bool RadMakeWritableExecutable(void* addr, size_t size, unsigned long* oldProtect) { + if (oldProtect) *oldProtect = 0; + size_t pageSize = RadMemoryPageSize(); + uintptr_t start = reinterpret_cast(addr) & ~(pageSize - 1); + uintptr_t end = (reinterpret_cast(addr) + size + pageSize - 1) & ~(pageSize - 1); + return mprotect(reinterpret_cast(start), end - start, + PROT_READ | PROT_WRITE | PROT_EXEC) == 0; +} + +void RadRestoreMemoryProtection(void* /*addr*/, size_t /*size*/, unsigned long /*oldProtect*/) { + // no-op +} + +void RadFlushICache(void* addr, size_t size) { + __builtin___clear_cache( + reinterpret_cast(addr), + reinterpret_cast(addr) + size + ); +} + +#endif \ No newline at end of file diff --git a/src/radhook.cpp b/src/radhook.cpp index 97dacbe..e0c5943 100644 --- a/src/radhook.cpp +++ b/src/radhook.cpp @@ -1,4 +1,6 @@ #include "radhook/radhook.h" +#include "radhook/private/disassemble.h" +#include "radhook/private/memory.h" #include #include @@ -38,6 +40,7 @@ const char* RadHookResultToString(RadHookResult result) { case RadHookResult::InvalidDetour: return "InvalidDetour"; case RadHookResult::InvalidHandle: return "InvalidHandle"; case RadHookResult::TrampolineTooFar: return "TrampolineTooFar"; + case RadHookResult::TargetTooSmall: return "TargetTooSmall"; default: return "Unknown"; } } @@ -90,374 +93,6 @@ bool IsRegisteredLocked(RadHookHandle handle) { return false; } -// platform memory backend -#if defined(RADIUM_PLATFORM_WINDOWS) - -void* AllocExec(size_t size) { - return VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); -} - -void FreeExec(void* p, size_t /*size*/) { - if (p) VirtualFree(p, 0, MEM_RELEASE); -} - -void* AllocExecNear(void* target, size_t size) { - SYSTEM_INFO si; - GetSystemInfo(&si); - const uintptr_t pageSize = si.dwPageSize; - const uintptr_t targetAddr = reinterpret_cast(target); - const uintptr_t margin = 0x7FFF0000ULL; // stay safely under INT32_MAX - - uintptr_t minAddr = reinterpret_cast(si.lpMinimumApplicationAddress); - uintptr_t maxAddr = reinterpret_cast(si.lpMaximumApplicationAddress); - if (targetAddr > margin && targetAddr - margin > minAddr) minAddr = targetAddr - margin; - if (targetAddr + margin < maxAddr) maxAddr = targetAddr + margin; - - uintptr_t alignedTarget = targetAddr & ~(pageSize - 1); - - // search backward from target - for (uintptr_t addr = alignedTarget; addr >= minAddr && addr != 0; ) { - MEMORY_BASIC_INFORMATION mbi; - if (VirtualQuery(reinterpret_cast(addr), &mbi, sizeof(mbi)) == 0) break; - uintptr_t regionBase = reinterpret_cast(mbi.BaseAddress); - if (mbi.State == MEM_FREE) { - uintptr_t allocBase = (regionBase + pageSize - 1) & ~(pageSize - 1); - if (allocBase >= minAddr && allocBase + size <= regionBase + mbi.RegionSize) { - void* p = VirtualAlloc(reinterpret_cast(allocBase), size, - MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); - if (p) return p; - } - } - if (regionBase <= minAddr || regionBase < pageSize) break; - addr = regionBase - pageSize; - } - - // search forward from target - for (uintptr_t addr = alignedTarget; addr <= maxAddr; ) { - MEMORY_BASIC_INFORMATION mbi; - if (VirtualQuery(reinterpret_cast(addr), &mbi, sizeof(mbi)) == 0) break; - uintptr_t regionBase = reinterpret_cast(mbi.BaseAddress); - if (mbi.State == MEM_FREE) { - uintptr_t allocBase = (regionBase + pageSize - 1) & ~(pageSize - 1); - if (allocBase + size <= regionBase + mbi.RegionSize && allocBase + size <= maxAddr) { - void* p = VirtualAlloc( - reinterpret_cast(allocBase), size, - MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE - ); - if (p) return p; - } - } - if (mbi.RegionSize == 0) break; - addr = regionBase + mbi.RegionSize; - } - - return nullptr; -} - -bool MakeWritableExecutable(void* addr, size_t size, unsigned long* oldProtect) { - DWORD prev = 0; - if (!VirtualProtect(addr, size, PAGE_EXECUTE_READWRITE, &prev)) { - return false; - } - if (oldProtect) *oldProtect = prev; - return true; -} - -void RestoreProtection(void* addr, size_t size, unsigned long oldProtect) { - DWORD tmp; - VirtualProtect(addr, size, static_cast(oldProtect), &tmp); -} - -void FlushICache(void* addr, size_t size) { - FlushInstructionCache(GetCurrentProcess(), addr, size); -} - -#elif defined(RADIUM_PLATFORM_ANDROID) - -size_t PageSize() { - static size_t sz = static_cast(sysconf(_SC_PAGESIZE)); - return sz; -} - -void* AllocExec(size_t size) { - size_t pageSize = PageSize(); - size_t allocSize = (size + pageSize - 1) & ~(pageSize - 1); - void* p = mmap(nullptr, allocSize, PROT_READ | PROT_WRITE | PROT_EXEC, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - return (p == MAP_FAILED) ? nullptr : p; -} - -void FreeExec(void* p, size_t size) { - if (!p) return; - size_t pageSize = PageSize(); - size_t allocSize = (size + pageSize - 1) & ~(pageSize - 1); - munmap(p, allocSize); -} - -#ifndef MAP_FIXED_NOREPLACE -# define MAP_FIXED_NOREPLACE 0x100000 // linux >= 4.17 defined manually for older headers -#endif - -void* AllocExecNear(void* target, size_t size) { - size_t pageSize = PageSize(); - uintptr_t targetAddr = reinterpret_cast(target); - const uintptr_t margin = 0x7FFF0000ULL; - uintptr_t rangeLo = (targetAddr > margin) ? targetAddr - margin : pageSize; - uintptr_t rangeHi = targetAddr + margin; - - FILE* f = fopen("/proc/self/maps", "r"); - if (!f) return nullptr; - - struct Region { uintptr_t start, end; }; - std::vector regions; - char line[512]; - while (fgets(line, sizeof(line), f)) { - unsigned long long s = 0, e = 0; - if (sscanf(line, "%llx-%llx", &s, &e) == 2) { - regions.push_back({ static_cast(s), static_cast(e) }); - } - } - fclose(f); - std::sort(regions.begin(), regions.end(), - [](const Region& a, const Region& b) { return a.start < b.start; }); - - std::vector gaps; - uintptr_t prevEnd = rangeLo; - for (auto& r : regions) { - if (r.start >= rangeHi) break; - uintptr_t gapStart = prevEnd; - uintptr_t gapEnd = std::min(r.start, rangeHi); - if (gapEnd > gapStart) gaps.push_back({ gapStart, gapEnd }); - if (r.end > prevEnd) prevEnd = r.end; - } - if (prevEnd < rangeHi) gaps.push_back({ prevEnd, rangeHi }); - - size_t pageAlignedSize = (size + pageSize - 1) & ~(pageSize - 1); - - void* best = nullptr; - uintptr_t bestDist = static_cast(-1); - for (auto& g : gaps) { - uintptr_t alignedStart = (g.start + pageSize - 1) & ~(pageSize - 1); - uintptr_t alignedEnd = g.end & ~(pageSize - 1); - if (alignedEnd < alignedStart + pageAlignedSize) continue; - - uintptr_t lastValidStart = alignedEnd - pageAlignedSize; // already page-aligned - uintptr_t candidate; - if (targetAddr < alignedStart) candidate = alignedStart; - else if (targetAddr > lastValidStart) candidate = lastValidStart; - else candidate = targetAddr & ~(pageSize - 1); - - // clamp - if (candidate > lastValidStart) candidate = lastValidStart; - if (candidate < alignedStart) candidate = alignedStart; - - uintptr_t dist = (candidate > targetAddr) ? (candidate - targetAddr) : (targetAddr - candidate); - if (dist < bestDist) { - bestDist = dist; - best = reinterpret_cast(candidate); - } - } - if (!best) return nullptr; - - void* p = mmap(best, pageAlignedSize, PROT_READ | PROT_WRITE | PROT_EXEC, - MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE, -1, 0); - if (p == MAP_FAILED) return nullptr; - return p; -} - -bool MakeWritableExecutable(void* addr, size_t size, unsigned long* oldProtect) { - if (oldProtect) *oldProtect = 0; - size_t pageSize = PageSize(); - uintptr_t start = reinterpret_cast(addr) & ~(pageSize - 1); - uintptr_t end = (reinterpret_cast(addr) + size + pageSize - 1) & ~(pageSize - 1); - return mprotect(reinterpret_cast(start), end - start, - PROT_READ | PROT_WRITE | PROT_EXEC) == 0; -} - -void RestoreProtection(void* /*addr*/, size_t /*size*/, unsigned long /*oldProtect*/) { - // no-op -} - -void FlushICache(void* addr, size_t size) { - __builtin___clear_cache( - reinterpret_cast(addr), - reinterpret_cast(addr) + size - ); -} - -#endif - -// x86-64 disassembler -#if defined(RADHOOK_ARCH_X64) - -struct X64Insn { - size_t length = 0; - bool ripRelative = false; - size_t dispOffset = 0; - bool relativeControlFlow = false; - bool valid = false; -}; - -bool IsLegacyPrefix(uint8_t b) { - switch (b) { - case 0x66: case 0x67: case 0xF0: case 0xF2: case 0xF3: - case 0x2E: case 0x36: case 0x3E: case 0x26: case 0x64: case 0x65: - return true; - default: - return false; - } -} - -X64Insn DecodeX64(const uint8_t* p, size_t avail) { - X64Insn insn; - size_t i = 0; - bool opSize16 = false; - bool rexW = false; - - while (i < avail && IsLegacyPrefix(p[i])) { - if (p[i] == 0x66) opSize16 = true; - ++i; - } - if (i < avail && (p[i] & 0xF0) == 0x40) { - rexW = (p[i] & 0x08) != 0; - ++i; - } - if (i >= avail) return insn; - - uint8_t opcode = p[i++]; - bool twoByte = false; - if (opcode == 0x0F) { - if (i >= avail) return insn; - twoByte = true; - opcode = p[i++]; - } - - bool hasModRM = false; - int immSize = 0; - bool relBranch = false; - - if (!twoByte) { - uint8_t lo = opcode & 0x0F; - - if (opcode <= 0x3D && (opcode & 0xC0) == 0x00 && lo <= 0x05 && - (opcode & 0x07) <= 0x05) { - if (lo == 0x04) { immSize = 1; } - else if (lo == 0x05) { immSize = opSize16 ? 2 : 4; } - else if (lo <= 0x03) { hasModRM = true; } - else { return insn; } - } else if (opcode >= 0x50 && opcode <= 0x5F) { /* push/pop r64, no operand bytes */ } - else if (opcode == 0x68) { immSize = opSize16 ? 2 : 4; } - else if (opcode == 0x6A) { immSize = 1; } - else if (opcode == 0x69) { hasModRM = true; immSize = opSize16 ? 2 : 4; } - else if (opcode == 0x6B) { hasModRM = true; immSize = 1; } - else if (opcode >= 0x70 && opcode <= 0x7F) { immSize = 1; relBranch = true; } - else if (opcode == 0x80) { hasModRM = true; immSize = 1; } - else if (opcode == 0x81) { hasModRM = true; immSize = opSize16 ? 2 : 4; } - else if (opcode == 0x83) { hasModRM = true; immSize = 1; } - else if (opcode >= 0x84 && opcode <= 0x8F) { hasModRM = true; } - else if (opcode >= 0x90 && opcode <= 0x97) { /* xchg/nop, no operand bytes */ } - else if (opcode == 0x98 || opcode == 0x99) { /* cbw/cwd family */ } - else if (opcode == 0xA8) { immSize = 1; } - else if (opcode == 0xA9) { immSize = opSize16 ? 2 : 4; } - else if (opcode >= 0xB0 && opcode <= 0xB7) { immSize = 1; } - else if (opcode >= 0xB8 && opcode <= 0xBF) { immSize = rexW ? 8 : (opSize16 ? 2 : 4); } - else if (opcode == 0xC0 || opcode == 0xC1) { hasModRM = true; immSize = 1; } - else if (opcode == 0xC2) { immSize = 2; } - else if (opcode == 0xC3) { /* ret */ } - else if (opcode == 0xC6) { hasModRM = true; immSize = 1; } - else if (opcode == 0xC7) { hasModRM = true; immSize = opSize16 ? 2 : 4; } - else if (opcode == 0xC9) { /* leave */ } - else if (opcode == 0xCC) { /* int3 */ } - else if (opcode == 0xCD) { immSize = 1; } - else if (opcode == 0xD0 || opcode == 0xD1 || opcode == 0xD2 || opcode == 0xD3) { hasModRM = true; } - else if (opcode == 0xE8) { immSize = 4; relBranch = true; } - else if (opcode == 0xE9) { immSize = 4; relBranch = true; } - else if (opcode == 0xEB) { immSize = 1; relBranch = true; } - else if (opcode == 0xF6) { hasModRM = true; immSize = 1; } - else if (opcode == 0xF7) { hasModRM = true; immSize = opSize16 ? 2 : 4; } - else if (opcode == 0xFE || opcode == 0xFF) { hasModRM = true; } - else { - return insn; // unrecognized opcode - } - } else { - if (opcode >= 0x80 && opcode <= 0x8F) { immSize = 4; relBranch = true; } - else if (opcode == 0x1E) { immSize = 1; } - else if (opcode == 0x1F) { hasModRM = true; } - else if (opcode == 0x05 || opcode == 0x31 || opcode == 0x34 || opcode == 0x35 || opcode == 0xA2) { /* syscall/rdtsc/cpuid etc */ } - else if (opcode >= 0x40 && opcode <= 0x4F) { hasModRM = true; } // CMOVcc - else if (opcode == 0xAF) { hasModRM = true; } // imul - else if (opcode == 0xB6 || opcode == 0xB7 || opcode == 0xBE || opcode == 0xBF) { hasModRM = true; } // movzx/movsx - else if (opcode >= 0x10 && opcode <= 0x17) { hasModRM = true; } // movups/movaps family - else if (opcode >= 0x28 && opcode <= 0x2F) { hasModRM = true; } - else if (opcode >= 0x54 && opcode <= 0x5F) { hasModRM = true; } - else if (opcode == 0x6E || opcode == 0x6F || opcode == 0x7E || opcode == 0x7F || opcode == 0xD6) { hasModRM = true; } - else { - return insn; - } - } - - if (hasModRM) { - if (i >= avail) return insn; - uint8_t modrm = p[i++]; - uint8_t mod = (modrm >> 6) & 0x3; - uint8_t rm = modrm & 0x7; - - if (mod != 3 && rm == 4) { - if (i >= avail) return insn; - uint8_t sib = p[i++]; - uint8_t base = sib & 0x7; - if (mod == 0 && base == 5) { - i += 4; - } - } - - if (mod == 0 && rm == 5) { - insn.ripRelative = true; - insn.dispOffset = i; - i += 4; - } else if (mod == 1) { - i += 1; - } else if (mod == 2) { - i += 4; - } - } - - i += static_cast(immSize); - if (i > avail) return insn; - - insn.length = i; - insn.relativeControlFlow = relBranch; - insn.valid = true; - return insn; -} - -constexpr size_t kX64StubLen = 14; // FF25 00000000 + imm64 -constexpr size_t kX64MaxStolen = 32; -constexpr size_t kX64MaxRipFixups = 8; - -bool BuildStolenBytesX64(const uint8_t* target, size_t minLen, - uint8_t* outBytes, size_t& outLen, - size_t* outRipOffsets, size_t& outRipCount) { - size_t len = 0; - outRipCount = 0; - while (len < minLen) { - if (len >= kX64MaxStolen) return false; - X64Insn insn = DecodeX64(target + len, kX64MaxStolen - len); - if (!insn.valid) return false; - if (insn.relativeControlFlow) return false; - if (insn.ripRelative) { - if (outRipCount >= kX64MaxRipFixups) return false; - outRipOffsets[outRipCount++] = len + insn.dispOffset; - } - len += insn.length; - } - std::memcpy(outBytes, target, len); - outLen = len; - return true; -} - -#endif // RADHOOK_ARCH_X64 - // aarch64 prologue check #if defined(RADHOOK_ARCH_ARM64) @@ -499,14 +134,19 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) { size_t ripOffsets[kX64MaxRipFixups]; size_t ripCount = 0; - if (!BuildStolenBytesX64(reinterpret_cast(target), kX64StubLen, - stolen, stolenLen, ripOffsets, ripCount)) { - return RadHookResult::DisassemblyFailed; + StolenBytesError sberr = BuildStolenBytesX64( + reinterpret_cast(target), kX64StubLen, + stolen, stolenLen, ripOffsets, ripCount); + + if (sberr != StolenBytesError::None) { + return (sberr == StolenBytesError::InsufficientLength) + ? RadHookResult::TargetTooSmall + : RadHookResult::DisassemblyFailed; } size_t allocSize = stolenLen + kX64StubLen; - void* trampolineAlloc = AllocExecNear(target, allocSize); - if (!trampolineAlloc) trampolineAlloc = AllocExec(allocSize); // best-effort fallback + void* trampolineAlloc = RadAllocExecNear(target, allocSize); + if (!trampolineAlloc) trampolineAlloc = RadAllocExec(allocSize); // best-effort fallback if (!trampolineAlloc) return RadHookResult::MemoryAllocFailed; std::memcpy(trampolineAlloc, stolen, stolenLen); @@ -518,7 +158,7 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) { uintptr_t absTarget = reinterpret_cast(target) + off + 4 + static_cast(originalDisp); intptr_t newDisp = static_cast(absTarget) - (reinterpret_cast(trampolineAlloc) + off + 4); if (newDisp < INT32_MIN || newDisp > INT32_MAX) { - FreeExec(trampolineAlloc, allocSize); + RadFreeExec(trampolineAlloc, allocSize); return RadHookResult::TrampolineTooFar; } int32_t newDisp32 = static_cast(newDisp); @@ -535,11 +175,11 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) { } std::memcpy(jumpBack + 6, &backAddr, 8); - FlushICache(trampolineAlloc, allocSize); + RadFlushICache(trampolineAlloc, allocSize); unsigned long oldProtect = 0; - if (!MakeWritableExecutable(target, stolenLen, &oldProtect)) { - FreeExec(trampolineAlloc, allocSize); + if (!RadMakeWritableExecutable(target, stolenLen, &oldProtect)) { + RadFreeExec(trampolineAlloc, allocSize); return RadHookResult::MemoryProtectFailed; } @@ -560,8 +200,8 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) { h->stubLength = kX64StubLen; std::memcpy(target, hookStub, kX64StubLen); - RestoreProtection(target, stolenLen, oldProtect); - FlushICache(target, stolenLen); + RadRestoreMemoryProtection(target, stolenLen, oldProtect); + RadFlushICache(target, stolenLen); h->target = target; h->detour = detour; @@ -580,18 +220,18 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) { } size_t allocSize = kArm64StubLen + kArm64StubLen; - void* trampolineAlloc = AllocExec(allocSize); + void* trampolineAlloc = RadAllocExec(allocSize); if (!trampolineAlloc) return RadHookResult::MemoryAllocFailed; std::memcpy(trampolineAlloc, target, kArm64StubLen); uintptr_t backAddr = reinterpret_cast(target) + kArm64StubLen; WriteArm64Stub(reinterpret_cast(trampolineAlloc) + kArm64StubLen, backAddr); - FlushICache(trampolineAlloc, allocSize); + RadFlushICache(trampolineAlloc, allocSize); unsigned long oldProtect = 0; - if (!MakeWritableExecutable(target, kArm64StubLen, &oldProtect)) { - FreeExec(trampolineAlloc, allocSize); + if (!RadMakeWritableExecutable(target, kArm64StubLen, &oldProtect)) { + RadFreeExec(trampolineAlloc, allocSize); return RadHookResult::MemoryProtectFailed; } @@ -605,8 +245,8 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) { h->stubLength = kArm64StubLen; std::memcpy(target, hookStub, kArm64StubLen); - RestoreProtection(target, kArm64StubLen, oldProtect); - FlushICache(target, kArm64StubLen); + RadRestoreMemoryProtection(target, kArm64StubLen, oldProtect); + RadFlushICache(target, kArm64StubLen); h->target = target; h->detour = detour; @@ -624,11 +264,11 @@ RadHookResult EnableHookInternal(RadHookOpaque* h) { if (h->enabled) return RadHookResult::AlreadyEnabled; unsigned long oldProtect = 0; - if (!MakeWritableExecutable(h->vtableSlot, sizeof(void*), &oldProtect)) { + if (!RadMakeWritableExecutable(h->vtableSlot, sizeof(void*), &oldProtect)) { return RadHookResult::MemoryProtectFailed; } *h->vtableSlot = h->detour; - RestoreProtection(h->vtableSlot, sizeof(void*), oldProtect); + RadRestoreMemoryProtection(h->vtableSlot, sizeof(void*), oldProtect); h->enabled = true; return RadHookResult::Success; @@ -638,12 +278,12 @@ RadHookResult EnableHookInternal(RadHookOpaque* h) { if (h->enabled) return RadHookResult::AlreadyEnabled; unsigned long oldProtect = 0; - if (!MakeWritableExecutable(h->target, h->originalLength, &oldProtect)) { + if (!RadMakeWritableExecutable(h->target, h->originalLength, &oldProtect)) { return RadHookResult::MemoryProtectFailed; } std::memcpy(h->target, h->stubBytes, h->stubLength); - RestoreProtection(h->target, h->originalLength, oldProtect); - FlushICache(h->target, h->originalLength); + RadRestoreMemoryProtection(h->target, h->originalLength, oldProtect); + RadFlushICache(h->target, h->originalLength); h->enabled = true; return RadHookResult::Success; @@ -655,11 +295,11 @@ RadHookResult DisableHookInternal(RadHookOpaque* h) { if (!h->enabled) return RadHookResult::AlreadyDisabled; unsigned long oldProtect = 0; - if (!MakeWritableExecutable(h->vtableSlot, sizeof(void*), &oldProtect)) { + if (!RadMakeWritableExecutable(h->vtableSlot, sizeof(void*), &oldProtect)) { return RadHookResult::MemoryProtectFailed; } *h->vtableSlot = h->vtableOriginalEntry; - RestoreProtection(h->vtableSlot, sizeof(void*), oldProtect); + RadRestoreMemoryProtection(h->vtableSlot, sizeof(void*), oldProtect); h->enabled = false; return RadHookResult::Success; @@ -669,12 +309,12 @@ RadHookResult DisableHookInternal(RadHookOpaque* h) { if (!h->enabled) return RadHookResult::AlreadyDisabled; unsigned long oldProtect = 0; - if (!MakeWritableExecutable(h->target, h->originalLength, &oldProtect)) { + if (!RadMakeWritableExecutable(h->target, h->originalLength, &oldProtect)) { return RadHookResult::MemoryProtectFailed; } std::memcpy(h->target, h->originalBytes, h->originalLength); - RestoreProtection(h->target, h->originalLength, oldProtect); - FlushICache(h->target, h->originalLength); + RadRestoreMemoryProtection(h->target, h->originalLength, oldProtect); + RadFlushICache(h->target, h->originalLength); h->enabled = false; return RadHookResult::Success; @@ -722,7 +362,7 @@ RadHookResult RadHookDestroy(RadHookHandle handle) { } if (handle->trampolineAlloc) { - FreeExec(handle->trampolineAlloc, handle->trampolineAllocSize); + RadFreeExec(handle->trampolineAlloc, handle->trampolineAllocSize); handle->trampolineAlloc = nullptr; } diff --git a/src/x64disasm.cpp b/src/x64disasm.cpp new file mode 100644 index 0000000..e9abb23 --- /dev/null +++ b/src/x64disasm.cpp @@ -0,0 +1,163 @@ +#include "radhook/private/disassemble.h" + +#include + +// x86-64 disassembler +#if defined(RADHOOK_ARCH_X64) + +bool IsLegacyPrefix(uint8_t b) { + switch (b) { + case 0x66: case 0x67: case 0xF0: case 0xF2: case 0xF3: + case 0x2E: case 0x36: case 0x3E: case 0x26: case 0x64: case 0x65: + return true; + default: + return false; + } +} + +X64Insn DecodeX64(const uint8_t* p, size_t avail) { + X64Insn insn; + size_t i = 0; + bool opSize16 = false; + bool rexW = false; + + while (i < avail && IsLegacyPrefix(p[i])) { + if (p[i] == 0x66) opSize16 = true; + ++i; + } + if (i < avail && (p[i] & 0xF0) == 0x40) { + rexW = (p[i] & 0x08) != 0; + ++i; + } + if (i >= avail) return insn; + + uint8_t opcode = p[i++]; + bool twoByte = false; + if (opcode == 0x0F) { + if (i >= avail) return insn; + twoByte = true; + opcode = p[i++]; + } + + bool hasModRM = false; + int immSize = 0; + bool relBranch = false; + + if (!twoByte) { + uint8_t lo = opcode & 0x0F; + + if (opcode <= 0x3D && (opcode & 0xC0) == 0x00 && lo <= 0x05 && + (opcode & 0x07) <= 0x05) { + if (lo == 0x04) { immSize = 1; } + else if (lo == 0x05) { immSize = opSize16 ? 2 : 4; } + else if (lo <= 0x03) { hasModRM = true; } + else { return insn; } + } else if (opcode >= 0x50 && opcode <= 0x5F) { /* push/pop r64, no operand bytes */ } + else if (opcode == 0x68) { immSize = opSize16 ? 2 : 4; } + else if (opcode == 0x6A) { immSize = 1; } + else if (opcode == 0x69) { hasModRM = true; immSize = opSize16 ? 2 : 4; } + else if (opcode == 0x6B) { hasModRM = true; immSize = 1; } + else if (opcode >= 0x70 && opcode <= 0x7F) { immSize = 1; relBranch = true; } + else if (opcode == 0x80) { hasModRM = true; immSize = 1; } + else if (opcode == 0x81) { hasModRM = true; immSize = opSize16 ? 2 : 4; } + else if (opcode == 0x83) { hasModRM = true; immSize = 1; } + else if (opcode >= 0x84 && opcode <= 0x8F) { hasModRM = true; } + else if (opcode >= 0x90 && opcode <= 0x97) { /* xchg/nop, no operand bytes */ } + else if (opcode == 0x98 || opcode == 0x99) { /* cbw/cwd family */ } + else if (opcode == 0xA8) { immSize = 1; } + else if (opcode == 0xA9) { immSize = opSize16 ? 2 : 4; } + else if (opcode >= 0xB0 && opcode <= 0xB7) { immSize = 1; } + else if (opcode >= 0xB8 && opcode <= 0xBF) { immSize = rexW ? 8 : (opSize16 ? 2 : 4); } + else if (opcode == 0xC0 || opcode == 0xC1) { hasModRM = true; immSize = 1; } + else if (opcode == 0xC2) { immSize = 2; insn.isReturn = true; } + else if (opcode == 0xC3) { /* ret */ insn.isReturn = true; } + else if (opcode == 0xC6) { hasModRM = true; immSize = 1; } + else if (opcode == 0xC7) { hasModRM = true; immSize = opSize16 ? 2 : 4; } + else if (opcode == 0xC9) { /* leave */ } + else if (opcode == 0xCC) { /* int3 */ insn.isPadding = true; } + else if (opcode == 0xCD) { immSize = 1; } + else if (opcode == 0xD0 || opcode == 0xD1 || opcode == 0xD2 || opcode == 0xD3) { hasModRM = true; } + else if (opcode == 0xE8) { immSize = 4; relBranch = true; } + else if (opcode == 0xE9) { immSize = 4; relBranch = true; } + else if (opcode == 0xEB) { immSize = 1; relBranch = true; } + else if (opcode == 0xF6) { hasModRM = true; immSize = 1; } + else if (opcode == 0xF7) { hasModRM = true; immSize = opSize16 ? 2 : 4; } + else if (opcode == 0xFE || opcode == 0xFF) { hasModRM = true; } + else { + return insn; // unrecognized opcode + } + } else { + if (opcode >= 0x80 && opcode <= 0x8F) { immSize = 4; relBranch = true; } + else if (opcode == 0x1E) { immSize = 1; } + else if (opcode == 0x1F) { hasModRM = true; } + else if (opcode == 0x05 || opcode == 0x31 || opcode == 0x34 || opcode == 0x35 || opcode == 0xA2) { /* syscall/rdtsc/cpuid etc */ } + else if (opcode >= 0x40 && opcode <= 0x4F) { hasModRM = true; } // CMOVcc + else if (opcode == 0xAF) { hasModRM = true; } // imul + else if (opcode == 0xB6 || opcode == 0xB7 || opcode == 0xBE || opcode == 0xBF) { hasModRM = true; } // movzx/movsx + else if (opcode >= 0x10 && opcode <= 0x17) { hasModRM = true; } // movups/movaps family + else if (opcode >= 0x28 && opcode <= 0x2F) { hasModRM = true; } + else if (opcode >= 0x54 && opcode <= 0x5F) { hasModRM = true; } + else if (opcode == 0x6E || opcode == 0x6F || opcode == 0x7E || opcode == 0x7F || opcode == 0xD6) { hasModRM = true; } + else { + return insn; + } + } + + if (hasModRM) { + if (i >= avail) return insn; + uint8_t modrm = p[i++]; + uint8_t mod = (modrm >> 6) & 0x3; + uint8_t rm = modrm & 0x7; + + if (mod != 3 && rm == 4) { + if (i >= avail) return insn; + uint8_t sib = p[i++]; + uint8_t base = sib & 0x7; + if (mod == 0 && base == 5) { + i += 4; + } + } + + if (mod == 0 && rm == 5) { + insn.ripRelative = true; + insn.dispOffset = i; + i += 4; + } else if (mod == 1) { + i += 1; + } else if (mod == 2) { + i += 4; + } + } + + i += static_cast(immSize); + if (i > avail) return insn; + + insn.length = i; + insn.relativeControlFlow = relBranch; + insn.valid = true; + return insn; +} + +StolenBytesError BuildStolenBytesX64(const uint8_t* target, size_t minLen, + uint8_t* outBytes, size_t& outLen, + size_t* outRipOffsets, size_t& outRipCount) { + size_t len = 0; + outRipCount = 0; + while (len < minLen) { + if (len >= kX64MaxStolen) return StolenBytesError::ExceedsMaxStolen; + X64Insn insn = DecodeX64(target + len, kX64MaxStolen - len); + if (!insn.valid) return StolenBytesError::BadDecode; + if (insn.isReturn || insn.isPadding) return StolenBytesError::InsufficientLength; + if (insn.relativeControlFlow) return StolenBytesError::RelativeControlFlow; + if (insn.ripRelative) { + if (outRipCount >= kX64MaxRipFixups) return StolenBytesError::TooManyRipFixups; + outRipOffsets[outRipCount++] = len + insn.dispOffset; + } + len += insn.length; + } + std::memcpy(outBytes, target, len); + outLen = len; + return StolenBytesError::None; +} + +#endif // RADHOOK_ARCH_X64 \ No newline at end of file