mirror of
https://git.recroomarchive.org/Radium/libradhook.git
synced 2026-08-07 09:10:59 +00:00
fix: add support for "E9 rel32" for shorter method lengths
This commit is contained in:
@@ -13,7 +13,7 @@
|
||||
// versioning
|
||||
#define RADHOOK_VERSION_MAJOR 1
|
||||
#define RADHOOK_VERSION_MINOR 1
|
||||
#define RADHOOK_VERSION_PATCH 0
|
||||
#define RADHOOK_VERSION_PATCH 1
|
||||
|
||||
#if _MSC_VER
|
||||
# include <sal.h> // for source code annotations
|
||||
@@ -41,8 +41,12 @@ enum class RadHookResult : int {
|
||||
InvalidDetour,
|
||||
InvalidHandle,
|
||||
TrampolineTooFar,
|
||||
TargetTooSmall,
|
||||
UnknownTarget,
|
||||
BadDecode,
|
||||
RelativeControlFlow,
|
||||
TooManyRipFixups,
|
||||
ExceedsMaxStolen,
|
||||
InsufficientLength,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ struct X64Insn {
|
||||
bool relativeControlFlow = false;
|
||||
bool isReturn = false;
|
||||
bool isPadding = false;
|
||||
bool branchDisp = false;
|
||||
size_t branchDispOffset = 0;
|
||||
uint8_t branchDispSize = 0;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
@@ -31,8 +34,10 @@ DecodeX64(
|
||||
);
|
||||
|
||||
constexpr size_t kX64StubLen = 14; // FF25 00000000 + imm64
|
||||
constexpr size_t kX64StubLenShort = 5; // E9 rel32
|
||||
constexpr size_t kX64MaxStolen = 32;
|
||||
constexpr size_t kX64MaxRipFixups = 8;
|
||||
constexpr size_t kX64MaxBranchFixups = 8;
|
||||
|
||||
enum class StolenBytesError {
|
||||
None,
|
||||
@@ -43,6 +48,12 @@ enum class StolenBytesError {
|
||||
InsufficientLength,
|
||||
};
|
||||
|
||||
struct BranchFixup {
|
||||
size_t offset;
|
||||
size_t dispOffset;
|
||||
uint8_t dispSize;
|
||||
};
|
||||
|
||||
StolenBytesError
|
||||
BuildStolenBytesX64(
|
||||
const uint8_t* target,
|
||||
@@ -50,7 +61,9 @@ BuildStolenBytesX64(
|
||||
uint8_t* outBytes,
|
||||
size_t& outLen,
|
||||
size_t* outRipOffsets,
|
||||
size_t& outRipCount
|
||||
size_t& outRipCount,
|
||||
BranchFixup* outBranches,
|
||||
size_t& outBranchCount
|
||||
);
|
||||
|
||||
#endif // defined(RADHOOK_ARCH_X64)
|
||||
|
||||
@@ -40,8 +40,12 @@ 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";
|
||||
case RadHookResult::UnknownTarget: return "UnknownTarget";
|
||||
case RadHookResult::BadDecode: return "BadDecode";
|
||||
case RadHookResult::RelativeControlFlow:return "RelativeControlFlow";
|
||||
case RadHookResult::TooManyRipFixups: return "TooManyRipFixups";
|
||||
case RadHookResult::ExceedsMaxStolen: return "ExceedsMaxStolen";
|
||||
case RadHookResult::InsufficientLength: return "InsufficientLength";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
@@ -130,28 +134,41 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) {
|
||||
if (!detour) return RadHookResult::InvalidDetour;
|
||||
|
||||
#if defined(RADHOOK_ARCH_X64)
|
||||
auto fitsRel32 = [](intptr_t delta) {
|
||||
return delta >= INT32_MIN && delta <= INT32_MAX;
|
||||
};
|
||||
|
||||
uintptr_t targetAddr = reinterpret_cast<uintptr_t>(target);
|
||||
uintptr_t detourAddr = reinterpret_cast<uintptr_t>(detour);
|
||||
bool hookStubShort = fitsRel32(static_cast<intptr_t>(detourAddr) - static_cast<intptr_t>(targetAddr + kX64StubLenShort));
|
||||
size_t hookStubLen = hookStubShort ? kX64StubLenShort : kX64StubLen;
|
||||
|
||||
uint8_t stolen[kX64MaxStolen];
|
||||
size_t stolenLen = 0;
|
||||
size_t ripOffsets[kX64MaxRipFixups];
|
||||
size_t ripCount = 0;
|
||||
BranchFixup branches[kX64MaxBranchFixups];
|
||||
size_t branchCount = 0;
|
||||
|
||||
StolenBytesError sberr = BuildStolenBytesX64(
|
||||
reinterpret_cast<const uint8_t*>(target), kX64StubLen,
|
||||
stolen, stolenLen, ripOffsets, ripCount);
|
||||
reinterpret_cast<const uint8_t*>(target), hookStubLen,
|
||||
stolen, stolenLen, ripOffsets, ripCount, branches, branchCount);
|
||||
|
||||
if (sberr != StolenBytesError::None) {
|
||||
return (sberr == StolenBytesError::InsufficientLength)
|
||||
? RadHookResult::TargetTooSmall
|
||||
: RadHookResult::DisassemblyFailed;
|
||||
return static_cast<RadHookResult>(
|
||||
static_cast<int>(RadHookResult::UnknownTarget) + static_cast<int>(sberr)
|
||||
);
|
||||
}
|
||||
|
||||
// trampoline = stolen bytes + jump back to target
|
||||
size_t allocSize = stolenLen + kX64StubLen;
|
||||
void* trampolineAlloc = RadAllocExecNear(target, allocSize);
|
||||
if (!trampolineAlloc) trampolineAlloc = RadAllocExec(allocSize); // best-effort fallback
|
||||
if (!trampolineAlloc) trampolineAlloc = RadAllocExec(allocSize);
|
||||
if (!trampolineAlloc) return RadHookResult::MemoryAllocFailed;
|
||||
|
||||
std::memcpy(trampolineAlloc, stolen, stolenLen);
|
||||
|
||||
// rewrite rip-relative operands
|
||||
for (size_t k = 0; k < ripCount; ++k) {
|
||||
size_t off = ripOffsets[k];
|
||||
int32_t originalDisp = 0;
|
||||
@@ -166,18 +183,60 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) {
|
||||
std::memcpy(reinterpret_cast<uint8_t*>(trampolineAlloc) + off, &newDisp32, 4);
|
||||
}
|
||||
|
||||
// rewrite relative branch displacements
|
||||
for (size_t k = 0; k < branchCount; ++k) {
|
||||
size_t insnOff = branches[k].offset;
|
||||
size_t dispOff = branches[k].dispOffset;
|
||||
uint8_t dispSize = branches[k].dispSize;
|
||||
|
||||
int32_t originalDisp = 0;
|
||||
std::memcpy(&originalDisp, reinterpret_cast<const uint8_t*>(target) + dispOff, dispSize);
|
||||
if (dispSize == 1) originalDisp = static_cast<int8_t>(originalDisp); // sign-extend rel8
|
||||
|
||||
size_t insnLenAfterDisp = dispOff - insnOff + dispSize;
|
||||
uintptr_t absTarget = reinterpret_cast<uintptr_t>(target) + insnOff + insnLenAfterDisp + static_cast<intptr_t>(originalDisp);
|
||||
|
||||
intptr_t newDisp = static_cast<intptr_t>(absTarget) -
|
||||
(reinterpret_cast<intptr_t>(trampolineAlloc) + dispOff + dispSize);
|
||||
|
||||
if (dispSize == 1) {
|
||||
// rel8
|
||||
if (newDisp < INT8_MIN || newDisp > INT8_MAX) {
|
||||
RadFreeExec(trampolineAlloc, allocSize);
|
||||
return RadHookResult::TrampolineTooFar;
|
||||
}
|
||||
int8_t d8 = static_cast<int8_t>(newDisp);
|
||||
std::memcpy(reinterpret_cast<uint8_t*>(trampolineAlloc) + dispOff, &d8, 1);
|
||||
} else {
|
||||
if (newDisp < INT32_MIN || newDisp > INT32_MAX) {
|
||||
RadFreeExec(trampolineAlloc, allocSize);
|
||||
return RadHookResult::TrampolineTooFar;
|
||||
}
|
||||
int32_t d32 = static_cast<int32_t>(newDisp);
|
||||
std::memcpy(reinterpret_cast<uint8_t*>(trampolineAlloc) + dispOff, &d32, 4);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t* jumpBack = reinterpret_cast<uint8_t*>(trampolineAlloc) + stolenLen;
|
||||
uintptr_t backAddr = reinterpret_cast<uintptr_t>(target) + stolenLen;
|
||||
{
|
||||
intptr_t jumpBackDeltaShort = static_cast<intptr_t>(backAddr) -
|
||||
(reinterpret_cast<intptr_t>(jumpBack) + kX64StubLenShort);
|
||||
|
||||
if (fitsRel32(jumpBackDeltaShort)) {
|
||||
jumpBack[0] = 0xE9;
|
||||
int32_t d32 = static_cast<int32_t>(jumpBackDeltaShort);
|
||||
std::memcpy(jumpBack + 1, &d32, 4);
|
||||
} else {
|
||||
jumpBack[0] = 0xFF;
|
||||
jumpBack[1] = 0x25;
|
||||
jumpBack[2] = 0; jumpBack[3] = 0;
|
||||
jumpBack[4] = 0; jumpBack[5] = 0;
|
||||
}
|
||||
std::memcpy(jumpBack + 6, &backAddr, 8);
|
||||
}
|
||||
|
||||
RadFlushICache(trampolineAlloc, allocSize);
|
||||
|
||||
// patch target in place with a jump to the detour
|
||||
unsigned long oldProtect = 0;
|
||||
if (!RadMakeWritableExecutable(target, stolenLen, &oldProtect)) {
|
||||
RadFreeExec(trampolineAlloc, allocSize);
|
||||
@@ -188,19 +247,22 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) {
|
||||
h->originalLength = stolenLen;
|
||||
|
||||
uint8_t hookStub[kX64StubLen];
|
||||
{
|
||||
if (hookStubShort) {
|
||||
hookStub[0] = 0xE9;
|
||||
int32_t d32 = static_cast<int32_t>(detourAddr - (targetAddr + kX64StubLenShort));
|
||||
std::memcpy(hookStub + 1, &d32, 4);
|
||||
} else {
|
||||
hookStub[0] = 0xFF;
|
||||
hookStub[1] = 0x25;
|
||||
hookStub[2] = 0; hookStub[3] = 0;
|
||||
hookStub[4] = 0; hookStub[5] = 0;
|
||||
std::memcpy(hookStub + 6, &detourAddr, 8);
|
||||
}
|
||||
|
||||
uintptr_t detourAddr = reinterpret_cast<uintptr_t>(detour);
|
||||
std::memcpy(hookStub + 6, &detourAddr, 8);
|
||||
std::memcpy(h->stubBytes, hookStub, kX64StubLen);
|
||||
h->stubLength = kX64StubLen;
|
||||
std::memcpy(h->stubBytes, hookStub, hookStubLen);
|
||||
h->stubLength = hookStubLen;
|
||||
|
||||
std::memcpy(target, hookStub, kX64StubLen);
|
||||
std::memcpy(target, hookStub, hookStubLen);
|
||||
RadRestoreMemoryProtection(target, stolenLen, oldProtect);
|
||||
RadFlushICache(target, stolenLen);
|
||||
|
||||
@@ -213,6 +275,8 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) {
|
||||
return RadHookResult::Success;
|
||||
|
||||
#elif defined(RADHOOK_ARCH_ARM64)
|
||||
// bail if any of the first N instructions use pc-relative addressing
|
||||
// we dont fix these up on arm64 yet
|
||||
const uint32_t* src = reinterpret_cast<const uint32_t*>(target);
|
||||
for (size_t k = 0; k < kArm64StubLen / 4; ++k) {
|
||||
if (IsArm64PcRelative(src[k])) {
|
||||
@@ -220,6 +284,7 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) {
|
||||
}
|
||||
}
|
||||
|
||||
// trampoline = original instructions + absolute branch back to target
|
||||
size_t allocSize = kArm64StubLen + kArm64StubLen;
|
||||
void* trampolineAlloc = RadAllocExec(allocSize);
|
||||
if (!trampolineAlloc) return RadHookResult::MemoryAllocFailed;
|
||||
@@ -230,6 +295,7 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) {
|
||||
|
||||
RadFlushICache(trampolineAlloc, allocSize);
|
||||
|
||||
// patch target in place with an absolute branch to the detour
|
||||
unsigned long oldProtect = 0;
|
||||
if (!RadMakeWritableExecutable(target, kArm64StubLen, &oldProtect)) {
|
||||
RadFreeExec(trampolineAlloc, allocSize);
|
||||
|
||||
@@ -57,7 +57,10 @@ X64Insn DecodeX64(const uint8_t* p, size_t avail) {
|
||||
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 >= 0x70 && opcode <= 0x7F) {
|
||||
immSize = 1; relBranch = true;
|
||||
insn.branchDisp = true; insn.branchDispSize = 1;
|
||||
}
|
||||
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; }
|
||||
@@ -77,9 +80,18 @@ X64Insn DecodeX64(const uint8_t* p, size_t avail) {
|
||||
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 == 0xE8) {
|
||||
immSize = 4; relBranch = true;
|
||||
insn.branchDisp = true; insn.branchDispSize = 4;
|
||||
}
|
||||
else if (opcode == 0xE9) {
|
||||
immSize = 4; relBranch = true;
|
||||
insn.branchDisp = true; insn.branchDispSize = 4;
|
||||
}
|
||||
else if (opcode == 0xEB) {
|
||||
immSize = 1; relBranch = true;
|
||||
insn.branchDisp = true; insn.branchDispSize = 1;
|
||||
}
|
||||
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; }
|
||||
@@ -87,7 +99,10 @@ X64Insn DecodeX64(const uint8_t* p, size_t avail) {
|
||||
return insn; // unrecognized opcode
|
||||
}
|
||||
} else {
|
||||
if (opcode >= 0x80 && opcode <= 0x8F) { immSize = 4; relBranch = true; }
|
||||
if (opcode >= 0x80 && opcode <= 0x8F) {
|
||||
immSize = 4; relBranch = true;
|
||||
insn.branchDisp = true; insn.branchDispSize = 4;
|
||||
}
|
||||
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 */ }
|
||||
@@ -129,6 +144,8 @@ X64Insn DecodeX64(const uint8_t* p, size_t avail) {
|
||||
}
|
||||
}
|
||||
|
||||
if (insn.branchDisp) insn.branchDispOffset = i; // i is at the start of the displacement bytes
|
||||
|
||||
i += static_cast<size_t>(immSize);
|
||||
if (i > avail) return insn;
|
||||
|
||||
@@ -140,19 +157,26 @@ X64Insn DecodeX64(const uint8_t* p, size_t avail) {
|
||||
|
||||
StolenBytesError BuildStolenBytesX64(const uint8_t* target, size_t minLen,
|
||||
uint8_t* outBytes, size_t& outLen,
|
||||
size_t* outRipOffsets, size_t& outRipCount) {
|
||||
size_t* outRipOffsets, size_t& outRipCount,
|
||||
BranchFixup* outBranches, size_t& outBranchCount) {
|
||||
size_t len = 0;
|
||||
outRipCount = 0;
|
||||
outBranchCount = 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;
|
||||
}
|
||||
if (insn.branchDisp) {
|
||||
if (outBranchCount >= kX64MaxBranchFixups) return StolenBytesError::TooManyRipFixups;
|
||||
outBranches[outBranchCount++] = { len, len + insn.branchDispOffset, insn.branchDispSize };
|
||||
}
|
||||
|
||||
len += insn.length;
|
||||
}
|
||||
std::memcpy(outBytes, target, len);
|
||||
|
||||
Reference in New Issue
Block a user