Hook技术:Ring3层下的Inline Hook详解【附源码】

Ring3层下的Inline Hook是最常用的Hook手段之一,是一种通过修改机器码的方式来实现hook的技术。


探究API调用正常执行流程

我们知道正常函数的执行流程是call 0xxxxxxxx,然后跳转到调用的函数去执行。

0x1000地址的call指令执行后跳转到0x3000地址处执行,执行完毕后再返回执行call指令的下一条指令。

我们以MessageBox为例

正常调用如下:

函数执行流程: 

首先是call MessageBox

可以看到MessageBox的地址为77101370

跳转到Message的程序开头。

我们在hook的时候,可能会读取或者修改call指令执行之前所压入栈的内容。

那么,我们可以将call指令替换成jmp指令,jmp到我们自己编写的函数,在函数里call原来的函数,函数结束后再jmp回到原先call指令的下一条指令。如图:

探究Inline Hook中的函数调用过程

实现思路比较简单,主要思想:

1、构造跳转指令。

2、在内存中找到欲HOOK函数地址,并保存欲HOOK位置处的前5个字节。(但其实并不一定就是5个字节,像是MessageBox就是6个字节)

3、将构造的跳转指令写入需HOOK的位置处。

4、当被HOOK位置被执行时会转到我们的流程执行。

5、如果要执行原来的流程,那么恢复HOOK,也就是还原被修改的字节。

6、执行函数原来的流程。



在这过程中,主要涉及到几个问题:

1、如何定位自己要hook的地址?

2、如何处理函数开头要替换的字节?

3、如何跳转以及跳转地址如何计算?

4、自己的函数执行完成后,如何跳转回来?


那么带着这几个问题,我们开始尝试实现Inline hook。

我们还是以MessageBox为例子,来探究一下上面的问题。

第一步:构造跳转指令


这里我们使用近距离地址跳转的jmp指令,它的机器码为E9,这种类型的jmp指令需要一个地址参数,为当前jmp指令地址距离目标函数地址的字节数。

一、正常的跳转计算公式如下所示”:

计算公式:jmp跳转的地址=自己实现的函数地址-(jmp所在的地址+5) (这里的5是 jmp xxxxxxxx的五个字节。)

也就是:

*((ULONG*)(__HookCode + 1)) = (ULONG)FakeFuncAddress - ((ULONG)OriginalFuncAddress + 5);

、mov eax xxxxxxxx;

jmp eax

但是,在调用MessageBox的时候,函数开头的指令是6个字节,jmp xxxxxxxx只修改五个字节会出现问题。

所以在这里我们使用了mov eax,地址;jmp eax的方式。一共是七个字节。

// 构造新头部代码   
__NewCode[0] = 0xB8;            
memcpy(&__NewCode[1], &JmpAddress, 4);    // mov eax, _JmpAddr    
__NewCode[5] = 0xFF;            //    
__NewCode [6] = 0xE0;           // jmp eax

第二步:找到API函数地址,并保存前7字节

ReadProcessMemory(INVALID_HANDLE_VALUE, __MessageBoxAddress, __OldCode, 7, NULL);

第三步:将构造的跳转指令写入需HOOK的位置处

首先要修改内存属性为可读可写,然后再进行跳转指令的写入。


DWORD dwOldProtect = 0; //旧保护属性
	// 去内存保护
	::VirtualProtect(__MessageBoxAddress, 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);	//写入跳转,开始Hook
	WriteProcessMemory(INVALID_HANDLE_VALUE, __MessageBoxAddress, __NewCode, 7, NULL);	// 写内存保护
	::VirtualProtect(__MessageBoxAddress, 7, dwOldProtect, &dwOldProtect);

4、当被HOOK位置被执行时会转到我们的流程执行

进入MessageBoxA之后,会发现前7个字节已经改变为我们的跳板指令。

然后就会转入我们实现的函数,进行执行。

5、如果要执行原来的流程,那么恢复HOOK,也就是还原被修改的字节

我们对原函数前七字节进行修改。

6、执行函数原来的流程

之后再对MessageBoxA()调用,就恢复正常了。

综上我们Inline Hook就已经实现了,现在我们来解决之前的问题:

1、如何定位自己要hook的地址?

调用API所在Dll,GetProAddress。

2、如何处理函数开头要替换的字节?

一般来说,是对前五个字节保存替换,但要视具体情况而定。

