Hotpatching and Inline Function Hooking Explained

 Warning:  The post initially contains wrong information.  Continue to “Mistakes and Truths” for the correct information.

The Function Preamble

After Windows XP S2, Microsoft change the function calls preamble to allow hotpatching. This is the disassembly of MessageBoxW() and the preamble is like so:

The red border is the function preamble. Typical of all Windows APIs after XP S2.

mov edi, edi
push ebp
mov ebp, esp

Why this matters

The far jump instruction is 5 bytes. It just so happens that the move instruction is 2 bytes and the push instruction is 1 byte – all totaling 5 bytes. We can replace the preamble with a jump.

From this:

mov edi, edi
push ebp
mov ebp, esp
push -1
push 0

To this:

jmp 009C1249
push -1
push 0

The jump allows us to hook, intercept, the function call. Any variables push on the stack, we can play with to our hearts desire and we can also modify the function’s return.

Good Shit.  So How to Hook?

To inline hook the function in our target application we must:

0) Hooking code must already be running in the target application.

  • Unless the hook was already compiled in the application, we must inject our code. DLL injection is the most common way. I prefer to rebase the .reloc table of my injector. This will be discussed later in another post

The hooking:

1) Get the address of the function to hook.

HMODULE hModule = LoadLibrary(Module);
DWORD OriginalAddress = (DWORD)GetProcAddress(hModule, API);

2) Get the address of the replacement function. Just cast your function to a DWORD.

DWORD ReplacementAddress = (DWORD)Function;

3) Get the replace address offset by calculating the replacement address minus the original address minus the preamble size.

DWORD ReplacementAddressOffset = ReplacementAddress - OriginalAddress - 5;

4) Use the char* hack. In case you didn’t know, LPBYTE = BYTE*, and a BYTE = unsigned char. We cast the address to LPBYTE so we can modify the values of each address.

LPBYTE pOriginalAddress = (LPBYTE)OriginalAddress;
LPBYTE pReplacementAddressOffset = (LPBYTE)(&ReplacementAddressOffset);

5) Use VirtualProtect() with the PAGE_EXECUTE_READWRITE flag to write to the preamble location in memory.

DWORD OldProtect = 0;
DWORD NewProtect = PAGE_EXECUTE_READWRITE;

VirtualProtect((PVOID)OriginalAddress, 5, NewProtect, &OldProtect);

6) Copy the preamble in an array for unhooking later.

for (int i = 0; i < 5; i++)
	Store[i] = pOriginalAddress[i];

7) Replace the preamble with a jump and the replacement function address. 0xE9 = JMP.

pOriginalAddress[0] = (BYTE)0xE9;

for (int i = 0; i < 4; i++)
	pOriginalAddress[i + 1] = pReplacementAddressOffset[i];

8) Set VirtualProtect back to its original value.

VirtualProtect((PVOID)OriginalAddress, 5, OldProtect, &NewProtect);

9) Flush the instruction cache for safe measures in case the CPU cached old instructions.

FlushInstructionCache(GetCurrentProcess(), NULL, NULL);

Copy and Paste Ready? Of course!

BYTE Store[6] = "";

void HookAPI(wchar_t *Module, char *API, DWORD Function)
{
	HMODULE hModule = LoadLibrary(Module);
	DWORD OriginalAddress = (DWORD)GetProcAddress(hModule, API);
	DWORD ReplacementAddress = (DWORD)Function;
	DWORD ReplacementAddressOffset = ReplacementAddress - OriginalAddress - 5;
	LPBYTE pOriginalAddress = (LPBYTE)OriginalAddress;
	LPBYTE pReplacementAddressOffset = (LPBYTE)(&ReplacementAddressOffset);

	DWORD OldProtect = 0;
	DWORD NewProtect = PAGE_EXECUTE_READWRITE;

	VirtualProtect((PVOID)OriginalAddress, 5, NewProtect, &OldProtect);

	for (int i = 0; i < 5; i++)
		Store[i] = pOriginalAddress[i];

	pOriginalAddress[0] = (BYTE)0xE9;

	for (int i = 0; i < 4; i++)
		pOriginalAddress[i + 1] = pReplacementAddressOffset[i];

	VirtualProtect((PVOID)OriginalAddress, 5, OldProtect, &NewProtect);

	FlushInstructionCache(GetCurrentProcess(), NULL, NULL);

	FreeLibrary(hModule);
}

