SSDT(系统服务描述符表 system services descriptor table)

SSDT表介绍

ntdll.dll模块中的函数有些以nt或zw开头的函数为了完成相应的功能需要进入内核,调用内核中以nt开头的函数来完成相应的功能。ntdll.dll里的函数在进入内核层之前首先将系统服务号传入eax寄存器中,然后调用KiSystemService函数进入内核层。进入内核后会根据eax值索引ssdt表里的函数进行执行相应地址的函数。

SSDT的每一项是一个系统服务函数的地址,可以通过HOOK这些函数完成特定的功能。

32位系统上SSDT是导出的,64位是不会导出的。

通过PCHunter查看win7 x64系统的SSDT表:

如何获得SSDT表的地址和每一个项对应的服务名称呢?

注意:内核文件有多个,操作系统会根据当前cpu和分页方式选择不同的内核文件。

ntoskrnl.exe - 单处理器,不支持PAE分页模式;

ntkrnlpa.exe - 单处理器,支持PAE分页模式;

ntkrnlmp.exe - 多处理器,不支持PAE分页模式;

ntkrpamp.exe - 多处理器,支持PAE分页模式。

32位系统

32系统上ntdll.dll使用mov eax,xxx传入索引值,可以通过遍历ntdll.dll查看每一个函数对应的服务号,从而找到服务函数名和服务编号的关系。另外,内核中32位的SSDT的起始地址是直接在ntoskrnl.exe中通过KeServiceDescriptorTable符号导出,不需要使用工具来获得,可以直接在驱动程序中引用该符号的地址。注意:在代码实现上应当引入头文件#include <ntimage.h>之后使用语句

extern SSDTEntry __declspec(dllimport) KeServiceDescriptorTable;

来获得KeServiceDescriptorTable的地址。

32位系统中KeServiceDescriptorTable结构如下图所示

#pragma pack(1)
typedef struct _SERVICE_DESCRIPTOR_TABLE
{
    PULONG ServiceTableBase;//SSDT的起始地址
    PULONG ServiceCounterTableBase;//
    ULONG  NumberOfService;//SSDT表中服务函数的总数
    PUCHAR ParamTableBase;//服务函数的参数个数数组的起始地址,数组的每一个成员占1字节,记录的值是对应函数的参数个数*4
} SSDTEntry, *PSSDTEntry;
#pragma pack()

ServiceTableBase的内容是SSDT表的起始地址,然后从ServiceTableBase开始是一个长度为NumberOfService的指针数组,每一项是4个字节,是SSDT表中每一个服务的函数地址。

在内核调试器windbg中使用dd KeServiceDescriptorTable命令查看KeServiceDescriptorTable数据,就可以看到SSDTEntry结构的每一项数据。

64位系统

64位系统上ntdll.dll使用 mov r10,rcx;mov eax,xxx传入索引值,可以通过遍历64位的ntdll.dll查看每一个函数对应的服务号。从而找到服务函数名和服务编号的关系。

如下图所示,使用IDA查看windows 7 x64的64位ntdll.dll的函数

通过SSDT表可以看出二者却是相互对应(这是因为,系统就是通过ntdll.dll来逆推出SSDT表的每一项对应的函数名的)

但是,由于64位的内核文件并未导出KeServiceDescriptorTable的信息,所以64位系统的SSDT表的起始地址无法直接获得。但是却同样可以在windbg中使用dd KeServiceDescriptorTable命令查看KeServiceDescriptorTable内容:

但是,我们却无法使用某种方式直接获得KeServiceDescriptorTable的地址,于是常采用间接方式。

使用windbg观察nt!KiSystemServiceRepeat函数的反汇编如下

可以发现,x64系统会在KiSystemServiceRepeat函数使用lea r10,[nt!KeServiceDescriptorTable]指令获得KeServiceDescriptorTable的地址,因此只要在KiSystemServiceRepeat函数内搜索4c 8d 15 得到这条指令的起始地址。

KeServiceDescriptorTable地址保存在这条指令之后的地址即fffff800`03e98779+指令操作数(是偏移量)得到的新地址中(指令反汇编的知识,用四个字节作为偏移量),即

pKeServiceDescriptorTable= fffff800`03e98779+2320c7= FFFFF800`040CA840

通过windbg验证KeServiceDescriptorTable的地址

而KeServiceDescriptorTable结构如下:

#pragma pack(1)
typedef struct _SERVICE_DESCIPTOR_TABLE
{
	PULONG ServiceTableBase;		// SSDT基址,8字节大小
	PVOID ServiceCounterTableBase;	// SSDT中服务被调用次数计数器,8字节大小
	ULONGLONG NumberOfService;		// SSDT服务函数的个数,8字节大小
	PVOID ParamTableBase;			// 系统服务参数表基址,8字节大小。实际指向的数组是以字节为单位的记录着对应服务函数的参数个数
}SSDTEntry, *PSSDTEntry;
#pragma pack()

通过以上信息,可以看出SSDT的首地址是fffff800`03e9a300,SSDT中以每4个字节为单位描述一个服务的地址项信息,不过其内容并非实际的地址,因为4个字节无法保存64位下的地址,实际上其内容左移4位得到的是地址相对偏移量,是真实服务地址相对SSDT起始地址的偏移量。下面的内容可以帮助理解真实服务地址的计算过程。

通过windbg查看fffff800`03e9a300的数据

通过以上信息,验证第一项和第二个项指向的地址,SSDT的首地址是fffff800`03e9a300:

fffff800`03e9a300+040d9a00>>4= fffff800`03e9a300+040d9a0= FFFFF800`042A7C0

fffff800`03e9a300+02f55c00>>4= fffff800`03e9a300+02f55c0= FFFFF800`0418F8C0

很明显这与PCHunter显示的一致

上面的操作需要直到KiSystemServiceRepeat地址,然而KiSystemServiceRepeat函数也没有导出。所以无法知道其地址。

如何获得64位Windows系统的SSDT表的基址

方式一:硬编码,对每一个系统的不同版本,分别使用windbg工具获得KiSystemServiceRepeat函数的地址。

方式二:读取msr寄存器(特别模块寄存器)的0xc0000082的值,即在微软的C语言中使用__readmsr(0xc0000082)获得KiSystemCall64函数的地址。

pKiSystemCall64=(PVOID)__readmsr(0xc0000082);

获得地址后遍历该地址后的数据会经过KiSystemServiceRepeat函数,就可以通过特征码4c 8d 15找到目标指令。

具体计算公式是:

特征码起始地址是pKiSystemCall64+i则(i表示相对KiSystemCall64函数开始处偏移i个字节)

pKeServiceDescriptorTable=(PVOID)(pKiSystemCall64+i+7+*(PLONG)( pKiSystemCall64+i+3));

pSSDT=*(PLONG)pKeServiceDescriptorTable

注意:上述代码中数字7表示lea r10,[xxx]指令的长度是7个字节

方式三:解析本操作系统下的内核文件的符号文件。

转载于:https://www.cnblogs.com/wf751620780/p/10460863.html

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值