Note: this is a short draft — I'll expand each section with a full walkthrough when I have time.
Trampoline hooking (also called inline hooking or detouring) is one of the most common ways to intercept a function at runtime. It is what lets a game trainer read a health value every frame, what an API monitor uses to log every call to a Windows API, and, on the defensive side, how many EDR products watch for suspicious behavior. Microsoft even ships a library for it: Detours.
The idea
A function lives at some address in memory and starts with a few machine instructions. The trick is to overwrite the first bytes of the target with a jump to your own function. But if you just overwrite them, the original is destroyed. So first you copy those original bytes somewhere else and append a jump back to the rest of the original function. That copy is the trampoline: it lets your hook still call the original behavior.
- Save the first N bytes of the target function (N must cover whole instructions).
- Build a trampoline: the saved bytes, followed by a jump to target + N.
- Overwrite the start of the target with a jump to your hook function.
- Your hook runs its own logic, then calls the trampoline to reach the original.
The jump that gets written
On x86 the simplest patch is a 5-byte relative near jump: the opcode 0xE9 followed by a 32-bit offset computed from the end of the jump to the destination.
// Write a 5-byte relative JMP at 'from' that lands on 'to'.
// E9 <rel32>, where rel32 = to - (from + 5)
void write_jmp(void *from, void *to) {
DWORD old;
VirtualProtect(from, 5, PAGE_EXECUTE_READWRITE, &old);
unsigned char *p = (unsigned char *)from;
p[0] = 0xE9;
*(int *)(p + 1) = (int)((char *)to - ((char *)from + 5));
VirtualProtect(from, 5, old, &old);
}Why the trampoline matters
Without the saved bytes you can intercept the call but you can no longer let it do its real work — fine for blocking something, useless for monitoring. The trampoline is what makes the hook transparent: the caller never knows it was redirected, because the original logic still runs through the copy.
The hard part: instruction length
x86 instructions are variable length, so you cannot just copy 5 bytes — you might cut an instruction in half. You have to copy whole instructions until you have at least 5 bytes, which means decoding them with a length-disassembler. This is why real implementations lean on a library such as Microsoft Detours or MinHook instead of hand-writing it.
Spotting it (defensive view)
- A function's prologue starting with an E9 jump that lands outside its own module is a classic sign of an inline hook.
- Executable memory pages that were recently made writable (RWX) around loaded module code.
- Comparing a module's in-memory code against a clean copy on disk reveals patched bytes.
I use this technique only against my own processes in a lab, both to understand how trainers and API monitors work and to see what the patched code looks like from a defender's side.