void UnHookAPI(wchar_t *Module, char *API)
{
	HMODULE hModule = LoadLibrary(Module);
	DWORD OriginalAddress = (DWORD)GetProcAddress(hModule, API);

	LPBYTE pOriginalAddress = (LPBYTE)OriginalAddress;

	DWORD OldProtect = 0;
	DWORD NewProtect = PAGE_EXECUTE_READWRITE;

	VirtualProtect((PVOID)pOriginalAddress, 5, NewProtect, &OldProtect);

	for (int i = 0; i < 5; i++)
		pOriginalAddress[i] = Store[i];

	VirtualProtect((PVOID)OriginalAddress, 5, OldProtect, &NewProtect);

	FlushInstructionCache(GetCurrentProcess(), NULL, NULL);

	FreeLibrary(hModule);
}

I went full retard. How do I use?

First make sure the code is injected and running in its own thread.

Here is a hooking example:


int HookedMessageBoxW(HWND hWnd, LPCWSTR lpText, LPCWSTR lpCaption, UINT uType)

{

	UnHookAPI(L"user32.dll", "MessageBoxW");

	int ret = MessageBoxW(hWnd, L"Hooked", lpCaption, uType);

	HookAPI(L"user32.dll", "MessageBoxW", (DWORD)HookedMessageBoxW);

	return ret;

}

DWORD WINAPI ThreadProc(LPVOID param)
{
	HookAPI(L"user32.dll", "MessageBoxW", (DWORD)HookedMessageBoxW);
	return 0;
}

Mistakes and Truths

Okay, I’ll be honest. The proper way to hook as Microsoft intended for hotpatching, is to replace the

‘mov edi, edi’ operation with a 2 byte short jump. You see the 5 no operations? Right above the preamble.

To properly hook. You replace the ‘mov edi, edi’ instruction with a 2 byte short jump. The jump redirects to the beginning of the no operation hotpatch and then we replace the 5 no operations with a far jump to our code.

First we replace our ‘mov edi, edi’ instruction with a short jump.

nop
nop
nop
nop
nop
jmp 7760CC97
push ebp
mov ebp, esp
push -1
push 0

Then we replace our no operations with a far jump.

jmp 02BE1244
jmp short 7760CC97
push ebp
mov ebp, esp
push -1
push 0

What it looks like in OllyDbg.  See how ‘push ebp’ and ‘mov ebp, esp’ remain untouched.

Why? It prevents a race condition that could happen if we hooked the function while the preamble is executing. In other words, if the CPU just finished executing ‘push ebp’ in the preamble and we just so happen to be at the perfect nanosecond while replacing ‘mov ebp, esp’, the hooked could cause a crash. This is rare, but might happen. But if we only replace ‘mov edi, edi’ instead of the whole preamble, we have no race condition since it is only one execution rather than three.

Below is the correct code, copy and paste ready, that replaces ‘mov edi, edi’ with a short jump and the no operations with a far jump.


