Ring3层下的Inline Hook是最常用的Hook手段之一,是一种通过修改机器码的方式来实现hook的技术。
我们知道正常函数的执行流程是call 0xxxxxxxx,然后跳转到调用的函数去执行。
0x1000地址的call指令执行后跳转到0x3000地址处执行,执行完毕后再返回执行call指令的下一条指令。
我们以MessageBox为例
正常调用如下:
首先是call MessageBox
可以看到MessageBox的地址为77101370
跳转到Message的程序开头。
我们在hook的时候,可能会读取或者修改call指令执行之前所压入栈的内容。
那么,我们可以将call指令替换成jmp指令,jmp到我们自己编写的函数,在函数里call原来的函数,函数结束后再jmp回到原先call指令的下一条指令。如图:
实现思路比较简单,主要思想:
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
ReadProcessMemory(INVALID_HANDLE_VALUE, __MessageBoxAddress, __OldCode, 7, NULL);
首先要修改内存属性为可读可写,然后再进行跳转指令的写入。
DWORD dwOldProtect = 0; //旧保护属性
// 去内存保护
::VirtualProtect(__MessageBoxAddress, 7, PAGE_EXECUTE_READWRITE, &dwOldProtect);
//写入跳转,开始Hook
WriteProcessMemory(INVALID_HANDLE_VALUE, __MessageBoxAddress, __NewCode, 7, NULL);
// 写内存保护
::VirtualProtect(__MessageBoxAddress, 7, dwOldProtect, &dwOldProtect);
进入MessageBoxA之后,会发现前7个字节已经改变为我们的跳板指令。
然后就会转入我们实现的函数,进行执行。
我们对原函数前七字节进行修改。
之后再对MessageBoxA()调用,就恢复正常了。
综上我们Inline Hook就已经实现了,现在我们来解决之前的问题:
1、如何定位自己要hook的地址?
调用API所在Dll,GetProAddress。
2、如何处理函数开头要替换的字节?
一般来说,是对前五个字节保存替换,但要视具体情况而定。
例如这里我们使用的就不是jmp xxxxxxxx,
而是mov eax xxxxxxxx;
jmp eax
这时候就需要保存替换前7个字节了。
具体参考上面实现
根据《加密与解密》中,一共有五种跳转方式。
看上面的实现流程。
源码已上传我的资源,无需积分,兄弟们需要自取。
#include
#include
#include
#include
//修改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;
}
运行结果:
参考资料: