inline hooking
在IAT hook的时候,如果遇到延迟绑定,即使再IAT hook GetProcAddress,也会使得本来很简单的事情,看起来是那么的复杂。那么有没有一种更好的方法来避免DLL延迟绑定的问题,答案是肯定有的,inline hook会是一种不错的选择。
Inline Hook通过硬编码的方式修改目标函数的内存空间(通常是开始的一段字节,且一般在第一个call之前,很多时候是第一个字节,这么做是主要是为了防止堆栈混乱,如果能够处理好堆栈问题,放在哪个位置都是无所谓的),写入跳转语句,跳转到对应的函数空间里去。这样,该目标函数只要被调用,程序就会跳转到我们的函数中来,我们在自己写的函数里需要完成2个问题:
1)处理好堆栈平衡。在程序流程中,堆栈的平衡是非常的重要,如果inline hook写入的是call语句,那么需要针对函数是否在函数里面做好了堆栈平衡做相应的处理,所以既要保证返回到目标函数中(如果写入的是jmp语句,就不需要),也要保证目标函数能在顺利执行完毕后返回到我们的函数中来。
2)执行被覆盖的指令。我们向目标函数地址空间些如跳转指令(jmp xxxxxxxx)时,势必要覆盖原先的一些汇编指令,所以我们一定要保证这些被覆盖的指令能够顺利执行。关于这部分指令的执行,一般是将其放在我们的函数中,让我们的函数“帮助”目标函数执行完被覆盖的指令,然后再跳回目标函数中被覆盖内后后的地址继续执行剩余内容。跳回去的时候,一定要算好是跳回到什么地址,是目标函数起始地址后的第几个字节。最好的处理方法就是跳回去之前,还原之前被覆盖的汇编指令。
如下分析所示:
Before hook:
INT Func() INT Func()
{ {
return 10; mov eax, 0xA
ret
} }
After hook:
INT Func() INT Func()
{ {
ReplaceFunc; jmp 0xXXXXXX;
ret
} }
目标函数所跳转到的函数,这里只是简单的返回个信息。
VOID ReplaceFunc(VOID)
{
::MessageBox(NULL, "hello world!", "", MB_OK);
HookStatus(false, g_TargetFuncAddr);
//return fnTestDll();
}
Hook函数,获取目标函数的地址,修改函数的起始5个字节。这里使用的是相对跳转,先计算跳转的距离XXXX,得到E9 XXXX(相对地址),写入到目标函数中去。
BOOL InlineHooking
(
IN LPCTSTR pImageName,
IN LPCTSTR pTargetFuncName,
IN int pReplaceFuncAddr
)
{
HMODULE hLib = LoadLibrary(pImageName);
if (NULL == hLib) return FALSE;
g_TargetFuncAddr = (int)GetProcAddress(hLib, pTargetFuncName);
if (NULL == g_TargetFuncAddr) return FALSE;
CopyMemory(g_OldCode,(const void *)g_TargetFuncAddr, 5);
g_NewCode[0] = 0xE9;
int dwJmpAddr = pReplaceFuncAddr - g_TargetFuncAddr - 5;
CopyMemory(&g_NewCode[1], &dwJmpAddr, 4);
g_bHooked = TRUE;
HookStatus(true, g_TargetFuncAddr);
return FALSE;
}
修改内存
bool HookStatus( bool blnIsHook, IN int pTargetFuncAddr )
{
DWORD oldACC,newACC;
if (!g_bHooked) {return false;}
//覆盖原先的一些汇编指令
if (blnIsHook)
{
VirtualProtect((LPVOID)pTargetFuncAddr, 5, PAGE_WRITECOPY, &oldACC);
CopyMemory((void *)pTargetFuncAddr, g_NewCode, 5);
VirtualProtect((LPVOID)pTargetFuncAddr, 5, oldACC, &newACC);
}
//还原之前的汇编指令,这部分代码会引发多线程的问题,后面会介绍它对应的解决办法
else
{
VirtualProtect((LPVOID)pTargetFuncAddr, 5, PAGE_WRITECOPY, &oldACC);
CopyMemory((void *)pTargetFuncAddr, g_OldCode, 5);
VirtualProtect((LPVOID)pTargetFuncAddr, 5, oldACC, &newACC);
}
return true;
}
确实,这个办法要比IAT HOOK好很多。我们不需要针对每个模块逐个HOOK了,也不担心GetProcAddress引起的问题。但是,这个方法确不能很好地在多线程环境中工作,当有多个线程执行了我们 inline Hook 的目标函数的时候,一个线程在使用修改之前的 目标函数 ,一个线程在使用修改之后的目标函数,就会导致修改之前的目标函数函数不能够实现挂钩的效果。针对这种问题我们可以给这个函数加锁或者信号量,但这样会使效率降低,只能同时一个线程使用它。有一种方法可以很好的避免这个问题,就是只要不还原之前的汇编指令,每一个线程都是使用修改之后的目标函数,我们把被覆盖的那部分指令搬到ReplaceFunc函数中去,在调回目标函数前,先执行该部分汇编指令。
通过上面例子的详述,我们可以知道inline hook的强大,它既可以用到JUMP XXXX,也可以使用为CALL XXXX,当然也可以添加其它你想要实现的功能的汇编指令。当然在HOOK之前你必须考虑到一个问题,就是即将被HOOK的目标函数是否有足够的空间来覆盖原先的汇编代码。
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法