void WINAPI_Hook(wchar_t *Module, char *API, DWORD Function)
{
 HMODULE hModule = LoadLibrary(Module);

 DWORD OriginalAddress = (DWORD)GetProcAddress(hModule, API);
 DWORD ReplacementAddress = (DWORD)Function;
 DWORD ReplacementAddressOffset = ReplacementAddress - OriginalAddress;
 DWORD NOPAddress = OriginalAddress - 5;
 DWORD NOPAddressOffset = -7; //includes 2 more bytes due to 'mov edi, edi'

 LPBYTE pOriginalAddress = (LPBYTE)OriginalAddress;
 LPBYTE pReplacementAddressOffset = (LPBYTE)(&ReplacementAddressOffset);
 LPBYTE pNOPAddress = (LPBYTE)NOPAddress;
 LPBYTE pNOPAddressOffset = (LPBYTE)(&NOPAddressOffset);

 DWORD OldProtect = 0;
 DWORD NewProtect = PAGE_EXECUTE_READWRITE;

 VirtualProtect((PVOID)NOPAddress, 5, NewProtect, &OldProtect);

 pNOPAddress[0] = (BYTE)0xE9; //far jmp
 for (int i = 0; i < 4; i++)
    pNOPAddress[i + 1] = pReplacementAddressOffset[i];

 VirtualProtect((PVOID)NOPAddress, 5, OldProtect, &NewProtect);

 VirtualProtect((PVOID)OriginalAddress, 2, NewProtect, &OldProtect);

 pOriginalAddress[0] = (BYTE)0xEB; //short jmp
 for (int i = 0; i < 1; i++)
    pOriginalAddress[i + 1] = pNOPAddressOffset[i];

 VirtualProtect((PVOID)OriginalAddress, 2, OldProtect, &NewProtect);

 FlushInstructionCache(GetCurrentProcess(), NULL, NULL);

 FreeLibrary(hModule);
}

void WINAPI_UnHook(wchar_t *Module, char *API)
{
 HMODULE hModule = LoadLibrary(Module);

 DWORD OriginalAddress = (DWORD)GetProcAddress(hModule, API);
 DWORD NOPAddress = OriginalAddress - 5;

 LPBYTE pOriginalAddress = (LPBYTE)OriginalAddress;
 LPBYTE pNOPAddress = (LPBYTE)NOPAddress;

 DWORD OldProtect = 0;
 DWORD NewProtect = PAGE_EXECUTE_READWRITE;

 VirtualProtect((PVOID)NOPAddress, 5, NewProtect, &OldProtect);

 for (int i = 0; i < 5; i++)
    pNOPAddress[i] = 0x90; //nop

 VirtualProtect((PVOID)NOPAddress, 5, OldProtect, &NewProtect);

 VirtualProtect((PVOID)pOriginalAddress, 2, NewProtect, &OldProtect);

 pOriginalAddress[0] = 0x8B; //mov
 pOriginalAddress[1] = 0xFF; //edi, edi

 VirtualProtect((PVOID)OriginalAddress, 2, OldProtect, &NewProtect);

 FlushInstructionCache(GetCurrentProcess(), NULL, NULL);

 FreeLibrary(hModule);
}

VirtualProtect() can be commented out in the UnHook method if you plan on re-hooking. Waste of cycles in nop-ing the short jump.

	VirtualProtect((PVOID)NOPAddress, 5, NewProtect, &OldProtect);

	for (int i = 0; i < 5; i++)
		pNOPAddress[i] = 0x90;

	VirtualProtect((PVOID)NOPAddress, 5, OldProtect, &NewProtect);

Final thoughts

Peter F. mentions in the comments, “you should be overwriting the nops before you overwrite the mov, otherwise you introduce the race-condition that the jump starts executing while you’re still replacing the nops.”  He’s right.  (Code aboved has been fixed.  Thanks!)

Not related at all.

This entry was posted in Blog. Bookmark the permalink.

3 Responses to Hotpatching and Inline Function Hooking Explained

  1. Peter Ferrie says:

    not all hot-patch-enabled functions have stack frames.
    also, you should be overwriting the nops before you overwrite the mov, otherwise you introduce the race-condition that the jump starts executing while you’re still replacing the nops.

  2. Peter Ferrie says:

    the other reason why it’s a two-bytes instruction is because you can write two bytes in one pass (and your code should do it, too, not one byte at a time).
    of course, using MMX or CMPXCHG8B, for example, you can write more than that at a time, but there isn’t a single instruction that writes only five bytes (and there are plenty of functions where you wouldn’t want to overwrite all eight bytes)

Leave a Reply

Your email address will not be published.


× 5 = thirty five

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Powered by Ajaxy

Recent Posts