最近在研究如何hook自己的函数。现在我们来讲一下hook函数的过程,首先要hook自己函数要找到自己函数的地址,之后找到不少于5个字节的硬编码地址替换成E9(JMP)跳转到我们制定的函数地址。我们制定的函数设置成裸函数,我们要保持进入我们定制函数前和后的寄存器和标志寄存器值不变和堆栈的平衡。同时我们要在函数里实现我们替换的代码。这样才能保持程序的正常执行。下面不多说了上代码讲解。
int CInjection::pushNum(int a, int b)
{
return a + b;
}//这个是我们要劫持的函数
我们实现一个bool CInjection::SetInlineHook(DWORD dwHookAddr, DWORD dwProcAddr, int dwLength)函数作为设置hook的,参数dwHookAddr是要替换代码的地址。dwProcAddr我们实现的HOOK函数地址。dwLength替换的字节长度。这个需要查看查看pushNum汇编硬编码字节数确定。下面看看这个函数的实现过程
bool CInjection::SetInlineHook(DWORD dwHookAddr, DWORD dwProcAddr, int dwLength)
{
BOOL bRet = false;
DWORD dwOldProtect;
DWORD dwJmpCode;
if (dwHookAddr == NULL || dwProcAddr == NULL)
return false;
if (dwLength < 5)//最少需要5个字节
return false;
MEMORY_BASIC_INFORMATION mbi;
VirtualQueryEx(GetCurrentProcess(), (LPVOID)dwHookAddr, &mbi, sizeof(MEMORY_BASIC_INFORMATION));//查询当前内存块的保护属性
bRet = VirtualProtectEx(GetCurrentProcess(), (LPVOID)dwHookAddr, dwLength, PAGE_EXECUTE_READWRITE, &mbi.Protect);//修改当前内存块保护属性为可读可写
if (!bRet)
{
return false;
}
//创建内存,储存硬编码
g_pCOdePatch = new BYTE[dwLength];
memcpy(g_pCOdePatch, (PDWORD)dwHookAddr, dwLength);
//跳转地址 = E9地址 + 5 +E9后面地址
dwJmpCode = dwProcAddr - dwHookAddr - 5;
//将地址初始化为Nop 硬编码0x90
memset((PBYTE)dwHookAddr, 0x90, dwLength);
//修改Hookddr内存硬编码
*(PBYTE)dwHookAddr = 0xE9;
*(PDWORD)((PBYTE)dwHookAddr + 1) = dwJmpCode;
g_dwHookAddr = dwHookAddr;
g_dwLength = dwLength;
g_dwRetAddr = dwHookAddr + dwLength;//保存执行完后返回地址
return true;
}
下面我看如何调用这个函数,因为pushNum这个代码地址段是变化的,我们没法获取到他代码硬编码地址。所以我们需要通过一些公式获取代码硬编码地址。
bool CInjection::ModefyInlineHook()
{
DWORD Funaddr = (DWORD)pushNum;//获取函数地址
PDWORD Funaddr2 = (PDWORD)((PBYTE)Funaddr + 1);//获取当前硬编码E9后地址
DWORD Funaddr3 = Funaddr + *Funaddr2 + 5;//获取运行硬编码地址
SetInlineHook(Funaddr3, (DWORD)HookProc, 9);//为什么是9根据pushNum汇编码看
/* 00E5FD80 55 push ebp
00E5FD81 8B EC mov ebp, esp
00E5FD83 81 EC C0 00 00 00 sub esp, 0C0h*/
//因为我们需要5个字节,但是前2行为3个字节不够,在加上一行就为9个字节
pushNum(1, 2);return true;}
下面介绍HookProc函数的实现,首先前面介绍过,这个函数是裸函数。什么是裸函数,简单理解就是编译器不会给你加入任何代码,堆栈平衡什么都由你自己维护。
extern "C" _declspec(naked) void HookProc()
{
_asm
{
//保存寄存器
pushad
pushfd
}
//
_asm
{
//为具体你的操作,你想做的事情都在这里实现。这里我只获取pushNum两个参数
mov EAX, DWORD PTR SS : [esp + 0x28]
mov dwParaX, EAX
mov EAX, DWORD PTR SS : [esp + 0x2C]
mov dwParaY, EAX
}
sprintf(szBuffer, "参数X:%x\n 参数Y:%x\n", dwParaX, dwParaY);
MessageBoxA(NULL, szBuffer, "HOOK参数数据", MB_OK);
//
_asm
{
popfd
popad
}
//执行覆盖代码
_asm
{
push ebp//我们把替换了得代码在执行一遍
mov ebp, esp
sub esp, 0C0h
/* 00E5FD80 55 push ebp
00E5FD81 8B EC mov ebp, esp
00E5FD83 81 EC C0 00 00 00 sub esp, 0C0h*/
}
_asm
{
jmp g_dwRetAddr//跳转回pushNum函数
}
}