找回密码
 加入我们

QQ登录

只需一步,快速开始

搜索
查看: 8530|回复: 5

TP的学习笔记:实现通用且较为稳定安全的Head Inline Hook模块

[复制链接]

78

主题

190

回帖

9

精华

贵宾会员

积分
15605
发表于 2015-7-25 14:45:52 | 显示全部楼层 |阅读模式
记得从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。
代码如下:
  1. void Hook(PVOID Func,PVOID NewFunc,PVOID ProxyFunc)
  2. {
  3.         PMDL pMdl;
  4.         ULONG PatchSize;
  5.         BYTE g_HookCode[5]={0xE9,0,0,0,0};//Relative Jump
  6.         BYTE Jmp_Orig_Code[7]={0xEA,0,0,0,0,0x08,0x00 }; //Absolute Address Jump
  7.         PatchSize=GetPatchSize(Func,5);//Get The Number Of Bytes To Patch
  8.         RtlCopyMemory((PBYTE)ProxyFunc,(PBYTE)Func,PatchSize);//Set The Head Of Original Function
  9.         *((PULONG)(Jmp_Orig_Code+1))=(ULONG)((PBYTE)Func+PatchSize );//Address Of Original Function + N
  10.         RtlCopyMemory((PBYTE)ProxyFunc+PatchSize,Jmp_Orig_Code,7);//Absolute Jump
  11.         *((ULONG*)(g_HookCode+1))=(ULONG)NewFunc-(ULONG)Func-5;//Calculate The Relative Jump
  12.         pMdl=IoAllocateMdl(Func,5,FALSE,FALSE,NULL);//Allocate MDL
  13.         if(pMdl)
  14.         {
  15.                 __try
  16.                 {
  17.                         //Lock Page
  18.                         MmProbeAndLockPages(pMdl,KernelMode,IoWriteAccess);
  19.                 }
  20.                 __except(EXCEPTION_EXECUTE_HANDLER)
  21.                 {
  22.                         IoFreeMdl(pMdl);
  23.                 }
  24.                 //Raise The Interrupt Request Level
  25.                 f_oldirql=KeRaiseIrqlToDpcLevel();
  26.                 //Initialize And Acquire Spinlock
  27.                 KeInitializeSpinLock(&f_spinlock);
  28.                 KeAcquireSpinLockAtDpcLevel(&f_spinlock);
  29.                 //Close Write-Protection
  30.                 _asm
  31.                 {
  32.                         cli              
  33.                         mov eax, cr0   
  34.                         and eax, not 10000h
  35.                         mov cr0, eax     
  36.                 }
  37.                 //Hook Operation
  38.                 RtlCopyMemory(Func, g_HookCode, 5);
  39.                 //Reopen Write-Protection
  40.                 _asm
  41.                 {
  42.                         mov eax, cr0   
  43.                         or  eax, 10000h            
  44.                         mov cr0, eax              
  45.                         sti                  
  46.                 }
  47.                 //Release Spinlock
  48.                 KeReleaseSpinLockFromDpcLevel(&f_spinlock);
  49.                 //Lower Interupt Request Level
  50.                 KeLowerIrql(f_oldirql);
  51.                 //Unlock Pages
  52.                 MmUnlockPages(pMdl);
  53.                 //Free MDL
  54.                 IoFreeMdl(pMdl);
  55.         }
  56. }
复制代码

卸载钩子的时候过程相似,也需要锁定页面,提升中断请求级并使用自旋锁,代码如下:
  1. void UnHook(PVOID Func,PVOID ProxyFunc)
  2. {
  3.         PMDL pMdl;
  4.         //Allocate MDL
  5.         pMdl=IoAllocateMdl(Func,5,FALSE,FALSE,NULL);
  6.         if(pMdl)
  7.         {
  8.                 __try
  9.                 {
  10.                         //Lock Pages
  11.                         MmProbeAndLockPages(pMdl,KernelMode,IoWriteAccess);
  12.                 }
  13.                 __except(EXCEPTION_EXECUTE_HANDLER)
  14.                 {
  15.                         IoFreeMdl(pMdl);
  16.                 }
  17.                 //Raise Interupt Request Level
  18.                 f_oldirql=KeRaiseIrqlToDpcLevel();
  19.                 //Initialize And Acquire Spinlock
  20.                 KeInitializeSpinLock(&f_spinlock);
  21.                 KeAcquireSpinLockAtDpcLevel(&f_spinlock);
  22.                 //Close Write-Protection
  23.                 _asm
  24.                 {
  25.                         cli              
  26.                         mov eax, cr0   
  27.                         and eax, not 10000h
  28.                         mov cr0, eax     
  29.                 }
  30.                 //Unhook Operation
  31.                 RtlCopyMemory(Func,ProxyFunc,5);
  32.                 //Reopen Write-Protection
  33.                 _asm
  34.                 {
  35.                         mov eax, cr0   
  36.                         or  eax, 10000h            
  37.                         mov cr0, eax              
  38.                         sti                  
  39.                 }
  40.                 //Release Spinlock
  41.                 KeReleaseSpinLockFromDpcLevel(&f_spinlock);
  42.                 //Lower Interupt Request Level
  43.                 KeLowerIrql(f_oldirql);
  44.                 //Unlock Pages
  45.                 MmUnlockPages(pMdl);
  46.                 //Free MDL
  47.                 IoFreeMdl(pMdl);
  48.         }
  49. }
