|
我记得我第一次搞Hook的时候,是直接拿黑客防线09年的《Ring0中Inline Hook Shadow SSDT实现窗体保护》来搞得。当时里面的附件是Hook了ObOpenObjectByPointer实现的进程保护,但是就在某一天,它蓝屏了。。。后来才听说了暴力修改WP标志位是一种不安全不稳定的办法,于是就诞生了本文。本文提到的技术,参考了《SSDT和Shadow SSDT完全解析(二)》的文章。主要就是用MDL的方式避开只读保护罢了。本文只需要简单的修改一点点就可实现通用的Win32 Inline Hook SSDT模块。
首先先讲如何避开只读保护。
既然暴力修改WP标志位是不稳定的方法,那我们就不能用这个方法。这里选用MDL映射的方式。首先创建一个MDL,在MmCreateMdl的第二个参数填入要Hook的函数地址,第三个参数填入5。这主要是因为jmp xxxxxxxx指令是5个字节的。
MDL创建完成后,用MmBuildMdlForNonPagedPool建立一段内存区域,修改MDL->MdlFlags的值,再用MmMapLockedPages建立映射。最后就可以用RtlCopyMemory来修改前五个字节了。(顺带着说一点,如果是想用地址替换的方式实现钩子的话,建议用InterlockedExchange函数)代码如下:
- //HOOK函数
- void Hook (PVOID Func,PVOID New_Func,PVOID Proxy_Func,PMDL MdlForFunc,PVOID Msct)
- {
- ULONG PatchSize;
- BYTE g_HookCode[5] = { 0xE9, 0, 0, 0, 0 };//相对跳转
- BYTE Jmp_Orig_Code[7] = { 0xEA, 0, 0, 0, 0, 0x08, 0x00 }; //绝对地址跳转
- PatchSize=GetPatchSize(Func,5);//获得要Patch的字节数
- //构造Proxy_Func
- RtlCopyMemory((PBYTE)Proxy_Func,(PBYTE)Func,PatchSize);//实现原函数头
- *((PULONG)(Jmp_Orig_Code+1))=(ULONG)((PBYTE)Func+PatchSize );//原函数+N 地址
- RtlCopyMemory((PBYTE)Proxy_Func+PatchSize,Jmp_Orig_Code,7);//绝对地址跳转
- *((ULONG*)(g_HookCode+1))=(ULONG)New_Func-(ULONG)Func-5;//计算JMP 地址
- MdlForFunc=MmCreateMdl(NULL,Func,5);
- if(MdlForFunc)
- {
- MmBuildMdlForNonPagedPool(MdlForFunc);
- MdlForFunc->MdlFlags=MdlForFunc->MdlFlags|MDL_MAPPED_TO_SYSTEM_VA;
- Msct=MmMapLockedPages(MdlForFunc,KernelMode);
- RtlCopyMemory(Func,g_HookCode,5);
- }
- }
复制代码
实现了Hook,就要实现Unhook,方法很简单,先用RtlCopyMemory恢复前五个字节,接着用MmUnmapLockedPages取消映射,再用IoFreeMdl释放掉Mdl就可以了。代码如下:
//UnHook函数
- void UnHook (PVOID Func,PVOID Proxy_Func,PMDL MdlForFunc,PVOID Msct)
- {
- RtlCopyMemory(Func,Proxy_Func,5);
- if(MdlForFunc)
- {
- MmUnmapLockedPages(Msct,MdlForFunc);
- IoFreeMdl(MdlForFunc);
- }
- }
复制代码
这里的Hook函数和Unhook函数参数比较多。。。如果是小白仅仅要调用这个Hook/Unhook函数,那么在Hook函数中,第一个参数填入要Hook的地址,第二个参数填入伪函数的地址,第三个参数填入代理函数的地址,第四个参数填入对应的MDL,第五个填入MSCT(其实是MappedSystemCallTable的缩写),其实后两个参数已经在Hook函数里处理完成了,调用之前不用理睬,声明一下变量就行。至于Unhook,也很简单,调用的时候,第一个参数输入被Hook的函数地址,第二个参数输入代理函数的地址,第三个参数输入为函数定义的MDL,第四个填入对应的MSCT即可。
完成了Hook和Unhook,接着就是动态确认服务号。由于Win32的SSDT和Win64的SSDT不一样,不是那种随机的,所以有些人可能会用硬编码了。我记得www.vsyide.com上有个查看SDT的小工具,可以确定多个系统的SSDT/SSSDT的函数服务号,这个工具大概长着个样子:
vsyide的SSDT查看工具
于是便可以惊喜的发现它只能看XP,2003,Vista,2008,win7,win8这六个系统。。。那么win10呢。。。所以用硬编码不是个好选择
那么我们如何取得Index呢?在内核里有相同数量的NtXXX函数和ZwXXX函数。那我们看看ZwTerminateProcess的反汇编结果:
windbg对ZwTerminateProcess的反汇编
可以发现第一个字节是0x8b,第二到第五个字节是0x00000101,也就是0x101,这就是我们要的服务编号!
所以我们的动态获取Index的思路就是取得ZwXXX函数的地址,然后读地址+1,长度为4的数据,这个数据就是Index了,代码如下:
- ULONG DynamicGetNtTerminateProcessIndex()
- {
- UNICODE_STRING uniFuncName;
- UCHAR tmp=0;
- ULONG IndexOf_NtTerminateProcess=0;
- //取得ZwTerminateProcess的函数地址
- RtlInitUnicodeString(&uniFuncName,L"ZwTerminateProcess");
- ZwTerminateProcess=MmGetSystemRoutineAddress(&uniFuncName);
- //先判断一下有木有被Hook
- RtlCopyMemory(&tmp,ZwTerminateProcess,1);
- if(tmp==0xb8)
- {
- //取得Index
- RtlCopyMemory(&IndexOf_NtTerminateProcess,(PVOID)((ULONG)ZwTerminateProcess+1),4);
- }
- return IndexOf_NtTerminateProcess;
- }
复制代码
上述代码就是取得NtTerminateProcess的Index的代码了。
要做到通用,只需要修改RtlInitUnicodeString这一行中的字符串就行了,别忘了把"Nt"改成"Zw"{:soso_e113:}
那么就开始测试结果吧,下面贴出XP和2k3的测试结果图片。
winxp的保护效果
2k3的保护效果
本文提及的技术,均在windows xp和windows server 2003内测试通过。 |
评分
-
查看全部评分
|