首页
社区
课程
招聘
[原创]内核层的三种Hook技术的使用
发表于: 2021-10-20 17:26 27998

[原创]内核层的三种Hook技术的使用

2021-10-20 17:26
27998

本文是在Win7 X86系统上进行实验,实验的内容是在内核层通过三种不同的Hook方法来实现进程保护,让运行的程序不被其他的程序关闭。这里用来被保护的程序是一个简单的HelloWord弹窗程序,程序名是demo.exe。

    一个程序要想关闭一个进程,首先就要获取这个进程的句柄,并且获得的这个句柄还需要具备进程关闭的访问掩码。也就是说,在用户层我们想要关闭一个进程,首先就需要使用以下的代码来获得一个进程的句柄。

其中第一个参数PROCESS_TERMINATE就是让我们获得的进程句柄具有关闭进程的访问掩码,它在文档中的定义如下

那也就是说,如果HOOK了打开进程的函数,就可以在我们编写的函数中判断所打开的进程是否是我们要保护的进程,以及所要获得的进程句柄是否是要具备进程关闭的访问掩码,如果是的话就把它去掉。这个时候,程序如果使用这个被删除掉进程关闭访问掩码的句柄来关闭进程,就会失败,这就完成了对进程的保护作用。

而在用户层所调用的OpenProcess到内核层以后,其实在内核中是调用ZwOpenProcess,所以只要在内核中监控这个函数就可以完成实验。该函数在文档中的定义如下

参数2所代表的就是要打开的这个进程要具备的访问掩码,通过对它的判断与修改就可以实现进程保护。

参数4的一个指向CLIENT_ID的指针,CLIENT_ID在文档中的定义如下

其中的UniqueProcess代表的就是进程的PID,根据它就可以用来判断是否是要保护的进程。

由于三个实验只是HOOK的方法的代码不同,而其他的比如用户层与内核层的通信等等是相同的。所以为了避免重复,这里先给出代码框架,后续的HOOK代码只需要加进去就好。

用户层所要做的事情就是

安装和卸载驱动

根据要保护的进程名获取进程PID,以传给内核层供内核层使用

根据用户输入选择安装或者卸载HOOK,向内核层发送相应的IoControlCode并把返回结果告诉用户

用户层的代码如下

    而驱动程序就要接受用户层传下来的IoControlCode来进行Hook或者UnHook,并把返回结果传回用户层,具体代码如下

InlineHook的实现原理是:将原函数要执行的代码改为jmp跳转指令,跳转到我们要执行的代码中执行中。而在我们的代码中,为了保证Hook以后原本的功能可以实现就需要恢复被修改的字节然后再次调用,可是在多线程的环境中频繁的修改与删除很容易带来错误,造成蓝屏。

为了避免这种情况,可以采取热补丁的技术来实现,首先看看NtOpenProcess函数的反汇编结果

可以看到在函数最开始执行了一句mov edi, edi这么一个无意义的指令,而这个指令对应的硬编码是两字节0x8BFF,而在这个函数上面有5个字节的0x90。所以可以通过修改最开始的两个字节实现短跳转跳到上面的5个字节的0x90的开始地址执行,而这五个字节的0x90就可以修改为跳转到要真正执行的我们的函数的地址。

而在我们的函数中,如果要执行原来的代码,只需要直接跳转到NtOpenProcess函数2个字节偏移处,也就是略过最开始的mov edi, edi这条指令,直接从push ebp开始执行,这样就不用频繁的对原来的函数进行修改。

这里还有一点需要注意,由于页保护的存在,所以不能直接通过函数地址对函数中的字节进行修改。需要通过内存描述符(MDL)描述一块包含该内存区域的起始地址,拥有者进程,字节数量,标记等信息的内存区域,并通过将它修改具有可写的属性来实现对函数的读写。

具体实现代码如下

程序员使用OpenProcess打开进程的时候,这个函数其实是Kernel32.dll的导出函数,而在Kernel32.dll中这个函数的作用就是检测参数,随后将参数入栈以后在调用ntdll.dll中的ZwOpenProcess,该函数的具体实现如下

可以看到这个函数首先将0xBE赋给eax,此时这个0xBE就叫做调用号,它用来指定在内核中你要调用的那个内核函数是哪个。随后对0x7FFE0300这个地址中保存的地址进行了调用。

而0x7FFE03000这个地址中究竟保存的是什么就要取决于你的CPU是否支持快速调用,如果支持这个地址中保存的就是ntdll.dll中的KiFastCallSystemCall,如果不支持那么保存的就是ntdll.dll中的KiIntSystemCall。

而检测系统是否支持快速调用的方法是,为eax赋值为1,随后调用cpuid指令,那么处理器的信息就会被放到ecx和edx寄存器中,此时判断edx的第11位是否为1,如果为1那么当前CPU就支持快速调用,否则不支持。检测代码如下

而要实现SysenterHook的电脑,它的CPU是要支持快速调用的,所以此时继续跟进ntdll.dll中的KiFastCallSystemCall,函数实现如下

可以看到,函数首先将esp赋值给edx,也就是说此时edx指向的是包含返回地址和参数的栈地址。但要注意,由于调用了ZwOpenProcess以后又调用了KiFastSystemCall。所以此时edx所指的地址保存了两个返回地址,具体如下图所示


[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2022-2-26 20:40 被1900编辑 ,原因:
收藏
免费 5
支持
分享
最新回复 (3)
雪    币: 183
活跃值: (6634)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
2
大佬你好,内核相关的学习资料有啥推荐吗,我只有谭文的那本内核编程,不胜感激
2021-11-25 09:52
0
雪    币: 22413
活跃值: (25361)
能力值: ( LV15,RANK:910 )
在线值:
发帖
回帖
粉丝
3
风清涯 大佬你好,内核相关的学习资料有啥推荐吗,我只有谭文的那本内核编程,不胜感激[em_13]
新出的Windows内核编程也挺好的
2021-11-25 10:07
0
雪    币: 183
活跃值: (6634)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
4
g_pNtOpenProcessMap_5应该是地址+5
2022-7-27 16:34
0
游客
登录 | 注册 方可回帖
返回
//