mirror of
https://git.recroomarchive.org/Radium/libradhook.git
synced 2026-08-07 09:10:59 +00:00
fix: split files, add padding and return checking on disasm
This commit is contained in:
@@ -18,6 +18,8 @@ endif()
|
|||||||
|
|
||||||
add_library(radhook
|
add_library(radhook
|
||||||
src/radhook.cpp
|
src/radhook.cpp
|
||||||
|
src/memory.cpp
|
||||||
|
src/x64disasm.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(radhook
|
target_include_directories(radhook
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ enum class RadHookResult : int {
|
|||||||
InvalidDetour,
|
InvalidDetour,
|
||||||
InvalidHandle,
|
InvalidHandle,
|
||||||
TrampolineTooFar,
|
TrampolineTooFar,
|
||||||
|
TargetTooSmall,
|
||||||
Unknown,
|
Unknown,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
46
include/radhook/defines.h
Normal file
46
include/radhook/defines.h
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#ifndef __LIBRADHOOK_DEFINES_HPP__
|
||||||
|
#define __LIBRADHOOK_DEFINES_HPP__
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
// 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__
|
||||||
57
include/radhook/private/disassemble.h
Normal file
57
include/radhook/private/disassemble.h
Normal file
@@ -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 <windows.h>
|
||||||
|
#elif defined(RADIUM_PLATFORM_ANDROID)
|
||||||
|
# include <sys/mman.h>
|
||||||
|
# include <unistd.h>
|
||||||
|
#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__
|
||||||
102
include/radhook/private/memory.h
Normal file
102
include/radhook/private/memory.h
Normal file
@@ -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 <windows.h>
|
||||||
|
#elif defined(RADIUM_PLATFORM_ANDROID)
|
||||||
|
# include <sys/mman.h>
|
||||||
|
# include <unistd.h>
|
||||||
|
#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__
|
||||||
@@ -1,48 +1,7 @@
|
|||||||
#ifndef __LIBRADHOOK_HPP__
|
#ifndef __LIBRADHOOK_HPP__
|
||||||
#define __LIBRADHOOK_HPP__
|
#define __LIBRADHOOK_HPP__
|
||||||
|
|
||||||
#include <cstdint>
|
#include "defines.h"
|
||||||
#include <cstddef>
|
|
||||||
|
|
||||||
#include <type_traits>
|
|
||||||
|
|
||||||
// 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 "api.h"
|
#include "api.h"
|
||||||
|
|
||||||
#endif // __LIBRADHOOK_HPP__
|
#endif // __LIBRADHOOK_HPP__
|
||||||
218
src/memory.cpp
Normal file
218
src/memory.cpp
Normal file
@@ -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 <windows.h>
|
||||||
|
#elif defined(RADIUM_PLATFORM_ANDROID)
|
||||||
|
# include <sys/mman.h>
|
||||||
|
# include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// platform memory backend
|
||||||
|
#if defined(RADIUM_PLATFORM_WINDOWS)
|
||||||
|
|
||||||
|
size_t RadMemoryPageSize() {
|
||||||
|
static size_t sz = [] {
|
||||||
|
SYSTEM_INFO si;
|
||||||
|
GetSystemInfo(&si);
|
||||||
|
return static_cast<size_t>(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<uintptr_t>(target);
|
||||||
|
const uintptr_t margin = 0x7FFF0000ULL; // stay safely under INT32_MAX
|
||||||
|
|
||||||
|
uintptr_t minAddr = reinterpret_cast<uintptr_t>(si.lpMinimumApplicationAddress);
|
||||||
|
uintptr_t maxAddr = reinterpret_cast<uintptr_t>(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<LPCVOID>(addr), &mbi, sizeof(mbi)) == 0) break;
|
||||||
|
uintptr_t regionBase = reinterpret_cast<uintptr_t>(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<LPVOID>(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<LPCVOID>(addr), &mbi, sizeof(mbi)) == 0) break;
|
||||||
|
uintptr_t regionBase = reinterpret_cast<uintptr_t>(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<LPVOID>(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<DWORD>(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<size_t>(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<uintptr_t>(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<Region> 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<uintptr_t>(s), static_cast<uintptr_t>(e) });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
std::sort(regions.begin(), regions.end(),
|
||||||
|
[](const Region& a, const Region& b) { return a.start < b.start; });
|
||||||
|
|
||||||
|
std::vector<Region> 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<uintptr_t>(-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<void*>(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<uintptr_t>(addr) & ~(pageSize - 1);
|
||||||
|
uintptr_t end = (reinterpret_cast<uintptr_t>(addr) + size + pageSize - 1) & ~(pageSize - 1);
|
||||||
|
return mprotect(reinterpret_cast<void*>(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<char*>(addr),
|
||||||
|
reinterpret_cast<char*>(addr) + size
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
432
src/radhook.cpp
432
src/radhook.cpp
@@ -1,4 +1,6 @@
|
|||||||
#include "radhook/radhook.h"
|
#include "radhook/radhook.h"
|
||||||
|
#include "radhook/private/disassemble.h"
|
||||||
|
#include "radhook/private/memory.h"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -38,6 +40,7 @@ const char* RadHookResultToString(RadHookResult result) {
|
|||||||
case RadHookResult::InvalidDetour: return "InvalidDetour";
|
case RadHookResult::InvalidDetour: return "InvalidDetour";
|
||||||
case RadHookResult::InvalidHandle: return "InvalidHandle";
|
case RadHookResult::InvalidHandle: return "InvalidHandle";
|
||||||
case RadHookResult::TrampolineTooFar: return "TrampolineTooFar";
|
case RadHookResult::TrampolineTooFar: return "TrampolineTooFar";
|
||||||
|
case RadHookResult::TargetTooSmall: return "TargetTooSmall";
|
||||||
default: return "Unknown";
|
default: return "Unknown";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,374 +93,6 @@ bool IsRegisteredLocked(RadHookHandle handle) {
|
|||||||
return false;
|
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<uintptr_t>(target);
|
|
||||||
const uintptr_t margin = 0x7FFF0000ULL; // stay safely under INT32_MAX
|
|
||||||
|
|
||||||
uintptr_t minAddr = reinterpret_cast<uintptr_t>(si.lpMinimumApplicationAddress);
|
|
||||||
uintptr_t maxAddr = reinterpret_cast<uintptr_t>(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<LPCVOID>(addr), &mbi, sizeof(mbi)) == 0) break;
|
|
||||||
uintptr_t regionBase = reinterpret_cast<uintptr_t>(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<LPVOID>(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<LPCVOID>(addr), &mbi, sizeof(mbi)) == 0) break;
|
|
||||||
uintptr_t regionBase = reinterpret_cast<uintptr_t>(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<LPVOID>(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<DWORD>(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<size_t>(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<uintptr_t>(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<Region> 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<uintptr_t>(s), static_cast<uintptr_t>(e) });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fclose(f);
|
|
||||||
std::sort(regions.begin(), regions.end(),
|
|
||||||
[](const Region& a, const Region& b) { return a.start < b.start; });
|
|
||||||
|
|
||||||
std::vector<Region> 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<uintptr_t>(-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<void*>(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<uintptr_t>(addr) & ~(pageSize - 1);
|
|
||||||
uintptr_t end = (reinterpret_cast<uintptr_t>(addr) + size + pageSize - 1) & ~(pageSize - 1);
|
|
||||||
return mprotect(reinterpret_cast<void*>(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<char*>(addr),
|
|
||||||
reinterpret_cast<char*>(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<size_t>(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
|
// aarch64 prologue check
|
||||||
#if defined(RADHOOK_ARCH_ARM64)
|
#if defined(RADHOOK_ARCH_ARM64)
|
||||||
|
|
||||||
@@ -499,14 +134,19 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) {
|
|||||||
size_t ripOffsets[kX64MaxRipFixups];
|
size_t ripOffsets[kX64MaxRipFixups];
|
||||||
size_t ripCount = 0;
|
size_t ripCount = 0;
|
||||||
|
|
||||||
if (!BuildStolenBytesX64(reinterpret_cast<const uint8_t*>(target), kX64StubLen,
|
StolenBytesError sberr = BuildStolenBytesX64(
|
||||||
stolen, stolenLen, ripOffsets, ripCount)) {
|
reinterpret_cast<const uint8_t*>(target), kX64StubLen,
|
||||||
return RadHookResult::DisassemblyFailed;
|
stolen, stolenLen, ripOffsets, ripCount);
|
||||||
|
|
||||||
|
if (sberr != StolenBytesError::None) {
|
||||||
|
return (sberr == StolenBytesError::InsufficientLength)
|
||||||
|
? RadHookResult::TargetTooSmall
|
||||||
|
: RadHookResult::DisassemblyFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t allocSize = stolenLen + kX64StubLen;
|
size_t allocSize = stolenLen + kX64StubLen;
|
||||||
void* trampolineAlloc = AllocExecNear(target, allocSize);
|
void* trampolineAlloc = RadAllocExecNear(target, allocSize);
|
||||||
if (!trampolineAlloc) trampolineAlloc = AllocExec(allocSize); // best-effort fallback
|
if (!trampolineAlloc) trampolineAlloc = RadAllocExec(allocSize); // best-effort fallback
|
||||||
if (!trampolineAlloc) return RadHookResult::MemoryAllocFailed;
|
if (!trampolineAlloc) return RadHookResult::MemoryAllocFailed;
|
||||||
|
|
||||||
std::memcpy(trampolineAlloc, stolen, stolenLen);
|
std::memcpy(trampolineAlloc, stolen, stolenLen);
|
||||||
@@ -518,7 +158,7 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) {
|
|||||||
uintptr_t absTarget = reinterpret_cast<uintptr_t>(target) + off + 4 + static_cast<intptr_t>(originalDisp);
|
uintptr_t absTarget = reinterpret_cast<uintptr_t>(target) + off + 4 + static_cast<intptr_t>(originalDisp);
|
||||||
intptr_t newDisp = static_cast<intptr_t>(absTarget) - (reinterpret_cast<intptr_t>(trampolineAlloc) + off + 4);
|
intptr_t newDisp = static_cast<intptr_t>(absTarget) - (reinterpret_cast<intptr_t>(trampolineAlloc) + off + 4);
|
||||||
if (newDisp < INT32_MIN || newDisp > INT32_MAX) {
|
if (newDisp < INT32_MIN || newDisp > INT32_MAX) {
|
||||||
FreeExec(trampolineAlloc, allocSize);
|
RadFreeExec(trampolineAlloc, allocSize);
|
||||||
return RadHookResult::TrampolineTooFar;
|
return RadHookResult::TrampolineTooFar;
|
||||||
}
|
}
|
||||||
int32_t newDisp32 = static_cast<int32_t>(newDisp);
|
int32_t newDisp32 = static_cast<int32_t>(newDisp);
|
||||||
@@ -535,11 +175,11 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) {
|
|||||||
}
|
}
|
||||||
std::memcpy(jumpBack + 6, &backAddr, 8);
|
std::memcpy(jumpBack + 6, &backAddr, 8);
|
||||||
|
|
||||||
FlushICache(trampolineAlloc, allocSize);
|
RadFlushICache(trampolineAlloc, allocSize);
|
||||||
|
|
||||||
unsigned long oldProtect = 0;
|
unsigned long oldProtect = 0;
|
||||||
if (!MakeWritableExecutable(target, stolenLen, &oldProtect)) {
|
if (!RadMakeWritableExecutable(target, stolenLen, &oldProtect)) {
|
||||||
FreeExec(trampolineAlloc, allocSize);
|
RadFreeExec(trampolineAlloc, allocSize);
|
||||||
return RadHookResult::MemoryProtectFailed;
|
return RadHookResult::MemoryProtectFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -560,8 +200,8 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) {
|
|||||||
h->stubLength = kX64StubLen;
|
h->stubLength = kX64StubLen;
|
||||||
|
|
||||||
std::memcpy(target, hookStub, kX64StubLen);
|
std::memcpy(target, hookStub, kX64StubLen);
|
||||||
RestoreProtection(target, stolenLen, oldProtect);
|
RadRestoreMemoryProtection(target, stolenLen, oldProtect);
|
||||||
FlushICache(target, stolenLen);
|
RadFlushICache(target, stolenLen);
|
||||||
|
|
||||||
h->target = target;
|
h->target = target;
|
||||||
h->detour = detour;
|
h->detour = detour;
|
||||||
@@ -580,18 +220,18 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
size_t allocSize = kArm64StubLen + kArm64StubLen;
|
size_t allocSize = kArm64StubLen + kArm64StubLen;
|
||||||
void* trampolineAlloc = AllocExec(allocSize);
|
void* trampolineAlloc = RadAllocExec(allocSize);
|
||||||
if (!trampolineAlloc) return RadHookResult::MemoryAllocFailed;
|
if (!trampolineAlloc) return RadHookResult::MemoryAllocFailed;
|
||||||
|
|
||||||
std::memcpy(trampolineAlloc, target, kArm64StubLen);
|
std::memcpy(trampolineAlloc, target, kArm64StubLen);
|
||||||
uintptr_t backAddr = reinterpret_cast<uintptr_t>(target) + kArm64StubLen;
|
uintptr_t backAddr = reinterpret_cast<uintptr_t>(target) + kArm64StubLen;
|
||||||
WriteArm64Stub(reinterpret_cast<uint8_t*>(trampolineAlloc) + kArm64StubLen, backAddr);
|
WriteArm64Stub(reinterpret_cast<uint8_t*>(trampolineAlloc) + kArm64StubLen, backAddr);
|
||||||
|
|
||||||
FlushICache(trampolineAlloc, allocSize);
|
RadFlushICache(trampolineAlloc, allocSize);
|
||||||
|
|
||||||
unsigned long oldProtect = 0;
|
unsigned long oldProtect = 0;
|
||||||
if (!MakeWritableExecutable(target, kArm64StubLen, &oldProtect)) {
|
if (!RadMakeWritableExecutable(target, kArm64StubLen, &oldProtect)) {
|
||||||
FreeExec(trampolineAlloc, allocSize);
|
RadFreeExec(trampolineAlloc, allocSize);
|
||||||
return RadHookResult::MemoryProtectFailed;
|
return RadHookResult::MemoryProtectFailed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -605,8 +245,8 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) {
|
|||||||
h->stubLength = kArm64StubLen;
|
h->stubLength = kArm64StubLen;
|
||||||
|
|
||||||
std::memcpy(target, hookStub, kArm64StubLen);
|
std::memcpy(target, hookStub, kArm64StubLen);
|
||||||
RestoreProtection(target, kArm64StubLen, oldProtect);
|
RadRestoreMemoryProtection(target, kArm64StubLen, oldProtect);
|
||||||
FlushICache(target, kArm64StubLen);
|
RadFlushICache(target, kArm64StubLen);
|
||||||
|
|
||||||
h->target = target;
|
h->target = target;
|
||||||
h->detour = detour;
|
h->detour = detour;
|
||||||
@@ -624,11 +264,11 @@ RadHookResult EnableHookInternal(RadHookOpaque* h) {
|
|||||||
if (h->enabled) return RadHookResult::AlreadyEnabled;
|
if (h->enabled) return RadHookResult::AlreadyEnabled;
|
||||||
|
|
||||||
unsigned long oldProtect = 0;
|
unsigned long oldProtect = 0;
|
||||||
if (!MakeWritableExecutable(h->vtableSlot, sizeof(void*), &oldProtect)) {
|
if (!RadMakeWritableExecutable(h->vtableSlot, sizeof(void*), &oldProtect)) {
|
||||||
return RadHookResult::MemoryProtectFailed;
|
return RadHookResult::MemoryProtectFailed;
|
||||||
}
|
}
|
||||||
*h->vtableSlot = h->detour;
|
*h->vtableSlot = h->detour;
|
||||||
RestoreProtection(h->vtableSlot, sizeof(void*), oldProtect);
|
RadRestoreMemoryProtection(h->vtableSlot, sizeof(void*), oldProtect);
|
||||||
|
|
||||||
h->enabled = true;
|
h->enabled = true;
|
||||||
return RadHookResult::Success;
|
return RadHookResult::Success;
|
||||||
@@ -638,12 +278,12 @@ RadHookResult EnableHookInternal(RadHookOpaque* h) {
|
|||||||
if (h->enabled) return RadHookResult::AlreadyEnabled;
|
if (h->enabled) return RadHookResult::AlreadyEnabled;
|
||||||
|
|
||||||
unsigned long oldProtect = 0;
|
unsigned long oldProtect = 0;
|
||||||
if (!MakeWritableExecutable(h->target, h->originalLength, &oldProtect)) {
|
if (!RadMakeWritableExecutable(h->target, h->originalLength, &oldProtect)) {
|
||||||
return RadHookResult::MemoryProtectFailed;
|
return RadHookResult::MemoryProtectFailed;
|
||||||
}
|
}
|
||||||
std::memcpy(h->target, h->stubBytes, h->stubLength);
|
std::memcpy(h->target, h->stubBytes, h->stubLength);
|
||||||
RestoreProtection(h->target, h->originalLength, oldProtect);
|
RadRestoreMemoryProtection(h->target, h->originalLength, oldProtect);
|
||||||
FlushICache(h->target, h->originalLength);
|
RadFlushICache(h->target, h->originalLength);
|
||||||
|
|
||||||
h->enabled = true;
|
h->enabled = true;
|
||||||
return RadHookResult::Success;
|
return RadHookResult::Success;
|
||||||
@@ -655,11 +295,11 @@ RadHookResult DisableHookInternal(RadHookOpaque* h) {
|
|||||||
if (!h->enabled) return RadHookResult::AlreadyDisabled;
|
if (!h->enabled) return RadHookResult::AlreadyDisabled;
|
||||||
|
|
||||||
unsigned long oldProtect = 0;
|
unsigned long oldProtect = 0;
|
||||||
if (!MakeWritableExecutable(h->vtableSlot, sizeof(void*), &oldProtect)) {
|
if (!RadMakeWritableExecutable(h->vtableSlot, sizeof(void*), &oldProtect)) {
|
||||||
return RadHookResult::MemoryProtectFailed;
|
return RadHookResult::MemoryProtectFailed;
|
||||||
}
|
}
|
||||||
*h->vtableSlot = h->vtableOriginalEntry;
|
*h->vtableSlot = h->vtableOriginalEntry;
|
||||||
RestoreProtection(h->vtableSlot, sizeof(void*), oldProtect);
|
RadRestoreMemoryProtection(h->vtableSlot, sizeof(void*), oldProtect);
|
||||||
|
|
||||||
h->enabled = false;
|
h->enabled = false;
|
||||||
return RadHookResult::Success;
|
return RadHookResult::Success;
|
||||||
@@ -669,12 +309,12 @@ RadHookResult DisableHookInternal(RadHookOpaque* h) {
|
|||||||
if (!h->enabled) return RadHookResult::AlreadyDisabled;
|
if (!h->enabled) return RadHookResult::AlreadyDisabled;
|
||||||
|
|
||||||
unsigned long oldProtect = 0;
|
unsigned long oldProtect = 0;
|
||||||
if (!MakeWritableExecutable(h->target, h->originalLength, &oldProtect)) {
|
if (!RadMakeWritableExecutable(h->target, h->originalLength, &oldProtect)) {
|
||||||
return RadHookResult::MemoryProtectFailed;
|
return RadHookResult::MemoryProtectFailed;
|
||||||
}
|
}
|
||||||
std::memcpy(h->target, h->originalBytes, h->originalLength);
|
std::memcpy(h->target, h->originalBytes, h->originalLength);
|
||||||
RestoreProtection(h->target, h->originalLength, oldProtect);
|
RadRestoreMemoryProtection(h->target, h->originalLength, oldProtect);
|
||||||
FlushICache(h->target, h->originalLength);
|
RadFlushICache(h->target, h->originalLength);
|
||||||
|
|
||||||
h->enabled = false;
|
h->enabled = false;
|
||||||
return RadHookResult::Success;
|
return RadHookResult::Success;
|
||||||
@@ -722,7 +362,7 @@ RadHookResult RadHookDestroy(RadHookHandle handle) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (handle->trampolineAlloc) {
|
if (handle->trampolineAlloc) {
|
||||||
FreeExec(handle->trampolineAlloc, handle->trampolineAllocSize);
|
RadFreeExec(handle->trampolineAlloc, handle->trampolineAllocSize);
|
||||||
handle->trampolineAlloc = nullptr;
|
handle->trampolineAlloc = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
163
src/x64disasm.cpp
Normal file
163
src/x64disasm.cpp
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
#include "radhook/private/disassemble.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
// 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<size_t>(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
|
||||||
Reference in New Issue
Block a user