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 // versioning
#define RADHOOK_VERSION_MAJOR 1 #define RADHOOK_VERSION_MAJOR 1
#define RADHOOK_VERSION_MINOR 1 #define RADHOOK_VERSION_MINOR 1
#define RADHOOK_VERSION_PATCH 0 #define RADHOOK_VERSION_PATCH 1
#if _MSC_VER #if _MSC_VER
# include <sal.h> // for source code annotations # include <sal.h> // for source code annotations
@@ -41,8 +41,12 @@ enum class RadHookResult : int {
InvalidDetour, InvalidDetour,
InvalidHandle, InvalidHandle,
TrampolineTooFar, TrampolineTooFar,
TargetTooSmall,
UnknownTarget, UnknownTarget,
BadDecode,
RelativeControlFlow,
TooManyRipFixups,
ExceedsMaxStolen,
InsufficientLength,
Unknown, Unknown,
}; };

View File

@@ -15,13 +15,16 @@
#if defined(RADHOOK_ARCH_X64) #if defined(RADHOOK_ARCH_X64)
struct X64Insn { struct X64Insn {
size_t length = 0; size_t length = 0;
bool ripRelative = false; bool ripRelative = false;
size_t dispOffset = 0; size_t dispOffset = 0;
bool relativeControlFlow = false; bool relativeControlFlow = false;
bool isReturn = false; bool isReturn = false;
bool isPadding = false; bool isPadding = false;
bool valid = false; bool branchDisp = false;
size_t branchDispOffset = 0;
uint8_t branchDispSize = 0;
bool valid = false;
}; };
X64Insn X64Insn
@@ -30,9 +33,11 @@ DecodeX64(
size_t avail 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 kX64MaxStolen = 32;
constexpr size_t kX64MaxRipFixups = 8; constexpr size_t kX64MaxRipFixups = 8;
constexpr size_t kX64MaxBranchFixups = 8;
enum class StolenBytesError { enum class StolenBytesError {
None, None,
@@ -43,6 +48,12 @@ enum class StolenBytesError {
InsufficientLength, InsufficientLength,
}; };
struct BranchFixup {
size_t offset;
size_t dispOffset;
uint8_t dispSize;
};
StolenBytesError StolenBytesError
BuildStolenBytesX64( BuildStolenBytesX64(
const uint8_t* target, const uint8_t* target,
@@ -50,7 +61,9 @@ BuildStolenBytesX64(
uint8_t* outBytes, uint8_t* outBytes,
size_t& outLen, size_t& outLen,
size_t* outRipOffsets, size_t* outRipOffsets,
size_t& outRipCount size_t& outRipCount,
BranchFixup* outBranches,
size_t& outBranchCount
); );
#endif // defined(RADHOOK_ARCH_X64) #endif // defined(RADHOOK_ARCH_X64)

View File

@@ -40,8 +40,12 @@ 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";
case RadHookResult::UnknownTarget: return "UnknownTarget"; 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"; default: return "Unknown";
} }
} }
@@ -130,28 +134,41 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) {
if (!detour) return RadHookResult::InvalidDetour; if (!detour) return RadHookResult::InvalidDetour;
#if defined(RADHOOK_ARCH_X64) #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]; uint8_t stolen[kX64MaxStolen];
size_t stolenLen = 0; size_t stolenLen = 0;
size_t ripOffsets[kX64MaxRipFixups]; size_t ripOffsets[kX64MaxRipFixups];
size_t ripCount = 0; size_t ripCount = 0;
BranchFixup branches[kX64MaxBranchFixups];
size_t branchCount = 0;
StolenBytesError sberr = BuildStolenBytesX64( StolenBytesError sberr = BuildStolenBytesX64(
reinterpret_cast<const uint8_t*>(target), kX64StubLen, reinterpret_cast<const uint8_t*>(target), hookStubLen,
stolen, stolenLen, ripOffsets, ripCount); stolen, stolenLen, ripOffsets, ripCount, branches, branchCount);
if (sberr != StolenBytesError::None) { if (sberr != StolenBytesError::None) {
return (sberr == StolenBytesError::InsufficientLength) return static_cast<RadHookResult>(
? RadHookResult::TargetTooSmall static_cast<int>(RadHookResult::UnknownTarget) + static_cast<int>(sberr)
: RadHookResult::DisassemblyFailed; );
} }
// trampoline = stolen bytes + jump back to target
size_t allocSize = stolenLen + kX64StubLen; size_t allocSize = stolenLen + kX64StubLen;
void* trampolineAlloc = RadAllocExecNear(target, allocSize); void* trampolineAlloc = RadAllocExecNear(target, allocSize);
if (!trampolineAlloc) trampolineAlloc = RadAllocExec(allocSize); // best-effort fallback if (!trampolineAlloc) trampolineAlloc = RadAllocExec(allocSize);
if (!trampolineAlloc) return RadHookResult::MemoryAllocFailed; if (!trampolineAlloc) return RadHookResult::MemoryAllocFailed;
std::memcpy(trampolineAlloc, stolen, stolenLen); std::memcpy(trampolineAlloc, stolen, stolenLen);
// rewrite rip-relative operands
for (size_t k = 0; k < ripCount; ++k) { for (size_t k = 0; k < ripCount; ++k) {
size_t off = ripOffsets[k]; size_t off = ripOffsets[k];
int32_t originalDisp = 0; 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); 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; uint8_t* jumpBack = reinterpret_cast<uint8_t*>(trampolineAlloc) + stolenLen;
uintptr_t backAddr = reinterpret_cast<uintptr_t>(target) + 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[0] = 0xFF;
jumpBack[1] = 0x25; jumpBack[1] = 0x25;
jumpBack[2] = 0; jumpBack[3] = 0; jumpBack[2] = 0; jumpBack[3] = 0;
jumpBack[4] = 0; jumpBack[5] = 0; jumpBack[4] = 0; jumpBack[5] = 0;
std::memcpy(jumpBack + 6, &backAddr, 8);
} }
std::memcpy(jumpBack + 6, &backAddr, 8);
RadFlushICache(trampolineAlloc, allocSize); RadFlushICache(trampolineAlloc, allocSize);
// patch target in place with a jump to the detour
unsigned long oldProtect = 0; unsigned long oldProtect = 0;
if (!RadMakeWritableExecutable(target, stolenLen, &oldProtect)) { if (!RadMakeWritableExecutable(target, stolenLen, &oldProtect)) {
RadFreeExec(trampolineAlloc, allocSize); RadFreeExec(trampolineAlloc, allocSize);
@@ -188,19 +247,22 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) {
h->originalLength = stolenLen; h->originalLength = stolenLen;
uint8_t hookStub[kX64StubLen]; 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[0] = 0xFF;
hookStub[1] = 0x25; hookStub[1] = 0x25;
hookStub[2] = 0; hookStub[3] = 0; hookStub[2] = 0; hookStub[3] = 0;
hookStub[4] = 0; hookStub[5] = 0; hookStub[4] = 0; hookStub[5] = 0;
std::memcpy(hookStub + 6, &detourAddr, 8);
} }
uintptr_t detourAddr = reinterpret_cast<uintptr_t>(detour); std::memcpy(h->stubBytes, hookStub, hookStubLen);
std::memcpy(hookStub + 6, &detourAddr, 8); h->stubLength = hookStubLen;
std::memcpy(h->stubBytes, hookStub, kX64StubLen);
h->stubLength = kX64StubLen;
std::memcpy(target, hookStub, kX64StubLen); std::memcpy(target, hookStub, hookStubLen);
RadRestoreMemoryProtection(target, stolenLen, oldProtect); RadRestoreMemoryProtection(target, stolenLen, oldProtect);
RadFlushICache(target, stolenLen); RadFlushICache(target, stolenLen);
@@ -213,6 +275,8 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) {
return RadHookResult::Success; return RadHookResult::Success;
#elif defined(RADHOOK_ARCH_ARM64) #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); const uint32_t* src = reinterpret_cast<const uint32_t*>(target);
for (size_t k = 0; k < kArm64StubLen / 4; ++k) { for (size_t k = 0; k < kArm64StubLen / 4; ++k) {
if (IsArm64PcRelative(src[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; size_t allocSize = kArm64StubLen + kArm64StubLen;
void* trampolineAlloc = RadAllocExec(allocSize); void* trampolineAlloc = RadAllocExec(allocSize);
if (!trampolineAlloc) return RadHookResult::MemoryAllocFailed; if (!trampolineAlloc) return RadHookResult::MemoryAllocFailed;
@@ -230,6 +295,7 @@ RadHookResult InstallHook(RadHookOpaque* h, void* target, void* detour) {
RadFlushICache(trampolineAlloc, allocSize); RadFlushICache(trampolineAlloc, allocSize);
// patch target in place with an absolute branch to the detour
unsigned long oldProtect = 0; unsigned long oldProtect = 0;
if (!RadMakeWritableExecutable(target, kArm64StubLen, &oldProtect)) { if (!RadMakeWritableExecutable(target, kArm64StubLen, &oldProtect)) {
RadFreeExec(trampolineAlloc, allocSize); 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 == 0x6A) { immSize = 1; }
else if (opcode == 0x69) { hasModRM = true; immSize = opSize16 ? 2 : 4; } else if (opcode == 0x69) { hasModRM = true; immSize = opSize16 ? 2 : 4; }
else if (opcode == 0x6B) { hasModRM = true; immSize = 1; } 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 == 0x80) { hasModRM = true; immSize = 1; }
else if (opcode == 0x81) { hasModRM = true; immSize = opSize16 ? 2 : 4; } else if (opcode == 0x81) { hasModRM = true; immSize = opSize16 ? 2 : 4; }
else if (opcode == 0x83) { hasModRM = true; immSize = 1; } 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 == 0xCC) { /* int3 */ insn.isPadding = true; }
else if (opcode == 0xCD) { immSize = 1; } else if (opcode == 0xCD) { immSize = 1; }
else if (opcode == 0xD0 || opcode == 0xD1 || opcode == 0xD2 || opcode == 0xD3) { hasModRM = true; } else if (opcode == 0xD0 || opcode == 0xD1 || opcode == 0xD2 || opcode == 0xD3) { hasModRM = true; }
else if (opcode == 0xE8) { immSize = 4; relBranch = true; } else if (opcode == 0xE8) {
else if (opcode == 0xE9) { immSize = 4; relBranch = true; } immSize = 4; relBranch = true;
else if (opcode == 0xEB) { immSize = 1; 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 == 0xF6) { hasModRM = true; immSize = 1; }
else if (opcode == 0xF7) { hasModRM = true; immSize = opSize16 ? 2 : 4; } else if (opcode == 0xF7) { hasModRM = true; immSize = opSize16 ? 2 : 4; }
else if (opcode == 0xFE || opcode == 0xFF) { hasModRM = true; } 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 return insn; // unrecognized opcode
} }
} else { } 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 == 0x1E) { immSize = 1; }
else if (opcode == 0x1F) { hasModRM = true; } 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 == 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); i += static_cast<size_t>(immSize);
if (i > avail) return insn; 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, StolenBytesError BuildStolenBytesX64(const uint8_t* target, size_t minLen,
uint8_t* outBytes, size_t& outLen, 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; size_t len = 0;
outRipCount = 0; outRipCount = 0;
outBranchCount = 0;
while (len < minLen) { while (len < minLen) {
if (len >= kX64MaxStolen) return StolenBytesError::ExceedsMaxStolen; if (len >= kX64MaxStolen) return StolenBytesError::ExceedsMaxStolen;
X64Insn insn = DecodeX64(target + len, kX64MaxStolen - len); X64Insn insn = DecodeX64(target + len, kX64MaxStolen - len);
if (!insn.valid) return StolenBytesError::BadDecode; if (!insn.valid) return StolenBytesError::BadDecode;
if (insn.isReturn || insn.isPadding) return StolenBytesError::InsufficientLength; if (insn.isReturn || insn.isPadding) return StolenBytesError::InsufficientLength;
if (insn.relativeControlFlow) return StolenBytesError::RelativeControlFlow;
if (insn.ripRelative) { if (insn.ripRelative) {
if (outRipCount >= kX64MaxRipFixups) return StolenBytesError::TooManyRipFixups; if (outRipCount >= kX64MaxRipFixups) return StolenBytesError::TooManyRipFixups;
outRipOffsets[outRipCount++] = len + insn.dispOffset; 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; len += insn.length;
} }
std::memcpy(outBytes, target, len); std::memcpy(outBytes, target, len);