SSDT是Ring3层函数调用通往内核层的“大门”,SSDT表里面的每一项是服务函数的函数地址(对于32位windows系统来说是绝对地址,但在64位windows操作系统中的SSDT并不是绝对地址),那么通过这个函数地址定位到服务函数所在的位置,就可以对其进行Inline Hook。
目录
0x00 Hook过程概述
0x01详细的Hook过程
0x02注意
0x03 关于SSDT(System Service Descriptor Table)
1. 获取服务函数地址:
(1)、通过全局变量KeServiceDescriptorTable获得SSDT表的起始地址;
(2)、映射ntdll.dll到ring0空间,获得要Hook的函数的服务索引号;
(3)、根据获得的函数的服务索引号,从SSDT表中获得函数地址;
2. 进行Hook(这里采用的Inline Hook是修改函数前五个字节的方法):
(1)、申请一块五字节大小的内存用来备份原函数的前五个字节,用于驱动卸载的时候恢复原函数;
(2)、申请一块十字节大小的内存用来构造“跳板代码”;
(3)、拷贝原函数的前五个字节到跳板代码的前五个字节处;
(4)、计算由跳板代码处跳转到原函数开始处过五个字节的偏移(记为offset1),将jmp offset1指令写入跳板代码的后五个字节处;
(5)、编写Fake函数,也就是我们需要执行的代码;
(6)、计算原函数开始处跳转到Fake函数开始处的偏移(offset2),将jmp offset2指令写入原函数开始处;
3. 驱动卸载时,
恢复被Hook的函数,也就是将一开始备份的原函数开始处的五个字节拷贝到原函数开始处。
1. 第一步就是定位SSDT表中服务函数的地址:
(1)、通过导出的全局变量KeServiceDescriptorTable就可以定位到SSDT表处
(2)、有了基地址,那么我们要Hook的函数究竟是SSDT表中的第几项呢?一种方法是通过windbg等调试工具静态获得服务函数索引(就是函数在SSDT表中的第几项),但这种方法不具有通用性,所以这里采用动态获得函数索引的方法。其原理是通过映射ntdll.dll模块到ring0内存空间,从它的导出表中获得函数地址,如下图所示,第一条指令mov eax,0BEh,其机器码为b8be000000,所以函数地址处过一个字节后的四字节的内容就是函数索引,也就是这里的000000be。
(3)、获得了函数索引后,我们就可以定位到函数了
2. 定位到函数后,就利用修改函数的前五个字节的方法对其进行inlinehook
(1)、备份原函数的前五个字节
(2)、构造跳板代码,跳板代码的前五个字节是原函数的前五个字节,后五个字节是一个跳转指令,用于跳转回到原函数入口过五个字节处,构造跳板的主要目的是用于执行正确的原函数,即不执行我们的Fake函数。对于跳板代码,前五个字节从原函数开始处拷贝五个字节就行了,而后五个字节是
jmp offset(offset为当前地址处到原函数开始过五个字节处的偏移)也就是E9 xxxxxxxxh。offset的计算过程如下:
构造跳板的代码如下:
(3)、编写Fake函数,Fake函数就是我们设置的需要执行的代码,当调用NtOpenProcess进入该函数的时候,就会跳转到Fake函数处,执行我们的代码。
(4)、计算原函数开始处跳转到Fake函数开始出的偏移(offset2),将jmp offset2指令写入原函数开始处
(5)、至此,对NtOpenProcess函数的SSDT Inline Hook就完后成了。当驱动程序卸载的时候,需要恢复被修改的NtOpenProcess函数的前五个字节:
值得注意的是服务函数的内存是只读,可执行的,但不可写,要想修改服务函数的代码就需要一些措施了。
1. 改变CR0寄存器的第16(WP)位
CR0寄存器在Windows内存管理中发挥着重要的作用,其结构如下图所示:
其中第16位WP(Write Protect)叫做写保护属性位,当该位为1的时候禁止只读内存页的写操作,当该位为0的时候允许写入只读内存页。通过这种方式就可以实现修改服务函数代码的目的了。
2. 通过MDL(Memory Descriptor List)
MDL表示一种内存描述符列表结构,它描述已被页面锁定的用户或内核模式内存。对于MDL的操作系统提供了一系列的接口函数,这里就不再详述MDL的使用了。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课