7.3 HOOK SSDT(系统服务描述表)
7.3.1 SSDT简介
前面介绍了如何编写简单的驱动程序,这节将介绍内核下的一张非常有用的表。在很多游戏保护系统中,或在一些杀毒软件中,都会对该表进行修改,从而改变系统函数调用流程来起到反外挂、反病毒的作用。同样,病毒也在修改该表,从而修改系统函数调用流程来完成其自身的目的。这张非常关键的表叫做SSDT,即System Service Descriptor Table (系统服务描述表)。这张表的作用是把用户层的Win32 API和内核层的API建立一个关联。在该表中维护非常多Native API,或称本地API。下面通过WinDbg来查看一下该表。
依照前面的方法,使用WinDbg连接到虚拟机上,然后在命令提示符处输入dd KeServiceDescriptorTable命令,会得到一些十六进制的输出。KeServiceDescriptorTable是 Ntoskrnl.exe导出的一个指针,用来指向SSDT表的。我们查看一下命令的输出结果:
在该输出中,第一行就是 SSDT表,该表中的 80502b8c是一个函数指针数组,该指针数组保存了所有Native API的函数地址,0000011c是数组的大小,80503000里面保存的是一个参数个数数组,与Native API相对应。将SSDT定义成一个结构体,如下:
要想在驱动中获得该表,需要使用Notokrnl.exe导出的KeServiceDescriptorTable,将其定义如下:
有了上面的SSDT表和KeServiceDescriptorTable的定义,就可以编写与SSDT相关的程序了,不过似乎还少点什么。表里面对应的Native API到底是什么?用WinDbg来看一下,输入dd 80502b8c,输出结果如下:
全都是一些地址值比较接近的函数地址,为什么说是函数地址,因为这是函数指针数组。输入u8059a948命令,输出如下:
从输出可以看出,8059a948是NtAcceptConnectPort()函数的地址。再来看一个地址,输入u 805e7db6命令,输出如下:
这次输出的是NtAccessCheck()函数的反汇编代码。在SSDT表中,第3个参数告诉我们,这个数组的大小是0×11c,也就是数组最后一项的下标是0×11b。再看一下下标为0×11b中保存的地址是多少,输入命令dd 80502b8c + 11b * 4,80502b8c是数组的起始地址,11b是数组下标,那么乘4的是什么原因呢?数组地址的定位是通过数组首地址+下标×数组元素字节数得出的。一个函数的地址占用4个字节,因此要做乘4的操作。该命令输出如下:
再用u命令来查看805c2798处的反汇编代码,输入命令u 805c2798,输出如下:
数组中最后一项保存的是NtQueryPortInformationProcess()函数的地址。
7.3.2 HOOK SSDT
从上一节的介绍中我们知道,SSDT把用户层的Win32 API与内核层的Native API做了一个关联,而整个Native API都保存在SSDT中的一个函数指针数组中,只要修改函数指针数组中的某一项,就相当于HOOK了某个Native API函数。比如,修改SSDT中函数指针数组中的最后一个函数指针,那么就相当于HOOK了 NtQueryPortInformationProcess()函数。
下面 HOOK一个我们比较熟悉的函数,即创建进程函数NtCreateProcessEx()。该函数在指针数组的第 0×30项(该编号根据系统版本的不同而不同,是系统相关的)。通过编程获取 SSDT表,然后找到Native API的函数指针数组,然后修改其中第0×30项的内容为我们的函数地址,为了不影响进程的正常创建,在函数中调用NtCreateProcessEx()函数。代码如下:
在DriverEntry()中,调用了 HookCreateProcess()函数,该函数的作用就是将指针数组中 NtCreateProcessEx()函数的地址替换为 MyNtCreateProcess()函数的地址。而 MyNtCreateProcess()函数是用来取代NtCreateProcess()函数的函数,在我们的函数中调用了一条KdPrint()用于输出的代码。整个Hook的过程非常简单,只要找到指针数组的位置,保存原地址后修改为新的地址即可。在代码中出现了两个函数,分别是UN_PROTECT()和 RE_PROTECT()。这两个函数的作用是,禁止和开启CPU向标志为只读的内存页进行写入的操作。执行了 UN_PROTECT后CPU可以向标志为只读的内存页进行写入操作。当写入完成好后,调用RE_PROTECT()函数恢复到原来的状态。把它放到虚拟机中,打开DebugView然后加载该驱动,加载成功后随便运行一个可执行程序,可以看到在DebugView中显示了在 MyNtCreateProcess()中的输出了,如图7-10所示,说明我们的HOOK成功了。
图7-10 MyCreateProcess()函数的输出
7.3.3 Inline HOOK SSDT
前面介绍了 HOOK SSDT,下面介绍一下Inline HOOK SSDT,为什么要介绍Inline HOOK SSDT呢?在有些情况下,反病毒软件会保护SSDT不受篡改,其保护方式是检查Native API SSDT呢?在有些情况下,反病毒软件会保护SSDT不受篡改,其保护方式是检查Native API的函数指针数组中的值是否正确。如果其中某项或某几项被修改,那么反病毒软件会将被修改的项恢复到原来的值,这样HOOK SSDT就不成功了。还记得前面章节介绍Inline HOOK的原理吗?Inline Hook是修改被HOOK的入口字节为一个跳转,从而跳转入函数中。如果换作是Inline Hook SSDT的话,那么就不会去修改Native API的函数指针数组中的值了,这样可以避免反病毒软件的检测(注:这只是一个简单的说明,现在使用Inline HOOK SSDT同样会被反病毒软件查杀)。
实现Inline Hook SSDT的代码如下:
关于代码的部分就不多介绍了,测试一下,其运行结果如图7-11所示。
图7-11 Inline Hook