例如这里我们使用的就不是jmp xxxxxxxx,

而是mov eax xxxxxxxx;

       jmp eax

这时候就需要保存替换前7个字节了。

3、如何跳转以及跳转地址如何计算?

具体参考上面实现

4、自己的函数执行完成后,如何跳转回来?

看上面的实现流程。

源码:

源码已上传我的资源,无需积分,兄弟们需要自取。

#include <windows.h>
#include <stdio.h>
#include <iostream>
#include <tchar.h>

//修改API入口为 mov eax, 00400000;jmp eax是程序能跳转到自己的函数
BYTE __NewCode[7] = { 0xE9, 0x0, 0x0, 0x0, 0x0, 0x0 };
BYTE __OldCode[7] = { 0 };

FARPROC __MessageBoxAddress;

int WINAPI MyMessageBoxA(
	HWND hWnd,          // handle to owner window
	LPCTSTR lpText,     // text in message box
	LPCTSTR lpCaption,  // message box title
	UINT uType          // message box style
);

void InlineHook();




void main()
{
	
	InlineHook();

	//调用MessageBoxA测试一下。
	MessageBoxA(NULL, "Hello World", "Title", MB_OK);
}

void InlineHook()
{
	HMODULE hModule_User32 = LoadLibrary("user32.dll");
	__MessageBoxAddress = GetProcAddress(hModule_User32, "MessageBoxA");

	//打印MessageBoxA的地址
	printf("__MessageBoxAddress is %x\n", __MessageBoxAddress);
	//MyMessageBoxA的地址
	printf("MyMessageBoxA Addr is %x\n", MyMessageBoxA);

	//读MessageBoxA函数的前6个字节
	if (ReadProcessMemory(INVALID_HANDLE_VALUE, __MessageBoxAddress, __OldCode, 7, NULL) == 0)
	{
		printf("ReadProcessMemory error\n");
		return;
	}
	//MessAgeBoxA()API 原6字节
	printf("__OldCode is %x%x%x%x%x%x%x\n", __OldCode[0], __OldCode[1], __OldCode[2],
		__OldCode[3], __OldCode[4], __OldCode[5], __OldCode[6]);

	DWORD JmpAddress = (DWORD)MyMessageBoxA;
	// 计算自定义函数的地址.
	// 构造新头部代码
	__NewCode[0] = 0xB8;            //
	memcpy(&__NewCode[1], &JmpAddress, 4);    // mov eax, _JmpAddr
	__NewCode[5] = 0xFF;            //
	__NewCode [6] = 0xE0;            // jmp eax
	DWORD dwOldProtect = 0;
	printf("NewBytes is %x%x%x%x%x\n", __NewCode[0], __NewCode[1], __NewCode[2], __NewCode[3],
		__NewCode[4], __NewCode[5], __NewCode[6]);

	//DWORD dwOldProtect = 0; //旧保护属性
	// 去内存保护
	::VirtualProtect(__MessageBoxAddress, 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);
	//写入跳转,开始Hook
	WriteProcessMemory(INVALID_HANDLE_VALUE, __MessageBoxAddress, __NewCode, 7, NULL);
	// 写内存保护
	::VirtualProtect(__MessageBoxAddress, 7, dwOldProtect, &dwOldProtect);
}

int WINAPI MyMessageBoxA(
	HWND hWnd,          // handle to owner window
	LPCTSTR lpText,     // text in message box
	LPCTSTR lpCaption,  // message box title
	UINT uType          // message box style
)
{
	
	printf("MessageBoxA 已经被Hook\r\n");
	//恢复API头7个字节
	WriteProcessMemory(INVALID_HANDLE_VALUE, (void*)__MessageBoxAddress,
		(void*)__OldCode, 7, NULL);



	//调用正确的函数
	int ret = MessageBoxA(NULL, "Hello World", "Title", MB_OK);


	//写入跳转语句,继续Hook
	WriteProcessMemory(INVALID_HANDLE_VALUE, (void*)__MessageBoxAddress,
		(void*)__NewCode, 7, NULL);

	return ret;
}


运行结果:

参考资料:

《加密与解密(第四版)》——第十三章



小弟在学习Inline hook过程中,遇到了很多问题,作为初学者虽然解决了不少,但一定还有遗漏或不对的地方,请大佬们指点和讨论。

文件好像传不上,附上下载网址:https://github.com/Powerful99/Hook




评论