SSDT表概念详解

SSDT 的全称是 System Services Descriptor Table,系统服务描述符表。

这个表就是一个把 Ring3 的 Win32 API 和 Ring0 的内核 API 联系起来。Ring3下调用的所有函数最终都会先进入到ntdll里面的,比如ReadFile,就会进入ntdll的ZwReadFile

SSDT 并不仅仅只包含一个庞大的地址索引表,它还包含着一些其它有用的信息,诸如地址索引的基地址、服务函数个数等。

1. //系统服务描述符表-在ntoskrnl.exe中导出KeServiceDescriptorTable这个表

2. #pragma pack(1)

3. typedef struct _ServiceDescriptorTable

4. {

5.     //System Service Dispatch Table的基地址

6.     PVOID ServiceTableBase;

7.     //SSDT中每个服务被调用次数的计数器。这个计数器一般由sysenter 更新。

8.     PVOID ServiceCounterTable;

9.     //由 ServiceTableBase 描述的服务数目。

10.     unsigned int NumberOfServices;

11.     //每个系统服务参数字节数表的基地址-系统服务参数表SSPT

12.     PVOID ParamTableBase;

13. }*PServiceDescriptorTable;

14. #pragma pack()

通过修改此表的函数地址可以对常用 Windows 函数及 API 进行 Hook,从而实现对一些关心的系统动作进行过滤、监控的目的。ZwOpenProcess、ZwLoadDriver。一些 HIPS、防毒软件、系统监控、注册表监控软件往往会采用此接口来实现自己的监控模块。

在 NT 4.0 以上的 Windows 操作系统中(windows2000),默认就存在两个系统服务描述表,这两个调度表对应了两类不同的系统服务,这两个调度表为:

SSDT:KeServiceDescriptorTable

ShadowSSDT:KeServiceDescriptorTableShadow

KeServiceDescriptorTable 主要是处理来自 Ring3 层的 Kernel32.dll 中的系统调用

比如函数 OpenProcess、ReadFile 等函数。从kernel32.dll--->ntdll.dll--->进入内核 ntoskrnl.exe(有些机器可能不是这个名字)

KeServiceDescriptorTableShadow 则主要处理来自 User32.dll 和 GDI32.dll 中的系统调用

比如常见的PostMessage、SendMessage、FindWindow,Win32k.sys等。

很多人一定很奇怪,为什么系统中有很多内核文件 ntoskrnl.exe 、ntkrnlpa.exe,简单来说就是他们都是同一套源代码根据编译选项的不同而编译出四个可执行文件,分别用于:

ntoskrnl - 单处理器,不支持PAE(物理地址扩展)

ntkrnlpa - 单处理器,支持PAE

ntkrnlmp - 多处理器,不支持PAE

ntkrpamp - 多处理器,支持PAE

在Vista之前,安装程序会在安装时根据系统的配置选择两个多处理器或者两个单处理器的版本复制到目标系统 system32中。从Vista开始以后,会统一使用多处理器版本,因为多处理器版本运行在单处理器上只是效率稍微低一些。

SSDT表已经导出了,通过ntoskrnl.exe的导出表可以查看到。既然KeServiceDescriptorTable是一个导出的全局变量(数组),那么我们来看wrk,大家都知道在编写代码的时候,要导出一个函数,通常使用def文件。所以ntoskrnl在编写的时候,同样也用到了def来导出导出文件是ntosx86.def,我们翻看wrk:

*********** ntosx86.def-->导出了 KeServiceDescriptorTable CONSTANT ***********

有了上面的介绍后,我们可以简单的将 KeServiceDescriptor 看做是一个数组了(其实质也就是个数组),在应用层 ntdll.dll 中的 API 在这个系统服务描述表(SSDT)中都存在一个与之相对应的服务.

Ntdll ZwReadFile  111h

Ntos mov  eax,       111h

当我们的应用程序调用 ntdll.dll 中的 API 时,最终会调用内核中与之相对应的系统服务,由于有了 SSDT,所以我们只需要告诉内核需要调用的服务所在 SSDT 中的索引就 OK 了,然后内核根据这个索引值就可以在 SSDT 中找到相对应的服务了,然后再由内核调用服务完成应用程序 API 的调用请求即可。

在ntdll下NtQuerySystemInformation和ZwQuerySystemInformation 的开头虽然是nt、zw两套函数,其实是一样的。我们看IDA,我们先看Nt*系列的函数地址:

.text:77F061F8 [email protected]

在ntdll中,zw和nt的两套函数其实他们都是同一个主体:

.text:77F061F8 mov   eax, 105h       ; NtQuerySystemInformation

