SSDT Hook
简单实现一个SSDT进程保护,使得用户无法在任务管理器中终止指定的进程。
文章目录
实验环境
- 驱动开发环境: VMware Workstation Pro 16.2.4 + Windows 7(32位)+ Microsoft Visual Studio 2010 Express + WDK 7600.16385.0
※ 环境搭建参考《Windows Server 2012 +WDK7600.16385.1+VS2010驱动开发环境搭建》
- 驱动加载测试软件:A1SysTest v0.3.0.1
- 系统内核调试信息输出捕获工具:Debugview v4.73
基本原理
-
SSDT 的全称是 System Services Descriptor Table,系统服务描述符表。这个表就是一个把 Ring3 的 Win32 API 和 Ring0 的内核 API 联系起来。如图1所示,SSDT 并不仅仅只包含一个庞大的地址索引表,它还包含着一些其它有用的信息,诸如地址索引的基地址、服务函数个数等。通过修改此表的函数地址可以对常用 Windows 函数及 API 进行 Hook,从而实现对一些关心的系统动作进行过滤、监控的目的。一些 HIPS、防毒软件、系统监控、注册表监控软件往往会采用此接口来实现自己的监控模块。
-
本文要简单实现一个SSDT进程保护,使得用户无法在任务管理器中终止指定的进程,需要关注OpenProcess()这个API。它在Ring3也就是用户层调用,它保存一些信息传入到Ring0内核层后系统实际调用的是ZwOpenProcess函数。在Kernel32模块里面查找OpenProcess函数,可以看到经过2个jmp后进入下一层,找到一个call进入其中便是ZwOpenProcess的调用。
ZwOpenProcess() API函数调用时在进入关键函数之前有一句汇编代码MOV EAX,0xBE,这是用EAX保存一个调用号0xBE。
在调用ZwOpenProcess()时调用号就是0xBE,那么我们如果通过调用号找到了函数地址,把这个地址替换成自定义的钩取函数,这样就能完成SSDT Hook,类似于Ring3的IAT Hook。
-
在Windows内核中设计了两张系统服务描述符表,一张表是上述所说的SSDT,它只保存非用户界面相关的系统服务(例如创建文件、创建进程等);另一张表称为ShadowSSDT,它专门用于保存和用户界面相关的服务(例如创建窗口等),这两张表在内核中都使用了同一个结构体的表示:
但实际上系统共有4个系统服务描述符,其中2个就是上述的2张表,另外2个没有被使用,可能是留着将来备用的:
-
进入Ring0时调用号是EAX传递的,但这个调用号并不只是一个普通的数字作为索引序号,系统会把他用32位数据表示,拆分成19:1:12的格式:
低12位组成一个真正的索引号,第12位表示服务表号,13-31位没有使用。
而进入内核后调用哪一张表,就由调用号中的第12位决定,为0则调用SSDT,为1则调用Shadow SSDT。所以想要对SSDT进行Hook,首先要找到SSDT。如图6,SSDT存在于KTHREAD结构体的偏移为0xbc的一个字段中,可双机调试使用windbg查看,输入dt_kthread,然后再找到偏移为0xbc的字段。
-
找到SSDT后,想要修改SSDT,还需要关闭所在的内存页只读保护属性。因为Windows XP及其以后的系统将一些重要的内存页设置为只读属性,这样就算有权力访问该表也不能随意对其修改,例如SSDT、IDT等。但这种方法很容易被绕过,我们只要修改其属性为可写属性就即可,不过当我们的事情做完后记得把它们恢复为只读属性,不将这些部分修改为然会造成一些很难预料到的后果(比如蓝屏)。
使用CR0控制寄存器关闭只读属性。控制寄存器是一些特殊的寄存器,它们可以控制CPU的一些重要特性。 如图7,CR0寄存器在486的处理器版本之后被加入了“写保护”(Write Protect,WP)位,WP位控制是否允许处理器向标记为只读属性的内存页写入数据。WP位为0,禁用写保护的功能;WP位为1,则开启写保护的功能。因此,只要将cr0的第16位WP位置0就可以禁用写保护,置1则可将其恢复。
实验过程
简单实现一个SSDT进程保护,使得用户无法在任务管理器中终止指定的进程,总体思路如下:
Ⅰ. 驱动加载DriverEntry()和驱动卸载OutLoad()
在DriverEntry()中如果系统成功加载SSDTHook.sys文件,会在系内核中输出调试信息“驱动启动成功!”。
Ⅱ. InstallHook()挂钩和UnistallHook脱钩()
想要对SSDT进行HOOK,首先要找到SSDT。SSDT存在于KTHREAD结构体的偏移为0xbc的一个字段中,使用PsGetCurrentThread()函数可获取当前KTHREAD的首地址。另外,需要注意的是SSDT所在的内存页属性是只读,没有写入的权限,所以需要把该地址设置为可写入,这样才能写入自己的函数。
Ⅲ. ShutPageProtect()关闭页只读保护
使用汇编语言控制CR0寄存器实现ShutPageProtect()关闭页只读保护。__declspec(naked)是用来告诉编译器函数代码的汇编语言为自己的所写,不需要编译器添加任何汇编代码注意,所以一定要记得在开始的时候保存上下文标志位(压栈),在结束的时候要记得恢复上下文(出栈),并且在结尾要加上ret命令。and eax, ~0x10000(and eax 0b01111111111111111)可以使EAX寄存器上的操作数第16位复0,相反or eax, 0x10000(or eax, 0b10000000000000000)可以使EAX寄存器上的操作数第16位置1。
Ⅳ. 自定义钩取函数MyZwOpenProcess()
简单起见,我将要保护的进程PID硬性编码为cmd.exe的PID。自定义钩取函数MyZwOpenProcess()的实现思路类似于IAT Hook中自定义钩取函数的实现思路,通过修改进程参数Desired Access的值为0,然后再将修改的参数传入正常调用的原ZwOpenProcess() API即可实现进程保护。
实现演示
SSDT-Hook演示
参考:
-
《SSDT-HOOK保护进程》,https://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458296439&idx=1&sn=f1339cdb865d5c672a5aafcb1fc44712&chksm=b18190fd86f619eb17f042c3613bf9fcd0c96a0b62aca82ebb07623d0fd213b8f5533e4856cf&scene=27
-
《【SSDT】SSDT hook技术》,https://blog.csdn.net/x1183834753/article/details/126748351
-
《进程隐藏与进程保护(SSDT Hook 实现)(一)》,http://www.cnblogs.com/BoyXiao/archive/2011/09/03/2164574.html
-
《进程隐藏与进程保护(SSDT Hook 实现)(二)》,https://www.cnblogs.com/BoyXiao/archive/2011/09/04/2166596.html
-
《通过修改CR0寄存器绕过SSDT驱动保护》,https://www.lmlphp.com/user/65518/article/item/922657/
-
《逆向工程核心原理》