From 5841f58f37a824864bc57506283f0cf6b6c64394 Mon Sep 17 00:00:00 2001 From: Ratin Gao Date: Wed, 27 May 2026 00:03:04 +0800 Subject: [PATCH] Recognize REX-prefixed indirect JMP thunks Decode x64 import-style FF /4 indirect JMP thunks with an optional single-byte REX prefix before the opcode, so valid forms such as 48 FF 25 disp32 and other 40..4F FF 25 disp32 encodings are handled consistently. Reuse the shared decoder in detour_skip_jmp, detour_find_jmp_bounds, and detour_does_code_end_function so thunk skipping, trampoline bounds, and function-end detection agree. Leave the x86 path unchanged. Keep the HPAT OS-patch probe unchanged: it still matches the exact unprefixed FF 25 disp32 stub generated by the OS, instead of broadening that special-case path to REX-prefixed forms. --- src/detours.cpp | 38 +++++++++++++++++++++++++++----------- 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/src/detours.cpp b/src/detours.cpp index 91d840d1..1b9b706a 100644 --- a/src/detours.cpp +++ b/src/detours.cpp @@ -379,6 +379,22 @@ inline PBYTE detour_gen_jmp_indirect(PBYTE pbCode, PBYTE *ppbJmpVal) return pbCode; } +inline BOOL detour_decode_jmp_indirect(PBYTE pbCode, PBYTE *ppbTarget) +{ + ULONG cbPrefix = 0; + + if ((pbCode[0] & 0xf0) == 0x40) { + cbPrefix = 1; + } + + if (pbCode[cbPrefix] != 0xff || pbCode[cbPrefix + 1] != 0x25) { + return FALSE; + } + + *ppbTarget = pbCode + cbPrefix + 6 + *(UNALIGNED INT32 *)&pbCode[cbPrefix + 2]; + return TRUE; +} + inline PBYTE detour_gen_brk(PBYTE pbCode, PBYTE pbLimit) { while (pbCode < pbLimit) { @@ -390,6 +406,7 @@ inline PBYTE detour_gen_brk(PBYTE pbCode, PBYTE pbLimit) inline PBYTE detour_skip_jmp(PBYTE pbCode, PVOID *ppGlobals) { PBYTE pbCodeOriginal; + PBYTE pbTarget; if (pbCode == NULL) { return NULL; @@ -399,9 +416,8 @@ inline PBYTE detour_skip_jmp(PBYTE pbCode, PVOID *ppGlobals) } // First, skip over the import vector if there is one. - if (pbCode[0] == 0xff && pbCode[1] == 0x25) { // jmp [+imm32] + if (detour_decode_jmp_indirect(pbCode, &pbTarget)) { // jmp [+imm32] // Looks like an import alias jump, then get the code it points to. - PBYTE pbTarget = pbCode + 6 + *(UNALIGNED INT32 *)&pbCode[2]; if (detour_is_imported(pbCode, pbTarget)) { PBYTE pbNew = *(UNALIGNED PBYTE *)pbTarget; DETOUR_TRACE(("%p->%p: skipped over import table.\n", pbCode, pbNew)); @@ -417,9 +433,8 @@ inline PBYTE detour_skip_jmp(PBYTE pbCode, PVOID *ppGlobals) pbCodeOriginal = pbCode; // First, skip over the import vector if there is one. - if (pbCode[0] == 0xff && pbCode[1] == 0x25) { // jmp [+imm32] + if (detour_decode_jmp_indirect(pbCode, &pbTarget)) { // jmp [+imm32] // Looks like an import alias jump, then get the code it points to. - PBYTE pbTarget = pbCode + 6 + *(UNALIGNED INT32 *)&pbCode[2]; if (detour_is_imported(pbCode, pbTarget)) { pbNew = *(UNALIGNED PBYTE *)pbTarget; DETOUR_TRACE(("%p->%p: skipped over import table.\n", pbCode, pbNew)); @@ -458,17 +473,16 @@ inline void detour_find_jmp_bounds(PBYTE pbCode, // We have to place trampolines within +/- 2GB of code. ULONG_PTR lo = detour_2gb_below((ULONG_PTR)pbCode); ULONG_PTR hi = detour_2gb_above((ULONG_PTR)pbCode); + PBYTE pbJmpTarget; DETOUR_TRACE(("[%p..%p..%p]\n", (PVOID)lo, pbCode, (PVOID)hi)); // And, within +/- 2GB of relative jmp vectors. - if (pbCode[0] == 0xff && pbCode[1] == 0x25) { // jmp [+imm32] - PBYTE pbNew = pbCode + 6 + *(UNALIGNED INT32 *)&pbCode[2]; - - if (pbNew < pbCode) { - hi = detour_2gb_above((ULONG_PTR)pbNew); + if (detour_decode_jmp_indirect(pbCode, &pbJmpTarget)) { // jmp [+imm32] + if (pbJmpTarget < pbCode) { + hi = detour_2gb_above((ULONG_PTR)pbJmpTarget); } else { - lo = detour_2gb_below((ULONG_PTR)pbNew); + lo = detour_2gb_below((ULONG_PTR)pbJmpTarget); } DETOUR_TRACE(("[%p..%p..%p] [+imm32]\n", (PVOID)lo, pbCode, (PVOID)hi)); } @@ -491,6 +505,8 @@ inline void detour_find_jmp_bounds(PBYTE pbCode, inline BOOL detour_does_code_end_function(PBYTE pbCode) { + PBYTE pbTarget; + if (pbCode[0] == 0xeb || // jmp +imm8 pbCode[0] == 0xe9 || // jmp +imm32 pbCode[0] == 0xe0 || // jmp eax @@ -502,7 +518,7 @@ inline BOOL detour_does_code_end_function(PBYTE pbCode) else if (pbCode[0] == 0xf3 && pbCode[1] == 0xc3) { // rep ret return TRUE; } - else if (pbCode[0] == 0xff && pbCode[1] == 0x25) { // jmp [+imm32] + else if (detour_decode_jmp_indirect(pbCode, &pbTarget)) { // jmp [+imm32] return TRUE; } else if ((pbCode[0] == 0x26 || // jmp es: