一 、SSDT(System Services Descriptor Table),系统服务描述符表。
这个表就是一个把ring3的Win32 API和ring0的内核API联系起来。SSDT并不仅仅只包含一个庞大的地址索引表,它还包含着一些其它有用的信息,诸如地址索引的基地址、服务函数个数等。通过修改此表的函数地址可以对常用windows函数及API进行hook,从而实现对一些关心的系统动作进行过滤、监控的目的。一些HIPS、防毒软件、系统监控、注册表监控软件往往会采用此接口来实现自己的监控模块,
目前极个别病毒确实会采用这种方法来保护自己或者破坏防毒软件,但在这种病毒进入系统前如果防毒软件能够识别并清除它将没有机会发作. SSDT到底是什么呢?打一个比方,SSDT相当于系统内部API的指向标,作用就是告诉系统,需要调用的API在什么地方。二、系统服务分发器(System Service Dispatcher)
Win32子系统和内核模块的关系图:
User32.Dll、Gdi32.Dll、Kernel32.Dll和Advapi32.Dll等用户层DLL是Win32子系统的核心组件,它们提供了Win32子系统API的实现。
所有关于Win32 API实现的DLL到最后都转向了ntdll.Dll,而Ntdll.Dll转向内核模式下的ntoskrnl.Exe。
Ntdll.Dll是一个操作系统的组件,为Native API提供了标准实现。它是Native API在用户层的前端,而真正的Native API实现还是在内核层的ntoskrnl.Exe中。
Windows XP 系统不再使用INT 2E来将CPU特权级从用户层切换到内核层。而是使用快速调用方法。在这种方法中,NTDLL向EAX寄存器中加载被请求服务的系统调用号,向EDX寄存器中加载当前堆栈指针ESP( 用来将用户层的堆栈拷贝到内核层的堆栈中 )。然后,发出Intel指令 SYSENTER 。
NTDLL.DLL中的大部分Natvie API都使用这种办法来切换到内核层。
当NTDLL将系统调用号通过EAX传递给ntoskrnl.Exe后,ntoskrnl中的中断服务例程会将这个调用号作为索引来查询一个特定的表,这个表被称为系统服务描述符表。
// 定义函数指针
typedef NTSTATUS (NTAPI* NTPROC)();
// 函数指针的指针
typedef NTPROC* PNTPROC;
// 定义类型大小
#define NTPROC_ sizeof(NTPROC)
// 系统服务表
typedef struct _SYSTEM_SERVICE_TABLE
{
PNTPROC ServiceTable; // 服务例程数组
PDWORD CounterTable; // 服务例程的个数
DWORD ServiceLimit;
PBYTE ArgumentTable; //参数表,保存用户层传递的每个参数的大小
//用来复制到内核层堆栈时使用
}SYSTEM_SERVICE_TABLE,
*PSYSTEM_SERVICE_TABLE,
**PPSYSTEM_SERVICE_TABLE;
// 系统服务描述符表
typedef struct _SERVICE_DESCRIPTOR_TABLE
{
SYSTEM_SERVICE_TABLE ntoskrnl; // ntoskrnl中的系统服务API
SYSTEM_SERVICE_TABLE win32k; // win32k.Sys中的服务API
SYSTEM_SERVICE_TABLE Table3; // 保留
SYSTEM_SERVICE_TABLE Table4; // 保留
}SYSTEM_DESCRIPTOR_TABLE,
*PSYSTEM_DESCRIPTOR_TABLE,
**PPSYSTEM_DESCRIPTOR_TABLE;
在内核驱动模块中访问该表非常容易,ntoskrnl.Exe到处了一个指针KeServiceDescriptorTable。指向其主服务描述符表
// 告诉链接器变量并不包含在当前模块中,不需要在链接时解析相应的符号名
// 当该模块被加载到进程地址空间后,针对该符号的引用会动态链接到相应模块
extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;
// 与ntoskrnl.Exe建立动态链接
PSERVICE_DESCRIPTOR_TABLE pSDT = KeServiceDescriptorTable;
隐藏在内核模式中INT 2E或者SYSENTER的中断处理例程是KiSystemService。这个函数符号,ntoskrn.Exe并不将其导出。
KiSystemService的执行流程:
1、从当前的线程控制块中检索SDT的指针。
2、将用户模式的函数堆栈复制到内核堆栈中。
3、根据EAX的服务号调用SDT对应索引的例程函数。
4、服务完整后,调用KiServiceExit。
第一步从当前线程控制块中检索SDT的指针,在只考虑ntoskrnl.Exe中的系统调用,不考虑调用Win32k.Sys时,它的值实际上就是KeServiceDescriptorTable的值。在每一个线程被初始化时,将KeServiceDescriptorTable的值写入到线程控制块中。