When I first learned about hooking, I started with mid-function hooks. Writing them while reverse engineering the target helped me understand how execution gets redirected, rather than just relying on Detours to hook API calls. Before following along, you should have a working knowledge of C++ and assembly.

When are mid-function hooks useful?

I tend to use them when anti-cheat software scans certain sections of a function but leaves others unchecked, giving me a place to insert a hook. They're also useful when a function's parameter types are unclear, when I need direct access to values held in specific registers, or when I need my code to run at a particular point during the game's execution.

Choosing the hook location

Let's assume you're already familiar with reverse engineering and have identified the function you want to hook. In this example, I'll be placing the hook at 0x00640BF1. Here's what that section looks like in OllyDbg:



I'm particularly interested in the contents of EDX immediately after the first three instructions have executed.

The idea is to replace a few instructions with a jump to our own function. Our hook will execute the instructions we replaced, run our custom code, and then jump back to the original function at the next untouched instruction.

The helper below handles the patching and calculates the relative jump offset for us:

1
2
3
4
5
6
7
8
9
10
11
12
13
void placeJMP(BYTE * address, DWORD jumpTo, DWORD length)
{
DWORD oldProtect, newProtect, relativeAddress;
VirtualProtect(address, length, PAGE_EXECUTE_READWRITE, &oldProtect);
relativeAddress = (DWORD) (jumpTo - (DWORD) address) - 5;
*address = 0xE9;
*((DWORD *)(address + 0x1)) = relativeAddress;
for(DWORD x = 0x5; x < length; x++)
{
*(address + x) = 0x90;
}
VirtualProtect(address, length, oldProtect, &newProtect);
}


Before installing the hook, we need to decide exactly which instructions to overwrite. Keep track of those instructions, because we'll need to reproduce their behavior inside our hook. Always overwrite complete instructions—never split one in the middle—or execution may resume with invalid instructions or an incorrect program state.

The jump written by this helper takes 5 bytes, so we need to overwrite at least 5 bytes' worth of complete instructions. In this example, I'll overwrite the following three instructions, which occupy 10 bytes in total:



1
2
3
00640BF1 8B41 18 MOV EAX,DWORD PTR DS:[ECX+0x18] // 3 bytes
00640BF4 D940 28 FLD DWORD PTR DS:[EAX+0x28] // 3 bytes
00640BF7 8B5424 04 MOV EDX,DWORD PTR SS:[ESP+0x4] // 4 bytes

Execution will jump from 0x00640BF1 to our hook. When we're finished, we'll jump back to 0x00640BFB, which is the address of the first instruction after the 10 bytes we replaced.

Here's the hook function:





1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
DWORD myData = 0; // Stores the register value captured in this example.

DWORD retJMP = 0x00640BFB;

__declspec(naked) void myMidfuncHook() // No compiler-generated prologue or epilogue; we manage execution ourselves.
{
__asm mov eax,dword ptr ds:[ecx+0x18] // Reproduce the instructions overwritten by our jump.
__asm fld dword ptr ds:[eax+0x28]
__asm mov edx,dword ptr ss:[esp+0x4]

// The original instructions have now run. As an example, save EBX for later use.
__asm mov myData, ebx

/*// Before making other calls or modifying registers, save the general-purpose registers and flags.
// For example:
__asm pushad // Save the general-purpose registers.
__asm pushfd // Save the flags.

// Make your calls or perform your calculations here.
// Naked functions have restrictions, so check the documentation before adding code.
// See: http://msdn.microsoft.com/en-us/libr...=vs.80%29.aspx

// Restore the saved flags and registers before resuming the original function.
__asm popfd
__asm popad
*/

// Jump back to the first instruction after the overwritten region.
__asm jmp [retJMP]
}


From your injected DLL, install the hook like this:

1
2
placeJMP((BYTE*)0x00640BF1, (DWORD)myMidfuncHook, 10); // Overwrite 10 bytes.




With everything set up correctly, execution should pass through your hook and then continue through the original function. The most common mistakes are overwriting part of an instruction, failing to reproduce the instructions you replaced, returning to the wrong address, or leaving the stack, registers, or flags in an unexpected state.

That covers the basic idea behind mid-function hooking. Have fun experimenting!