.text:77F061F8         ; RtlGetNativeSystemInformation

.text:77F061FD mov   edx, 7FFE0300h

.text:77F06202 call    dword ptr [edx]

.text:77F06204 retn    10h

然后再对比图片:

Mode检查 是 usermode 还是kernelmode。

众所周知 Ntdll.dll 中的 API 都只不过是一个简单的包装函数而已,当 Kernel32.dll 中的 API 通过 Ntdll.dll 时(比如:ReadFile --->ZwReadFile),会完成参数的检查,再调用一个中断(int 2Eh 或者 SysEnter 指令),从而实现从 Ring3 进入 Ring0 层,并且将所要调用的服务号(也就是在 SSDT 数组中的索引值)存放到寄存器 EAX 中 mov  eax, 105h(比如看IDA,然后对比xuetr是否一致:结果吻合),并且将参数地址放到指定的寄存器 EDX 中( mov  edx, 7FFE0300h),再将参数复制到内核地址空间中,再根据存放在 EAX 中的索引值来在 SSDT 数组中调用指定的服务。

我们来看内核下这个函数:

windbg的命令 u nt!ZwQuerySystemInformation

nt!ZwQuerySystemInformation:

804ffb1c b8ad000000      mov     eax,0ADh

804ffb21 8d542404          lea     edx,[esp+4]

804ffb25 9c                        pushfd

804ffb26 6a08                   push    8

804ffb28 e854e90300     call    nt!KeReleaseInStackQueuedSpinLockFromDpcLevel+0x95d (8053e481)

804ffb2d c21000               ret     10h

804ffb30 b8ae000000     mov     eax,0AEh

804ffb35 8d542404          lea     edx,[esp+4]

可以看到在 Ring0 下的 ZwQuerySystemInformation 将 105h 放入了寄存器 eax 中,

lkd> u ZwQuerySystemInformation

nt!ZwQuerySystemInformation:

84456c38 b805010000      mov  eax,105h     //将 105h 放入了寄存器eax中

84456c3d 8d542404          lea    edx,[esp+4]

84456c41 9c                         pushfd

84456c42 6a08                    push   8

84456c44 e835140000      call    nt!KiSystemService (8445807e)

84456c49 c21000                 ret     10h

然后调用了系统服务分发函数 KiSystemService,而这个 KiSystemService 函数则是根据 eax 寄存器中的索引值,然后再到SSDT 数组中找到索引值为eax 寄存器中存放的值的那个 SSDT 项,最后就是根据这个 SSDT 项中所存放的系统服务的地址来调用这个系统函数了。比如在这里就是调用 KeServiceDescriptorTable[105h] 处所保存的地址所对应的系统服务了也就是调用 Ring0 下的 NtQuerySystemInformation了。

说明一下内核中 Zw和Nt两套函数的区别

lkd> u ZwQuerySystemInformation

nt!ZwQuerySystemInformation:

84456c38 b805010000      mov    eax,105h //将 105h 放入了寄存器 eax 中

84456c3d 8d542404        lea     edx,[esp+4]

84456c41 9c              pushfd

84456c42 6a08            push    8

84456c44 e835140000      call    nt!KiSystemService (8445807e)

84456c49 c21000          ret     10h

lkd> u NtQuerySystemInformation  l 10

nt!NtQuerySystemInformation:

8464ae3e 8bff             mov    edi,edi

8464ae40 55              push    ebp

8464ae41 8bec            mov     ebp,esp

8464ae43 8b5508          mov     edx,dword ptr [ebp+8]

8464ae46 83fa53          cmp     edx,53h

8464ae49 7f21            jg      nt!NtQuerySystemInformation+0x2e (8464ae6c)

8464ae4b 7440            je      nt!NtQuerySystemInformation+0x4f (8464ae8d)

主体,就是nt系列函数。

所以结论就是:Zw系列函数只是类似一个过渡而Nt系列函数才是真正的执行主体。

至此,在应用层中调用 NtQuerySystemInformation 的全部流程也就结束了 ~

Ring3!ZwQuerySystemInformation 或者 NtQuerySystemInformation

进入内核

Ntos 105h ntos!ZwQuerySystemInformation

接着通过ssdt索引,找到

ntos!NtQuerySystemInformation 执行主体。

说了那么多理论知识,我们windbg来看下SSDT表的结构:

lkd> dd KeServiceDescriptorTable

84583b00  84498d5c 00000000 00000191 844993a4

84498d5c 就是SSDT表的起始地址。

00000191 就是SSDT表的个数 unsigned int NumberOfServices //这个成员就是个数

