fix: add support for "E9 rel32" for shorter method lengths

This commit is contained in:
nexusverypro
2026-07-05 19:31:46 +01:00
parent edf088f071
commit af6cf37212
4 changed files with 144 additions and 37 deletions

View File

@@ -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,
};

View File

@@ -15,13 +15,16 @@
#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;
size_t length = 0;
bool ripRelative = false;
size_t dispOffset = 0;
bool relativeControlFlow = false;
bool isReturn = false;
bool isPadding = false;
bool branchDisp = false;
size_t branchDispOffset = 0;
uint8_t branchDispSize = 0;
bool valid = false;
};
X64Insn
@@ -30,9 +33,11 @@ DecodeX64(
size_t avail
);
constexpr size_t kX64StubLen = 14; // FF25 00000000 + imm64
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)

View File

@@ -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;
{
jumpBack[0] = 0xFF;
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[2] = 0; jumpBack[3] = 0;
jumpBack[4] = 0; jumpBack[5] = 0;
std::memcpy(jumpBack + 6, &backAddr, 8);
}
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];
{
hookStub[0] = 0xFF;
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[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);

View File

@@ -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);