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*系列函数)
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.exe
和win32k.sys
服务函数
主要处理来自User32.dll
和GDI32.dll
的系统调用
与SSDT
不同,Shadow SSDT
未导出,不能在自己的模块中导入和直接引用
- 挂钩该表中的
NtGdiBitBlt
与NtGdiStretchBlt
可以实现截屏保护 - 挂钩
NtUserSetWindowsHookEx
可以防止或保护键盘钩子 - 挂钩与按键相关的函数
NtUserSendInput
可以防止模拟按键 - 挂钩
NtUserFindWindowEx
函数可以防止搜索函数 - 挂钩与窗口相关的函数
NtUserPostMessage
,NtUserQueryWindow
可以防止窗口被关闭
Shadow SSDT
的Hook原理与SSDT HOOK
原理一样,但是由于未导出
需要使用不同的方法来获得该表的地址以及服务函数的索引号。
例如,硬编码与KeServiceDescriptorTable
在不同系统的位置偏移,搜索KeAddSystemServiceTable
与KTHREAD.ServiceTable
以及有效内存搜索等
KeServiceDescriptorTableShadow
实际上也是一个SSDT结构数组
在XP中KeServiceDescriptorTableShadow
表位于KeServiceDescriptorTable
表上方偏移0x40处
KeServiceDescriptorTableShadow
包含4个子结构
第一个子结构是ntoskrnl.exe
,与KeServiceDescriptorTable
指向相同
第二个子结构(比较重要)即win32k.sys(gdi/user support)
的api