0%

ring0下的进程保护

SSDT表

SSDT表示系统服务表,我们ring3调用的很多函数都会调用这个SSDT表。这个表就是一个把 Ring3 的 Win32 API 和 Ring0 的内核 API 联系起来。SSDT 并不仅仅只包含一个庞大的地址索引表,它还包含着一些其它有用的信息,诸如地址索引的基地址、服务函数个数等。通过修改此表的函数地址可以对常用 Windows 函数及 API 进行 Hook,从而实现对一些关心的系统动作进行过滤、监控的目的。一些 HIPS、防毒软件、系统监控、注册表监控软件往往会采用此接口来实现自己的监控模块。

1
2
3
4
5
6
7
8
9
10
typedef struct _SERVICE_DESCRIPTOR_TABLE
{
PULONG ServiceTableBase; // 指针,指向函数地址,每个成员占4字节
PULONG ServiceCounterTableBase; // 当前系统服务表被调用的次数
ULONG NumberOfService; // 服务函数的总数
PUCHAR ParamTableBase; // 服务函数的参数总长度,以字节为单位,每个成员占一个字节
// 如:服务函数有两个参数,每个参数占四字节,那么对应参数总长度为8
// 函数地址成员 与 参数总长度成员 一一对应
} SSDTEntry, *PSSDTEntry;

用本地内核调试查看SSDT表,第一个参数指向的地址存储的是全部内核函数

image-20220513101524872

image-20220513101808757

第二个参数代表ssdt表里面有多少个内核函数

image-20220513101659232

第三个参数是一个指针指向一个地址,这里表示的是与上面的内核函数相对应的参数个数,例如第一个为10,参数个数就为0x10/4 = 4

image-20220513101831019

image-20220513101934441

系统中一共存在两个系统服务描述表,KeServiceDescriptorTableKeServiceDescriptorTableShadow,其中 KeServiceDescriptorTable 主要是处理来自 Ring3 层 Kernel32.dll 中的系统调用,而 KeServiceDescriptorTableShadow 则主要处理来自 User32.dll 和 GDI32.dll 中的系统调用,并且KeServiceDescriptorTable 在ntoskrnl.exe(Windows 操作系统内核文件,包括内核和执行体层)是导出的,而 KeServiceDescriptorTableShadow 则是没有被 Windows 操作系统所导出。

image-20220513102906581

CR0寄存器

SSDT表所在的内存页属性是只读,没有写入的权限,所以需要把该地址设置为可写入,这样才能写入自己的函数,使用的是CR0寄存器关闭只读属性,开关在CR0寄存器的第16位。

image-20220513110615404

image-20220513110650432

可以看到这里使用32位寄存器,而在CR0寄存器中,我们重点关注的是3个标志位:

PE ­ 是否启用保护模式,置1则启用。
PG ­ 是否使用分页模式, 置1则开启分页模式, 此标志置1时, PE 标志也必须置1,否则CPU报异常。
WP WP为1时, 不能修改只读的内存页 , WP为0时, 可以修改只读的内存页。

驱动

原作者写了个简单驱动,其主要代码部分如下,就是替换SSDT表中相应函数地址。自己写的函数地址就是将相应进程设为拒绝访问,最后调用原函数。

image-20220513111120329

image-20220513110923377

不过,x64需要绕过PatchGuard保护,困难较大

参考文章:https://drunkmars.top/2022/02/15/ssdt%20hook/