SSDT的全称是System Services Descriptor Table(系统服务描述符表),在内核中的实际名称是KeServiceDescriptorTable。这个表已通过内核ntoskrnl.exe导出

SSDT用于处理应用层通过kernel32.dll下发的各个API操作请求

ntdll.dll中的API是一个简单的包装函数

kernel32.dll中的API通过ntdll.dll会先完成对参数的检查,再调用一个中断(int 2Eh或者sysenter)实现从R3层进入R0层,并将要调用的服务号(也就是SSDT数组中的索引号index值)存放到寄存器EAX

最后根据存放在EAX中的索引值在SSDT数组中调用指定的服务(Nt*系列函数)

image-20240721184051220

SSDT表的结构定义如下

#pragma pack(1)
typedef struct ServiceDescriptorEntry
{
    unsigned int *ServiceTableBase;//表的基地址
    unsigned int *ServiceCounterTableBase;
    unsigned int NumberOfServices;//表中服务函数的个数
    unsigned char *ParamTableBase;
}ServiceDescriptorTableEntry_t,
*PServiceDescriptorTableEntry_t;
#pragma pack()

其中最重要的两个成员为ServiceTableBase(SSDT表的基地址)和NumberOfServices(表示系统中SSDT服务函数的个数)

SSDT表其实就是一个连续存放函数指针的数组

SSDT表的导入方法

__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;

SSDT表的基地址(数组的首地址)和SSDT函数的索引号(index)从而求出对应的服务服务函数地址

FuncAddr=KeServiceDescriptorable+4*index

(32位)

如果在x64平台上,SSDT中存放的是索引号所对应SSDT函数地址和SSDT表基地址的偏移量x16的值

因此计算公式为

FuncAddr=([KeServiceDescriptorable+index*4]>>4+KeServiceDescriptorable)

因此通用这个公式,只用知道SSDT表的首地址和对应函数的索引号,就可以将对应位置的服务函数替换为自己的函数,从而完成SSDT HOOK过程

Shadow SSDT的原理与此SSDT类似,它对应的表名为KeServiceDescriptorTableShadow是内核中未导出的另一张表

包含Ntoskrnel.exewin32k.sys服务函数

主要处理来自User32.dllGDI32.dll的系统调用

SSDT不同,Shadow SSDT未导出,不能在自己的模块中导入和直接引用

  • 挂钩该表中的NtGdiBitBltNtGdiStretchBlt可以实现截屏保护
  • 挂钩NtUserSetWindowsHookEx可以防止或保护键盘钩子
  • 挂钩与按键相关的函数NtUserSendInput可以防止模拟按键
  • 挂钩NtUserFindWindowEx函数可以防止搜索函数
  • 挂钩与窗口相关的函数NtUserPostMessage,NtUserQueryWindow可以防止窗口被关闭

Shadow SSDT的Hook原理与SSDT HOOK原理一样,但是由于未导出

需要使用不同的方法来获得该表的地址以及服务函数的索引号。

例如,硬编码与KeServiceDescriptorTable在不同系统的位置偏移,搜索KeAddSystemServiceTableKTHREAD.ServiceTable以及有效内存搜索等

KeServiceDescriptorTableShadow实际上也是一个SSDT结构数组

在XP中KeServiceDescriptorTableShadow表位于KeServiceDescriptorTable表上方偏移0x40处

KeServiceDescriptorTableShadow包含4个子结构

第一个子结构是ntoskrnl.exe,与KeServiceDescriptorTable指向相同

第二个子结构(比较重要)即win32k.sys(gdi/user support)的api