说驱动开发这么长时间了,也玩玩内核钩子,钩子(Hook)技术是一种截获对某一对象访问的技术,不仅在Windows平台,Linux平台上也有Hook技术。Hook技术种类繁多,实现细节也不同,还可以灵活使用。
我之前写过两篇Ring3下的API Inline Hook的博文,这两篇博文通过Inline Hook OpenProcess 实现对指定进程的保护,从而保护进程不能被其他进程打开、结束、写内存等等。
我们先来看看都有那些常见的Hook技术:
首先是Ring3层下的Hook技术,Ring3层下的Hook包括消息钩子和API Hook,消息钩子又分为全局消息钩子和进程内消息钩子,API Hook又分为IAT Hook和API Inline Hook,这两种钩子的区别和原理,看我上面的那两篇博文。
内核下Hook就比较多了,比如Object Hook,SSDT Hook,SSDT Shadow Hook,Inline Hook,IDT Hook,SysEnter Hook等等。
我就来说说SSDT Hook,当年SSDT Hook火的时候,我可能当时连编程都不知道,这是一种非常古老的技术,据说这是RootKit病毒第一次采用的技术,当然也是杀软和各种防护软件青睐的技术。
首先大家要知道Windows系统的内核和执行体层是ntoskrnl.exe,这是整个系统的内核,而ntdll.dll是用户模式到内核模式的接口,ntoskrnl中有两张表,分别是:KeServiceDescriptorTable 和 KeServiceDescriptorTableShadow
Ring3层 Kernel32.dll 中的API经过ntdll.dll后一般由KeServiceDescriptorTable中的函数处理。而KeServiceDescriptorTableShadow则主要处理来自 User32.dll 和 GDI32.dll 中的API。
KeServiceDescriptorTable在ntoskrnl.exe中是导出的。
如何知道他是导出的呢?可以使用Dumpbin工具来查看ntoskrnl的导出表:
我这里是32位系统,因此可以得到ntoskrnl导出的KeServiceDescriptorTable,64位系统上微软没有导出,就只能从32位其他电脑或虚拟机中复制一份ntoskrnl.exe。
我们来看看KeServiceDescriptorTable究竟是个什么东西。其实,他是这样的一个数据结构,至少前面是这样的:
我最早学习SSDTHook的时候就有一个非常傻的疑问,我当初就想KeServiceDescriptorTable他导出有什么用啊,怎么找到他在内存中的地址呢?需要先找到内核在内存中的基地址,然后根据KeServiceDescriptorTable在ntoskrnl中PE结构中的位置计算KeServiceDescriptorTable在内存中的地址,哈哈,要是这样做第一步就需要硬编码,而且还需要分析PE文件结构。其实很简单,KeServiceDescriptorTable是导出的,只要我们导入(声明一下,并在代码中引用)不就行了,KeServiceDescriptorTable在ntoskrnl.dll中是导出的,我们只需要和WDK中的ntoskrnl.lib连接即可,当驱动安装时,系统内核的PE加载器会自动对我们导入的KeServiceDescriptorTable进行重定向,我们就直接导入即可,非常简单。(关于重定向的细节,那就是PE文件结构的知识了)
简单说,就是我们声明KeServiceDescriptorTable,并在代码中引用它,那么编译器就会把这个声明编译成弱符号,连接时连接ntoskrnl.lib里的强符号即可。
就像这样:
(注意:用C++写代码必须用extern "C"修饰,C语言则不需要。这是符号修饰约定的问题,想必大家都明白,这里不再展开,当初学SSDTHook时不加extern "C"总是出问题,当初把我急的,算是怀念下吧)
KeServiceDescriptorTable中有我们非常感兴趣的内容,比如ServiceTableBase是SSDT的基地址,NumberOfServices是SSDT中函数的个数。
SSDT就是一张函数索引表,或者说就是一个数组。我们知道,内核函数中有两类非常特殊的函数,ZwXXX函数和NtXXX函数,其中XXX是一样的,在Ring3下他们是一样的,由ntdll.dll导出,而且只是名字不同,地址都是完全一样的,所以在R3下调用ZwXXX函数和NtXXX函数是完全一样的,
等等,不信?还是用dumpbin神器来看看ntdll.dll的导出表把,随便找两个ZwXXX和NtXXX函数,看看他们的地址一样吗。
我们继续,但是在内核,他们是由内核ntoskrnl.exe导出的,而且ZwXXX函数和NtXXX函数是不一样的,其中ZwXXX函数会调用同名的NtXXX函数。而这一个过程是需要查SSDT的,而如果你反汇编ZwXXX函数即可发现,ZwXXX函数只是简单地把索引号存入寄存器,那么我们可以很轻松地通过ZwXXX函数得到我们要Hook函数的索引号,WDK的头文件中包含了大部分内核函数的声明,我们可以直接得到ZwXXX函数的地址,也就是说我们可以很轻松地找到我们需要Hook函数的索引号,为了简化这一个过程,我们可以定义这样的一个宏来通过ZwXXX函数的地址得到在SSDT中的索引号。
OK,我们只需要得到KeServiceDescriptorTable中的SSDT的基地址,加上4*索引号,就是我们需要修改的地址,把这个地址修改成我们函数的地址,就可以拦截所有对这个内核函数的调用。
另外我们需要注意,SSDTHook 在32位系统上可以很好的工作,而在64位系统上,有两个困难:
1。我们编写的驱动无法直接加载,调试说我们可以引导系统时开启系统的“测试模式”,但发布时必须用受信CA颁发的证书数字签名,而且不仅是受信CA,必须是微软指定的几个CA才行,自己颁发一张根证书就算导入了系统的证书ROOT存储区也不行。或者破解系统的签名机制,需要关闭UEFI安全引导,破解bootmgr,winload,ntoskrnl等。
2。就算驱动加载了也不能随便改SSDT,必须过PG才行。
所以要在64位系统上实现hook,如果要求不是很高,在R3下做hoom也是一种很好的选择,具体看我的这篇博文:[Win32] API Hook(2)在64位系统上的实现
好了,这就是SSDTHook的原理了,下一篇博文我会给出一个SSDTHook的实现,最后我希望大家能想一下一个小问题,从而加深对SSDTHook的理解。
我们hook的到底是ZwXXX函数呢?还是hook的NtXXX函数呢?
我更倾向于hook的是NtXXX函数,如果你从Google、百度搜索SSDTHook,那么肯定有人说是hook的ZwXXX,也肯定有人说是NtXXX,那么请思考这几个问题:
1。我们hook后,在驱动程序中调用ZwXXX会被拦截吗?调用NtXXX呢?
2。我们hook的是ZwXXX吗,我们改动了ZwXXX了吗?
嘿嘿,或者说我们hook的就是SSDT,不是ZwXXX,也不是NtXXX。
下一篇将给出一个SSDTHook NtOpenProcess保护进程的实例。
联系客服