CTF真(太)好(难)玩(了)

0%

Windows系统调用学习笔记(3)—— 保存现场与系统服务表

从一节的学习,相信大家已经知道了系统调用时,进入内核层时各寄存器的值从哪里来,但是并没有介绍原寄存器的值要存到哪里去,这一节主要就是介绍进入0环前(3环)的所有寄存器的值要存到哪里去

0x01 Trap Frame 结构

无论是通过中断门进入0环,还是通过快速调用进入0环,进入0环前(3环)的所有寄存器的值都会存到这个结构体中。这个结构体本身处于0环,由windows操作系统进行维护。当程序通过中断门从3环进入0环时,ESP指向TrapFrame+0x64的位置。当程序通过快速调用从3环进入0环时,ESP指向TrapFrame+0x78的位置。

在保护模式下,最后四个成员(0x7C0x88)并没有被使用,因此无需考虑;只有在虚拟8086模式下,才会用到当中断门执行时,3环的SS、ESP、EFLAGS、CS、EIP会被存储到结构体的0x680x78中,而执行快速调用时不会

若通过中断门进入0环,在KiSystemService函数开始执行时,3环的SS、ESP、EFLAGS、CS、EIP就已经被存储到 TrapFrame 结构体中了。而TrapFrame 结构体的其它成员通过 KiSystemService 和 KiFastCallEntry 进行赋值。但不管是 KiSystemService 还是 KiFastCallEntry,最终都要执行一部分相同的代码,分为两个函数是因为进入0环时,堆栈里的值不一样,走同一条函数会出问题

0x02 系统服务表

系统服务表(System Service Table,SST),共有两张,第一张表后紧接第二张表,表里的函数都是来自内核文件导出的函数。但它并不包含内核文件导出的所有函数,而仅是3环最常用的内核函数。系统服务表位于 _KTHREAD +00xE0 处,其本质是一个个结构体

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

0x03 系统服务描述符表

系统服务描述符表(System Services Descriptor Table,SSDT),SSDT的每个成员叫做系统服务表(SST)。SSDT的第一个成员是导出的,声明一下即可使用;SSDT的第二个成员是未导出的,需要通过其它方式查找。在Windows中,SSDT的第三个成员和第四个成员未被使用。

在WinDbg中查看已导出成员:

在WinDbg中查看未导出成员:

实验:在SSDT中查找内核函数信息

在之前的实验中,我们通过分析三环的ReadProcessMemory函数,一步步了解三环函数是如何进入内核的。我们应该看到了当ReadProcessMemory即将进入内核时,传递了一个系统服务号,为0BAh。本次实验查找编号 0xBA 在 SSDT 表中的相关信息

查SSDT表,找到函数地址表与参数表:

函数地址表:

[函数地址表 + 系统服务号*4] = 内核函数地址

参数表:

[参数表 + 系统服务号] = 内核函数参数个数(单位:字节)

查看内核函数反汇编:

0x03 参考资料

  1. lzyddf师傅的博客