lkd> dd 84498d5c

84498d5c  84693e78 844db3ad 84623c60 8443f8ba

84498d6c  8469574f 84518306 84705f53 84705f9c

84498d7c  846184af 8471f7c2 84720a17 8460ec87

84498d8c  8469fd8d 846f8ca9 8464bbc0 8461b7c4

84498d9c  845b19ae 846eab84 84602240 84644bcc

84498dac  84691041 845f22bc 8469044e 8460fcfe

84498dbc  846a1814 84612381 846a15f4 84699d4c

84498dcc  846241e8 846e5927 84697119 846a1a46

这些是nt函数的主体:

lkd> u  84693e78

nt!NtAcceptConnectPort:

84693e78 8bff            mov     edi,edi

84693e7a 55              push    ebp

84693e7b 8bec            mov     ebp,esp

84693e7d 64a124010000    mov     eax,dword ptr fs:[00000124h]

84693e83 66ff8884000000   dec     word ptr [eax+84h]

84693e8a 56              push    esi

84693e8b 57              push    edi

84693e8c 6a01            push    1

所谓主体,就是真正的汇编执行代码而不是直接的过渡代码。

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

首地址 84498d5c ring0 服务号 105h

[Address] = SSDT首地址 +  4 * 索引号

ntos!NtQuerySystemInformation = 84498d5c + 4 * 105h = [84499170h]

[84499170h] = 8464ae3eh

lkd> u 8464ae3e

nt!NtQuerySystemInformation:

8464ae3e 8bff            mov     edi,edi

8464ae40 55             push    ebp

8464ae41 8bec            mov     ebp,esp

8464ae43 8b5508          mov     edx,dword ptr [ebp+8]

8464ae46 83fa53          cmp     edx,53h

8464ae49 7f21            jg      nt!NtQuerySystemInformation+0x2e (8464ae6c)

8464ae4b 7440            je      nt!NtQuerySystemInformation+0x4f (8464ae8d)

8464ae4d 83fa08          cmp     edx,8

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

SSDT表的遍历详细代码示例:http://blog.csdn.net/qq1084283172/article/details/41077983

下面也提供一份简单的SSDT表的遍历代码:

#include <ntifs.h>

typedef struct _SERVICE_DESCRIPTOR_TABLE {
	/*
	* Table containing cServices elements of pointers to service handler
	* functions, indexed by service ID.
	*/
	PULONG   ServiceTable;
	/*
	* Table that counts how many times each service is used. This table
	* is only updated in checked builds.
	*/
	PULONG  CounterTable;
	/*
	* Number of services contained in this table.
	*/
	ULONG   TableSize;
	/*
	* Table containing the number of bytes of parameters the handler
	* function takes.
	*/
	PUCHAR  ArgumentTable;
} SERVICE_DESCRIPTOR_TABLE, *PSERVICE_DESCRIPTOR_TABLE;

//ssdt表已经导出了,这里例行公事下
extern PSERVICE_DESCRIPTOR_TABLE KeServiceDescriptorTable;

//卸载函数
VOID DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
	DbgPrint("卸载完成!\n");
}

//入口函数
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath)
{
	int i = 0;

	DriverObject->DriverUnload = DriverUnload;

	for (i=0;i<KeServiceDescriptorTable->TableSize;i++)
	{
		DbgPrint("Number:%d Address:0x%08X\r\n\r\n",i, KeServiceDescriptorTable->ServiceTable[i]);
	}

	return STATUS_SUCCESS;
}

本文文档和代码的下载地址:http://download.csdn.net/detail/qq1084283172/8837431

注释:

学习资料整理于AGP讲课资料,感觉还不错。

图片来源于网上。

时间: 2024-10-14 22:48:16

SSDT表概念详解的相关文章

LINUX 信号概念详解

LINUX 信号概念详解 我们运行如下命令,可看到Linux支持的信号列表: # kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD 18) SIGCONT 19) SIGSTOP

varnish基础概念详解

varnish基础概念详解 比起squid更加轻量级,大致有以下几个特点: ·可以基于内存缓存,也可以在磁盘上缓存,但是就算存放在磁盘上,也不能实现持久缓存 只要进程崩溃,此前缓存统统失效,无论是在内存还是在磁盘,但是现在已经具备持久缓存功能,但是仍然在实验阶段,经常容易崩溃,而且最大大小不能超过1G 如果期望内存大小超过几十个G,比如图片服务器,纯粹使用内存,性能未必好,这时候可以使用磁盘进行缓存,或SSD X 2 做RAID 避免磁盘损坏,在实现随机访问上 ssd硬盘要比机械硬盘要好的多,如