复制代码

光有上述代码还不行,我们还要写个StartHook和StopHook函数去调用Hook和UnHook函数
  1. void StartHook()
  2. {
  3.         //deal with the NTKERNELAPI
  4.         UNICODE_STRING uniFuncName1;
  5.         UNICODE_STRING uniFuncName2;
  6.         RtlInitUnicodeString(&uniFuncName1,L"PsLookupProcessByProcessId");
  7.         RtlInitUnicodeString(&uniFuncName2,L"PsLookupThreadByThreadId");
  8.         PsLookupProcessByProcessId=MmGetSystemRoutineAddress(&uniFuncName1);
  9.         PsLookupThreadByThreadId=MmGetSystemRoutineAddress(&uniFuncName2);
  10.         //Initialize Proxy Function
  11.         Old_PsLookupProcessByProcessId=ExAllocatePool(NonPagedPool,20);
  12.         Old_PsLookupThreadByThreadId=ExAllocatePool(NonPagedPool,20);
  13.         //NOP The Proxy Function
  14.         memset(Old_PsLookupProcessByProcessId,0x90,20);
  15.         memset(Old_PsLookupThreadByThreadId,0x90,20);
  16.         //Start To Hook
  17.         Hook(PsLookupProcessByProcessId,fake_PsLookupProcessByProcessId,Old_PsLookupProcessByProcessId);
  18.         Hook(PsLookupThreadByThreadId,fake_PsLookupThreadByThreadId,Old_PsLookupThreadByThreadId);
  19. }
  20. void StopHook()
  21. {
  22.         //Unhook
  23.         UnHook(PsLookupProcessByProcessId,Old_PsLookupProcessByProcessId);
  24.         UnHook(PsLookupThreadByThreadId,Old_PsLookupThreadByThreadId);
  25.         //Release the memory
  26.         ExFreePool(Old_PsLookupProcessByProcessId);
  27.         ExFreePool(Old_PsLookupThreadByThreadId);
  28. }
复制代码

因此若是想Hook新的函数,只要向里面添加项目即可。
写伪函数就没有什么悬念了,在PsLookupProcessByProcessId的伪函数里过滤掉EPROCESS,在PsLookupThreadByThreadId中过滤掉IoThreadToProcess(ETHREAD)就可以了(当然也可以过滤PID和TID),代码如下:
  1. NTSTATUS fake_PsLookupProcessByProcessId(IN ULONG ProcessId,OUT PEPROCESS *Process)
  2. {
  3.         NTSTATUS st;
  4.         PEPROCESS txps;
  5.         st=Old_PsLookupProcessByProcessId(ProcessId,&txps);
  6.         if(txps==ProtectedProcess)
  7.         {
  8.                 st=0xC0000022;
  9.         }
  10.         else
  11.         {
  12.                 st=Old_PsLookupProcessByProcessId(ProcessId,Process);
  13.         }
  14.         return st;
  15. }

  16. NTSTATUS fake_PsLookupThreadByThreadId(IN ULONG ThreadId,OUT PETHREAD *Thread)
  17. {
  18.         NTSTATUS st;
  19.         PETHREAD txtd;
  20.         PEPROCESS txps;
  21.         st=Old_PsLookupThreadByThreadId(ThreadId,&txtd);
  22.         txps=IoThreadToProcess(txtd);
  23.         if(txps==ProtectedProcess)
  24.         {
  25.                 st=0xC0000022;
  26.         }
  27.         else
  28.         {
  29.                 st=Old_PsLookupThreadByThreadId(ThreadId,Thread);
  30.         }
  31.         return st;
  32. }
复制代码

效果很不错,在Windows XP,Windows Server 2003,Windows 7,Windows 8中均测试通过。如图所示:
Windows XP的测试效果:

XP下的保护效果

XP下的保护效果

Windows Server 2003的测试效果:

2k3的保护效果

2k3的保护效果

Windows 7的测试效果:

win7的保护效果

win7的保护效果

Windows 8的测试效果:

win8的保护效果

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的学习笔记系列帖子均是安利给大家的,既不设置阅读权限,也不设置水晶币。

InlineHookTemplate.zip

174.82 KB, 下载次数: 4401

下载代码不回帖是一种很欠扁的行为

评分

参与人数 1水晶币 +1 收起 理由
andylau004 + 1 很给力!

查看全部评分

857

主题

2632

回帖

2

精华

管理员

此生无悔入华夏,  长居日耳曼尼亚。  

积分
36130
发表于 2015-7-25 23:10:06 | 显示全部楼层
这可以算是新版的“NT通用进程保护”了。

1

主题

23

回帖

0

精华

铜牌会员

积分
205
发表于 2015-8-22 14:06:07 | 显示全部楼层
下载代码不回帖是一种很欠扁的行为,赤裸裸的威胁阿

78

主题

190

回帖

9

精华

贵宾会员

积分
15605
 楼主| 发表于 2015-8-22 15:01:03 | 显示全部楼层
我也只是口头上说说,真正赤裸裸的威胁是那种“想看隐藏内容请回复”之类的。

5

主题

116

回帖

0

精华

铜牌会员

积分
174
发表于 2023-12-7 00:09:54 | 显示全部楼层
x64 可以这样玩吗?
您需要登录后才可以回帖 登录 | 加入我们

本版积分规则

快速回复 返回顶部 返回列表