跳转至

0x03. 系统调用

一、系统调用路径

类似于 Linux 下的系统调用,Windows 下由内核向用户态进程提供的服务称为系统服务(system service),所有对系统服务的调用都通过 ntdll.dll 来完成进入内核的过程:

以 Notepad 创建文件为示例的 Windows 系统调用路径

ntdll.dll 中以 Nt 开头的以及以 Zw 开头的函数皆为系统调用,本质上没有差别

NtDeleteFile 这一系统服务为例,若支持 syscall 指令则通过 syscall 指令进入内核,否则通过 int 0x2E 软中断走中断门进入内核:

0x7FFE0000 这块区域为一块内核与用户进程共享的内存(只有内核有写权限),为一个 KUSER_SHARED_DATA 结构体,由内核向用户态提供一些信息

在偏移 0x308 处存放的便是一个布尔值,用来标识该架构是否支持 syscall 指令

Windows 系统调用内核层的入口被设置为 KiSystemCall64() 函数,当进行 syscall 指令/ 0x2E 软中断时,CPU 会切换到 ring0 并执行该函数作为统一的系统调用入口点,该函数定义于 ntoskrnl.exe 中,根据 rax 寄存器指定的系统调用号来调用 SSDT 中对应的内核函数

注:有的时候系统调用入口为 kiSystemCall64Shadow() 函数,不过这个函数最终会跳转到 kiSystemCall64() 中的 KiSystemServiceUser 标签

二、系统服务描述符表

SSDT 基本结构

逆向分析 ntoskrnl.exe 中的 KiSystemCall64() 函数,注意到其会引用到 KeServiceDescriptorTableKeServiceDescriptorTable 这两个变量,这便是系统服务描述符表(System Service Descriptor Table,简称 SSDT ),用来存储不同的系统调用号对应的系统调用函数

其本质上是一个 tag_SERVICE_DESCRIPTOR_TABLE 结构体,为由四张子表组成的 SYSTEM_SERVICE_TABLE 结构体数组

typedef struct tag_SERVICE_DESCRIPTOR_TABLE {
    SYSTEM_SERVICE_TABLE nt;        // KeServiceDescriptorTable 只有这张表
    SYSTEM_SERVICE_TABLE win32k;    // KeServiceDescriptorTableShadow 额外多用一个这张表
    SYSTEM_SERVICE_TABLE sst3;
    SYSTEM_SERVICE_TABLE sst4;
} SERVICE_DESCRIPTOR_TABLE;

SYSTEM_SERVICE_TABLE 结构体定义如下,其中的 ServiceTable 成员为实质上的系统调用表:

typedef struct tag_SYSTEM_SERVICE_TABLE {
    PULONG      ServiceTable;   // 实际的系统调用表
    PULONG      CounterTable;   // 
    ULONG       ServiceLimit;   // 系统调用的数量
    PCHAR       ArgumentTable;  // 参数数量表
} SYSTEM_SERVICE_TABLE;

ServiceTable 成员 实际上为一个指向【目标函数到系统调用表偏移】的数组的指针,每一项为 4 字节,但是实际上计算时会 将该值先右移四位 后再与 系统调用表起始地址 相加获取系统调用实际的地址,即: $$ Addr_{syscall} = Addr_{ServiceTable} + (ServiceTable[syscall_nr] >> 4) $$

由此我们得到 SSDT 的基本结构如下图所示(以 NtOpenFile() 系统调用为例):

自己画的图

在 Windbg 中验证:

这里为什么要进行左右移 4 位的神秘操作有以下原因:

  • 出于安全性考虑,这样使得通过 SSDT 调用到的系统调用范围被限制在 SSDT 附近 4G 偏移处,驱动地址空间之间间隔至少 4GB,而 SSDT 地址又是写死在代码里的,这样可以很好地防止对 SSDT 的劫持,加大攻击难度
  • 存储栈上参数数量,NT kernel 很多系统调用参数都远大于 6 个,因此需要使用栈进行传参,此时这 4 bit 便用于记录不同系统调用使用栈进行传递的参数数量

Shadow SSDT:GUI程序的 SSDT

影子系统服务描述符表(Shadow System Service Descriptor Table,简称 Shadow SSDT )是GUI 程序所专用的一张 SSDT,相比起 SSDT 而言其额外多了一张负责图形相关系统调用的子表 W32pServiceTable ,位于 win32k.sys图形子系统 中:

SSDT 和 Shadow SSDT 第一张子表都是 KiServiceTable ,对应 NT kernel 的常规系统调用

偷的图,注意这里指向 SystemServiceTable 不是指针而是展开

当应用程式在 Windows 下进行系统调用时,线程控制块(KTHREAD )中对应的标志位决定了调用哪一张大表,eax 寄存器的 13 ~ 12 位被用来指定调用哪一张子表,低 12 位则被用来指定表中具体的系统调用:

扩展阅读:KeServiceDescriptorTableFilter

自 Windows 10 开始引入了这张表,当 KTHREAD 中设置了 RestrictedGuiThread 标志位时会用该表替代 Shadow SSDT,不过目前该表相关的资料暂时比较少(🕊)