进行概念详解 多线程上篇(二)

操作系统是程序与硬件交互的中间层,现代操作系统将程序的一次执行抽象为进程和线程的概念. 进程作为资源分配的基本单位,线程作为执行的基本单位. 进程和线程其实就是操作系统程序本身实现控制一个程序运行的数据项描述 所有的程序都是面向语言进行开发的,而语言本身是面向操作系统的,线程是操作系统对程序一次运行的抽象 所以,所有的多线程编程模型,必然遵从操作系统的大逻辑,必然是符合操作系统的对线程的抽象概念,操作系统在抽象之上提供了API供应用程序调用 简言之,应用程序的底层是借助于操作系统来完成多线程编程

Linux 程序设计学习笔记----终端及串口编程基础之概念详解

转载请注明出处,谢谢! linux下的终端及串口的相关概念有: tty,控制台,虚拟终端,串口,console(控制台终端)详解 部分内容整理于网络. 终端/控制台 终端和控制台都不是个人电脑的概念,而是多人共用的小型中型大型计算机上的概念. 1.终端 一台主机,连很多终端,终端为主机提供了人机接口,每个人都通过终端使用主机的资源. 终端有字符哑终端和图形终端两种. 控制台是另一种人机接口, 不通过终端与主机相连, 而是通过显示卡-显示器和键盘接口分别与主机相连, 这是人控制主机的第一人机接口.

SQL Server表分区详解

原文:SQL Server表分区详解 什么是表分区 一般情况下,我们建立数据库表时,表数据都存放在一个文件里. 但是如果是分区表的话,表数据就会按照你指定的规则分放到不同的文件里,把一个大的数据文件拆分为多个小文件,还可以把这些小文件放在不同的磁盘下由多个cpu进行处理.这样文件的大小随着拆分而减小,还得到硬件系统的加强,自然对我们操作数据是大大有利的. 所以大数据量的数据表,对分区的需要还是必要的,因为它可以提高select效率,还可以对历史数据经行区分存档等.但是数据量少的数据就不要凑这个热

mysql简单的单表查询详解

mysql简单的单表查询详解 MySQL的查询操作: 单表查询:简单查询 多表查询:连续查询 联合查询: 选择和投影: 投影:挑选要显示的字段 选择:挑选符合条件的行 投影:SELECT 字段1, 字段2, ... FROM tb_name;  SELECT * FROM tb_name; 选择:SELECT 字段1, ... FROM tb_name WHERE 子句; 布尔条件表达式 mysql> CREATE TABLE students (SID INT UNSIGNED AUTO_IN

微赞微擎手动增加模块数据库表结构详解

微赞微擎手动增加模块数据库表结构详解 有时候微擎或微赞的模块没有安装模块的xml文件,那我们先想安装到自己的系统上,要怎么处理呢,下面我们详细的介绍下步骤,个人能力有限,如有不正确之处,敬请谅解~ 1.模块的代码复制 这个就不用多说了吧,当然需要把相应的addons文件夹里的模块复制到自己系统的目录里,不然不要做一下的事情了 2.数据库表结构修改 代码复制过来,如果有相应的xml安装包或者install.php文件,可以直接安装,但是我们这里讲的是没有,那只能把原来要复制的表结构记录复制过来,插

C# 系统应用之注册表使用详解

在平时做项目时,我们有时会遇到注册表的操作,例如前面我们需要获取IE浏览器地址栏的信息.获取"我的电脑"地址栏输入的文件夹信息.USB最近使用信息等.注册表项是注册表的基本组织单位,它包含子表项和值条目.简言之,注册表项相当于注册表里的文件夹.它们存储计算机安装程序的信息,如颜色设置.屏幕大小.历史记录等. 举个以前使用注册表最多的例子:当我们玩魔兽或dota时,总需要打开"运行"->输入"regedit"打开注册表,在HKEY_CURRE

RAID0 RAID1 RAID3 RAID5 RAID6 RAID10 RAID50概念详解

RAID0 RAID1 RAID3 RAID5 RAID6 RAID10 RAID50概念详解摘要:RAID 0又称为Stripe或Striping(条带卷),它代表了所有RAID级别中最高的存储性能.RAID 0提高存储性能的原理是把连续的数据分散到多个磁盘上存取,这样,系统有数据请求就可以被多个磁盘并行的执行,每个磁盘执行属于它自己的那部分数据请求.这种数据上的并行操作可以充分利用总线的带宽,显著提高磁盘整体存取性能RAID 1又称为Mirror或Mirroring(镜像卷),它的宗旨是最大