|
记得从2011年开始学Win32内核的时候,看到的不少和Inline Hook有关的文章。要么直接写HookXXX完事,搞得只能Hook这个函数,让小白改不了,比如在《VB小子玩转驱动程序》系列帖子的第九章的Hook ObReferenceObjectByHandle的附件;要么就是稳定性太差,Hook某些调用极其频繁的函数(比如ObOpenObjectByPointer)的时候会蓝屏。因此,本人在此发表一个较为安全的Inline Hook模块,以挂钩PsLookupProcessByProcess+PsLookupThreadByThreadId为例,制作进程保护器(进程保护器效果立竿见影,上次的MDL映射方式貌似只支持NT5)。
由于Inline Hook的基本原理是修改函数自身,而非地址替换,故SSDT Hook以及EAT/IAT Hook均不是Inline Hook,但属于地址替换的Call Hook是修改函数中某个call指令的后四个字节,所以Call Hook是Inline Hook。
本文所示范的是Head Inline Hook。Head Inline Hook有很多方式实现,可以是修改成jmp xxxxxxxx的方式,也就是E9 XXXXXXXX,它占五个字节,这是最常见的方式;也可以是修改成push xxxxxxxx ret的方式,即68 XXXXXXXX C3,它占六个字节,一般不这么搞;还有就是修改成jmp dword ptr xxxxxxxx的方式,也就是FF 25 XXXXXXXX,它也占六个字节。由于绝大多数的Win32下的内核函数头五个字节均是8B FF 55 8B EC,因此如果不是去挂钩奇怪的函数,比如KiSwapContext,其头五个字节是83 EC 10 89 5C,并不是完整的两条指令,故Hook之后会导致WinDbg显示异常的情况。由于其头七个字节是完整的两个指令,故要Hook这个函数并使其在WinDbg中显示不异常,可以用jmp dword ptr xxxxxxxx改掉前六个字节,第七个字节填NOP就可以使其显示不异常了。
首先先讲一下我们的Hook过程:首先利用反汇编引擎取得要Patch的字节个数,然后申请MDL并锁定页面,提升中断请求级到DPC级别,在该中断请求级中申请并使用自旋锁防止关闭中断的时候蓝屏,随后关闭中断并暴力修改CR0寄存器的值,使写入保护失效,接着修改函数头五个字节为jmp xxxxxxxx,再改回CR0寄存器的值并开启中断,使得写入保护生效。然后在DPC中断请求级下释放掉自旋锁,并降低中断请求级,最后一步便是解锁页面并释放MDL。
代码如下:
- void Hook(PVOID Func,PVOID NewFunc,PVOID ProxyFunc)
- {
- PMDL pMdl;
- ULONG PatchSize;
- BYTE g_HookCode[5]={0xE9,0,0,0,0};//Relative Jump
- BYTE Jmp_Orig_Code[7]={0xEA,0,0,0,0,0x08,0x00 }; //Absolute Address Jump
- PatchSize=GetPatchSize(Func,5);//Get The Number Of Bytes To Patch
- RtlCopyMemory((PBYTE)ProxyFunc,(PBYTE)Func,PatchSize);//Set The Head Of Original Function
- *((PULONG)(Jmp_Orig_Code+1))=(ULONG)((PBYTE)Func+PatchSize );//Address Of Original Function + N
- RtlCopyMemory((PBYTE)ProxyFunc+PatchSize,Jmp_Orig_Code,7);//Absolute Jump
- *((ULONG*)(g_HookCode+1))=(ULONG)NewFunc-(ULONG)Func-5;//Calculate The Relative Jump
- pMdl=IoAllocateMdl(Func,5,FALSE,FALSE,NULL);//Allocate MDL
- if(pMdl)
- {
- __try
- {
- //Lock Page
- MmProbeAndLockPages(pMdl,KernelMode,IoWriteAccess);
- }
- __except(EXCEPTION_EXECUTE_HANDLER)
- {
- IoFreeMdl(pMdl);
- }
- //Raise The Interrupt Request Level
- f_oldirql=KeRaiseIrqlToDpcLevel();
- //Initialize And Acquire Spinlock
- KeInitializeSpinLock(&f_spinlock);
- KeAcquireSpinLockAtDpcLevel(&f_spinlock);
- //Close Write-Protection
- _asm
- {
- cli
- mov eax, cr0
- and eax, not 10000h
- mov cr0, eax
- }
- //Hook Operation
- RtlCopyMemory(Func, g_HookCode, 5);
- //Reopen Write-Protection
- _asm
- {
- mov eax, cr0
- or eax, 10000h
- mov cr0, eax
- sti
- }
- //Release Spinlock
- KeReleaseSpinLockFromDpcLevel(&f_spinlock);
- //Lower Interupt Request Level
- KeLowerIrql(f_oldirql);
- //Unlock Pages
- MmUnlockPages(pMdl);
- //Free MDL
- IoFreeMdl(pMdl);
- }
- }
复制代码
卸载钩子的时候过程相似,也需要锁定页面,提升中断请求级并使用自旋锁,代码如下:
- void UnHook(PVOID Func,PVOID ProxyFunc)
- {
- PMDL pMdl;
- //Allocate MDL
- pMdl=IoAllocateMdl(Func,5,FALSE,FALSE,NULL);
- if(pMdl)
- {
- __try
- {
- //Lock Pages
- MmProbeAndLockPages(pMdl,KernelMode,IoWriteAccess);
- }
- __except(EXCEPTION_EXECUTE_HANDLER)
- {
- IoFreeMdl(pMdl);
- }
- //Raise Interupt Request Level
- f_oldirql=KeRaiseIrqlToDpcLevel();
- //Initialize And Acquire Spinlock
- KeInitializeSpinLock(&f_spinlock);
- KeAcquireSpinLockAtDpcLevel(&f_spinlock);
- //Close Write-Protection
- _asm
- {
- cli
- mov eax, cr0
- and eax, not 10000h
- mov cr0, eax
- }
- //Unhook Operation
- RtlCopyMemory(Func,ProxyFunc,5);
- //Reopen Write-Protection
- _asm
- {
- mov eax, cr0
- or eax, 10000h
- mov cr0, eax
- sti
- }
- //Release Spinlock
- KeReleaseSpinLockFromDpcLevel(&f_spinlock);
- //Lower Interupt Request Level
- KeLowerIrql(f_oldirql);
- //Unlock Pages
- MmUnlockPages(pMdl);
- //Free MDL
- IoFreeMdl(pMdl);
- }
- }
复制代码
光有上述代码还不行,我们还要写个StartHook和StopHook函数去调用Hook和UnHook函数
- void StartHook()
- {
- //deal with the NTKERNELAPI
- UNICODE_STRING uniFuncName1;
- UNICODE_STRING uniFuncName2;
- RtlInitUnicodeString(&uniFuncName1,L"PsLookupProcessByProcessId");
- RtlInitUnicodeString(&uniFuncName2,L"PsLookupThreadByThreadId");
- PsLookupProcessByProcessId=MmGetSystemRoutineAddress(&uniFuncName1);
- PsLookupThreadByThreadId=MmGetSystemRoutineAddress(&uniFuncName2);
- //Initialize Proxy Function
- Old_PsLookupProcessByProcessId=ExAllocatePool(NonPagedPool,20);
- Old_PsLookupThreadByThreadId=ExAllocatePool(NonPagedPool,20);
- //NOP The Proxy Function
- memset(Old_PsLookupProcessByProcessId,0x90,20);
- memset(Old_PsLookupThreadByThreadId,0x90,20);
- //Start To Hook
- Hook(PsLookupProcessByProcessId,fake_PsLookupProcessByProcessId,Old_PsLookupProcessByProcessId);
- Hook(PsLookupThreadByThreadId,fake_PsLookupThreadByThreadId,Old_PsLookupThreadByThreadId);
- }
- void StopHook()
- {
- //Unhook
- UnHook(PsLookupProcessByProcessId,Old_PsLookupProcessByProcessId);
- UnHook(PsLookupThreadByThreadId,Old_PsLookupThreadByThreadId);
- //Release the memory
- ExFreePool(Old_PsLookupProcessByProcessId);
- ExFreePool(Old_PsLookupThreadByThreadId);
- }
复制代码
因此若是想Hook新的函数,只要向里面添加项目即可。
写伪函数就没有什么悬念了,在PsLookupProcessByProcessId的伪函数里过滤掉EPROCESS,在PsLookupThreadByThreadId中过滤掉IoThreadToProcess(ETHREAD)就可以了(当然也可以过滤PID和TID),代码如下:
- NTSTATUS fake_PsLookupProcessByProcessId(IN ULONG ProcessId,OUT PEPROCESS *Process)
- {
- NTSTATUS st;
- PEPROCESS txps;
- st=Old_PsLookupProcessByProcessId(ProcessId,&txps);
- if(txps==ProtectedProcess)
- {
- st=0xC0000022;
- }
- else
- {
- st=Old_PsLookupProcessByProcessId(ProcessId,Process);
- }
- return st;
- }
- NTSTATUS fake_PsLookupThreadByThreadId(IN ULONG ThreadId,OUT PETHREAD *Thread)
- {
- NTSTATUS st;
- PETHREAD txtd;
- PEPROCESS txps;
- st=Old_PsLookupThreadByThreadId(ThreadId,&txtd);
- txps=IoThreadToProcess(txtd);
- if(txps==ProtectedProcess)
- {
- st=0xC0000022;
- }
- else
- {
- st=Old_PsLookupThreadByThreadId(ThreadId,Thread);
- }
- return st;
- }
复制代码
效果很不错,在Windows XP,Windows Server 2003,Windows 7,Windows 8中均测试通过。如图所示:
Windows XP的测试效果:
XP下的保护效果
Windows Server 2003的测试效果:
2k3的保护效果
Windows 7的测试效果:
win7的保护效果
Windows 8的测试效果:
win8的保护效果
我个人认为Hook这两个函数是非常出人意料的,因为在黑客防线2010年中的《模拟实现NT通用PspTerminateProcess》里,利用PsLookupThreadByThreadId+IoThreadToProcess来枚举线程,故该文的代码对本进程保护无效。
另外说八点:
一、本文的附件防不了复制句柄取得句柄的方式杀进程,由于NtDuplicateObject是导出函数,大家可以Hook这个函数。
二、NT6操作系统里有个函数叫ZwGetNextProcess,此函数可以枚举系统内所有进程,并以句柄的形式返回,故这种方式也防不了。要防止这种方式杀进程,可以Hook NtGetNextProcess,也可以Hook ObOpenObjectByPointer,还可以摘除活动进程链表。
三、利用线程消息洪水也可以杀死本文的进程保护。这里需要Hook SSSDT表中的NtUserPostThreadMessage。
四、本文的通用Hook模块,不解决罕见函数头的问题。故用本文的Hook通用模块Hook像KiSwapContext之类的函数虽然有效且不会蓝屏,但WinDbg会显示异常。
五、可以改进《模拟实现NT通用PspTerminateProcess》的代码,绕过PsLookupProcessByProcessId可以用枚举句柄表的方式实现,而绕过PsLookupThreadByThreadId的钩子还是中规中矩的遍历ThreadListHead吧。
六、本文的附件包括了《模拟实现NT通用PspTerminateProcess》原文的代码。
七、附件的描述摘自Hovi.Delphic的帖子
八、TP的学习笔记系列帖子均是安利给大家的,既不设置阅读权限,也不设置水晶币。 |
评分
-
查看全部评分
|