Windows系统调用学习笔记(四)—— 系统服务表&SSDT

前言

一、学习自滴水编程达人中级班课程,官网:https://bcdaren.com
二、海东老师牛逼!

要点回顾

API进入0环后调用的函数:

  1. 中断门 – KiSystemService
  2. 快速调用 – KiFastCallEntry

上一篇留了几个练习:

  1. 进0环后,原来的寄存器存在哪里?
  2. 如何根据系统服务号(eax中存储)找到要执行的内核函数?
  3. 调用时参数是存储到3环的堆栈,如何传递给内核函数?
  4. 两种调用方式是如何返回到3环的?

本篇将对第二个和第三个练习进行说明

系统服务表

描述

  1. 系统服务表:System Service Table
  2. 系统服务表共有两张,第一张表后紧接第二张表
  3. 系统服务表里的函数都是来自内核文件导出的函数
  4. 它并不包含内核文件导出的所有函数,而是3环最常用的内核函数
  5. 系统服务表位于 _KTHREAD +00xE0

结构体

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

结构图
系统服务表

实验:分析 KiSystemService 与 KiFastCallEntry 共同代码

IDA反汇编

.text:00406932 loc_406932:                             ; CODE XREF: _KiBBTUnexpectedRange+18↑j
.text:00406932                                         ; _KiSystemService+71↑j
.text:00406932                 mov     edi, eax						; 从 eax 寄存器中取出3环传进来的系统调用号
.text:00406934                 shr     edi, 8						; 系统调用号右移8位
.text:00406937                 and     edi, 30h						; 右移后的值和0x30进行与运算
																	; 目的是检测第12位是否为1
																	; WindowsNT基本的(Native)系统调用有两百多个,编号都小于0x1000
																	; 编号大于0x1000的系统调用号是微软扩展出来的
																	; 这些扩展出的系统调用位于动态安装的模块中,即win32k.sys
																	; 若第12位为0x00,则将低12位作为下标在 ntoskrl.exe 中寻找对应的系统调用
																	; 若第12位为0x10,则将低12位作为下标在 win32k.sys 中寻找对应的系统调用
.text:0040693A                 mov     ecx, edi						; 将运算结果赋值给 ecx
.text:0040693C                 add     edi, [esi+0E0h]				; 将系统服务表的指针赋值给 edi
																	; nt!_KTHREAD
																	;    +0x0e0 ServiceTable     : Ptr32 Void
																	; 这里将系统服务表所在地址直接加上edi的运算结果
																	; 巧妙地得到要查哪张表(两张表是连续的),每张表占16字节
.text:00406942                 mov     ebx, eax						; 将系统调用号赋值给 ebx
.text:00406944                 and     eax, 0FFFh					; 将系统调用号与 0xFFF进行与运算
																	; 目的是保留低12位,作为函数地址的下标
.text:00406949                 cmp     eax, [edi+8]					; typedef struct _SYSTEM_SERVICE_TABLE
																	; {
																	;    PVOID ServiceTableBase;     //这个指向系统服务函数地址表
																	;    PULONG ServiceCounterTableBase;
																	;    ULONG NumberOfService;      //服务函数总数
																	;    ULONG ParamTableBase;       //参数总长度
																	; }SYSTEM_SERVICE_TABLE,*PSYSTEM_SERVICE_TABLE;
																	;
																	; +8 是服务函数的个数
.text:0040694C                 jnb     _KiBBTUnexpectedRange		; 若大于系统调用号的个数则跳转,即系统调用号越界
.text:00406952                 cmp     ecx, 10h						; 将 ecx 与0x10进行比较
																	; ecx 保存的是 edi 与0x30与运算后的结果,只能是0x00货0x10
.text:00406955                 jnz     short loc_406972				; 若系统调用号小于0x1000,则跳转
.text:00406957                 mov     ecx, large fs:18h			; 只有当ecx == 0x10才会向下执行
																	; 作用是动态加载GUI等图形相关函数
.text:0040695E                 xor     ebx, ebx
.text:00406960
.text:00406960 loc_406960:
.text:00406960                 or      ebx, [ecx+0F70h]
.text:00406966                 jz      short loc_406972
.text:00406968                 push    edx
.text:00406969                 push    eax
.text:0040696A                 call    ds:_KeGdiFlushUserBatch
.text:00406970                 pop     eax
.text:00406971                 pop     edx
.text:00406972
.text:00406972 loc_406972:                             ; CODE XREF: _KiFastCallEntry+B6↑j
.text:00406972                                         ; KiSystemServiceAccessTeb()+6↑j
.text:00406972                 inc     large dword ptr fs:638h		; _KPRCB -> +0x518 KeSystemCalls 增加1
.text:00406979                 mov     esi, edx						; edx保存的是3环参数的指针
.text:0040697B                 mov     ebx, [edi+0Ch]				; edi指向要使用的系统服务表
																	; +0Ch是ParamTableBase(参数表指针)
.text:0040697E                 xor     ecx, ecx						;ecx清零
.text:00406980                 mov     cl, [eax+ebx]				; eax保存的是3环传入的系统调用号
																	; ebx保存的是是参数表指针
																	; 这条指令的目的是得到内核函数的参数总长度,存入cl
.text:00406983                 mov     edi, [edi]					; 取出系统调用表的第一个成员(函数地址指针)
.text:00406985                 mov     ebx, [edi+eax*4]				; 函数地址指针 + 系统调用号*4(乘4是因为每个成员占4字节)
																	; 目的是找到函数地址,存入ebx
.text:00406988                 sub     esp, ecx						; 提升堆栈,大小为参数总长度
.text:0040698A                 shr     ecx, 2						; 参数总长度/4 = 参数个数
.text:0040698D                 mov     edi, esp						; 设置要COPY的目的地
.text:0040698F                 test    byte ptr [ebp+72h], 2
.text:00406993                 jnz     short loc_40699B
.text:00406995                 test    byte ptr [ebp+6Ch], 1
.text:00406999                 jz      loc_4069A7
.text:0040699B
.text:0040699B loc_40699B:                             ; CODE XREF: KiSystemServiceAccessTeb()+33↑j
.text:0040699B                 cmp     esi, ds:_MmUserProbeAddress	; 全局变量存储用户能访问的最大范围
																	; 这条指令的作用是判断3环参数是否越界
.text:004069A1                 jnb     loc_406B4F					; 越界则跳转至异常处理
.text:004069A7
.text:004069A7 loc_4069A7:
.text:004069A7                 rep movsd							; 将3环参数复制到0环堆栈
.text:004069A9                 call    ebx							; 调用函数

SSDT

描述

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

在WinDbg中查看已导出成员:

kd>dd KeServiceDescriptorTable

SSDT
在WinDbg中查看未导出成员:
SSDT_Shadow

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

实验说明

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

第一步:查看函数地址

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

第二步:查看参数个数

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

内核函数参数个数

第三步:查看内核函数反汇编

内核函数反汇编

练习

要求:在SSDT表中追加一个函数地址(NtReadVirtualMemory),自己编写API的3环部分调用这个新增的函数(注意:使用2-9-9-12分页)。

答案:略(待补充)

发布了45 篇原创文章 · 获赞 2 · 访问量 1847

猜你喜欢

转载自blog.csdn.net/qq_41988448/article/details/102994374
今日推荐