Function Detour
Detour a function at its entry to a hook function, while keeping the original function callable.
This hooks every caller at once, so it suits targets with too many callsites to relocate one by one with write_call.
Syntax
DetourHookHandle Hook::AddDetourHook<N>(address, function);Parameter
N: length of the stolen prologue, at least 5 bytes, and it must end on an instruction boundary. e.g.mov [rsp+8], rbxis48 89 5C 24 08, which is 5 bytes.push rbx+sub rsp, 20his40 53+48 83 EC 20, which is 6 bytes.address: address of the first instruction of the target function.function: hook function, it should have the same signature as the target function.
Stolen Prologue
The first N bytes are copied verbatim into the trampoline, so they must not contain any rip-relative operand (mov rax, [rip+disp], lea rcx, [rip+disp], etc.) or relative branch (call, jmp, jcc). DKUtil does not disassemble the prologue: pick N from the disassembly, the debug log prints the stolen bytes.
As a sanity check, a head that already starts with a branch (e.g. hooked by another plugin, or an incremental linking thunk), a ret or an int3 is refused.
HookHandle
A DetourHookHandle object will be returned:
class DetourHookHandle
{
const std::size_t StolenSize;
const Imm64 OriginalFunc;
Imm64 Destination;
std::vector<OpCode> OldBytes{};
std::vector<OpCode> Detour{};
};DetourHookHandle can be implicitly converted to address of original function. Like other hook APIs, the hook is disabled until Enable() is called, Disable() restores the stolen prologue. The original function stays callable either way.
Workflow
; target function, N bytes
jmp relay ; E9 rel32, padded with NOP
; trampoline, N + 19 bytes
dq function
relay: ; TramEntry
jmp qword ptr [rip - 0xE] ; -> function
original: ; OriginalFunc
<N stolen bytes>
jmp target + N ; E9 rel32Each detour consumes N + 19 bytes of trampoline.
Wrapper
For the ease of use, you can use Hook::write_detour<N> for convenience. This wrapper will enable itself and return the original function address, same as write_call.
Example
Given target function:
0x140345670: mov [rsp+8], rbx ; 48 89 5C 24 08
0x140345675: push rdi ; 57
0x140345676: sub rsp, 20h ; 48 83 EC 20We want every call to Game.exe+0x345670 to go through Hook_345670:
class Hook
{
// hook function
static void* Hook_345670(void* a_this, void* a_arg)
{
// do something
return func(a_this, a_arg);
}
// original function
static inline std::add_pointer_t<decltype(Hook_345670)> func;
public:
static void Install()
{
auto addr = dku::Hook::Module::get().base() + 0x345670;
// steal mov [rsp+8], rbx, save original function
func = dku::Hook::write_detour<5>(addr, Hook_345670);
}
};// forward
void* Hook_345670(void* a_this, void* a_arg);
// original function
std::add_pointer_t<decltype(Hook_345670)> func;
auto addr = dku::Hook::Module::get().base() + 0x345670;
// steal mov [rsp+8], rbx + push rdi
auto handle = dku::Hook::AddDetourHook<6>(addr, AsAddress(&Hook_345670));
// save original function
func = *handle;
handle->Enable();
// toggle at will
handle->Disable();Patching
The function head is patched with a plain memory write, install the detour when no other thread can be executing the target function, e.g. during plugin load.