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 _NtQuerySystemInformation@16
在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讲课资料,感觉还不错。
图片来源于网上。
SSDT表概念详解的更多相关文章
- oracle表分区详解
原文来自:http://www.cnblogs.com/leiOOlei/archive/2012/06/08/2541306.html oracle表分区详解 从以下几个方面来整理关于分区表的概念及 ...
- Oracle表空间详解
Oracle表空间详解 1.表空间的分类 Oracle数据库把表空间分为两类:系统表空间和非系统表空间. 1.1系统表空间指的是数据库系统创建时需要的表空间,这些表空间在数据库创建时自动创建,是每个数 ...
- SQL Server表分区详解
原文:SQL Server表分区详解 什么是表分区 一般情况下,我们建立数据库表时,表数据都存放在一个文件里. 但是如果是分区表的话,表数据就会按照你指定的规则分放到不同的文件里,把一个大的数据文件拆 ...
- [转帖]Windows注册表内容详解
Windows注册表内容详解 来源:http://blog.sina.com.cn/s/blog_4d41e2690100q33v.html 对 windows注册表一知半解 不是很清晰 这里学习一下 ...
- ORACLE结构体系篇之表空间详解.md
表空间详解一.系统表空间SYSTEM 表空间是Oracle 数据库最重要的一个表空间,存放了一些DDL 语言产生的信息以及PL/SQL 包.视图.函数.过程等,称之为数据字典,因此该表空间也具有其特殊 ...
- (转)Mysql 多表查询详解
MySQL 多表查询详解 一.前言 二.示例 三.注意事项 一.前言 上篇讲到mysql中关键字执行的顺序,只涉及了一张表:实际应用大部分情况下,查询语句都会涉及到多张表格 : 1.1 多表连接有 ...
- Windows注册表内容详解
Windows注册表内容详解 http://blog.sina.com.cn/s/blog_4d41e2690100q33v.html (2011-04-05 10:46:17) 第一课 注册表 ...
- JWT基础概念详解
JWT基础概念详解 JWT介绍 之前我们文章讲过分布式session如何存储,其中就讲到过Token.JWT.首先,我们来回顾一下使用Token进行身份认证. 客户端发送登录请求到服务器 服务器在用户 ...
- CSS样式表继承详解
最近在恶补css样式表的基础知识.上次研究了css样式表之冲突问题详解 .这次是对 css 继承 特性的学习. 什么是css 继承?要想了解css样式表的继承,我们先从文档树(HTML DOM)开始. ...
随机推荐
- 面试题-python 如何读取一个大于 10G 的txt文件?
前言 用python 读取一个大于10G 的文件,自己电脑只有8G内存,一运行就报内存溢出:MemoryError python 如何用open函数读取大文件呢? 读取大文件 首先可以自己先制作一个大 ...
- Spring笔记(10) - 日志体系
一.概况 在项目开发当中,日志对于我们开发或运维人员来说,是一个必不可少的工具.在线下我们可以通过 debug 来查找排除问题,但对于线上系统来说,我们只能通过日志分析来查找问题,我们可以通过日志打印 ...
- MySQL基本指令3 和 索引 、分页
1视图: -创建 create view 视图名称 as SQL ps:虚拟 -修改 alter view 视图名称 as SQL -删除 drop view 视图名称 2触发器 3自定义函 ...
- 如何实现一个简易版的 Spring - 如何实现 @Autowired 注解
前言 本文是 如何实现一个简易版的 Spring 系列第四篇,在 上篇 介绍了 @Component 注解的实现,这篇再来看看在使用 Spring 框架开发中常用的 @Autowired 注入要如何实 ...
- 通达OA 页面敏感信息-2013/2015版本
参考 http://wiki.0-sec.org/0day/%E9%80%9A%E8%BE%BEoa/4.html 漏洞影响 2013.2015版本 复现过程 POC: http://0-sec.or ...
- STL之string容器
string string封装了char*,管理这个字符串,是一个char*型的容器. string的相关操作 头文件 #include<string> string构造函数 string ...
- webpack4.x 从零开始配置vue 项目(一)基础搭建项目
序 现在依旧记得第一次看到webpack3.x 版本配置时候的状态 刚开始看到这些真的是一脸懵.希望这篇文章能帮到刚开始入门的同学. webpack 是什么? webpack是一个模块化打包工具,w ...
- Apache JMeter 5.4.1 Build Development
1. 说明 经过漫长的等待终于将开发环境搭建成功了!网络慢真的是伤不起!grade,确实要比maven简洁.....嗯!真香! 2. 工具准备 JDK1.8+ 这... ...
- Java进阶专题(二十七) 将近2万字的Dubbo原理解析,彻底搞懂dubbo (下)
...接上文 服务发现 服务发现流程 整体duubo的服务消费原理 Dubbo 框架做服务消费也分为两大部分 , 第一步通过持有远程服务实例生成Invoker,这个Invoker 在客户端是核心的远程 ...
- DB性能瓶颈分析思路
在性能分析过程中,经常遇到性能瓶颈出现在SQL的情况,此类问题通常可以分为两大类场景,一是SQL自身性能差导致的慢,如索引缺失.索引失效.统计信息不准确.SQL过于复杂等:二是由于外部原因等待导致的S ...