挂钩SSDT详解附源代码
源代码下载地址:挂钩SSDT源代码
据微软所言,服务描述符表是一个由四个结构组成的数组,其中的每一个结构都是由四个双字项组成。因此,我们可以将服务描述符表表示为:
typedef struct ServiceDescriptorTable
{
SDE ServiceDescriptor[4];
}SDT;
其中,其中的每一个服务描述符呈现出四个双字的形式,它的结构为:
#pragma pack(1)
typedef struct ServiceDescriptorEntry
{
unsigned int *ServiceTableBase;
unsigned int *ServiceCounterTableBase; //Used only in checked build
unsigned int NumberOfServices;
unsigned char *ParamTableBase;
} SDE,*PSDE;
#pragma pack()
我们寻找的数据结构SSDT是由第一个域所引用的调用表(可以在调试器中用命令dps nt!KiServiceTable查看)
一、 禁用WP位
如果我们能够简单的将值换人并换出SSDT该有多好,然而障碍在于SSDT存在于只读内存中。因此,为了挂钩由SSDT引用的例程,我们一般的策略看起来应该类似于以下形式(以伪代码表示):
DisableReadProtection();
ModifySSDT();
EnableReadProtection();
英特尔的文档写明:“如果CR0.WP=1,访问类型由页目录和页表项的R/W标志位决定。如果CR0.WP=0,超级用户权限允许读写访问。”因此,为破坏SSDT上的写保护,我们需要临时清除写保护(Write Protect,WP)标志。
通过分配自己的MDL来描述SSDT,我们可以禁用读保护。MDL与存储SSDT内容的物理内存页相关联,MDL元素结构在WDK附带的wdm.h头文件中的定义为:
typedef struct _MDL
{
struct _MDL *Next;
CSHORT Size;
CSHORT MdlFlags;
struct _EPROCESS *Process;
PVOID MappedSystemVa;
PVOID StartVa;
ULONG ByteCount;
ULONG ByteOffset;
}MDL,*PMDL;
对于物理内存的这一区域,一旦我们添加了自己的私有描述,就可以使用按位OR以及MDL_MAPPED_TO_SYSTEM_VA宏调整MDL的权限。再一次,我们可以成功实现,因为我们拥有自己的MDL对象。最后,我们使用SSDT在物理内存中的位置与MDL之间的映射正式化。
然后,锁定我们在线性空间创建的MDL缓冲区。作为回报,我们得到一个新的线性地址,它也指向SSDT,而且能够对其进行修改。
简而言之,使用MDL,我们在系统的线性地址空间中创建了新的可写缓冲区,它恰好解析成存储SSDT的物理内存。只要两个区域解析成同样的物理内存区域,它就没什么不同。它只是一个数字游戏,纯粹而简单。如果你不能写入线性内存的给定区域,那么就创建自己的区域并且向其写入。
WP_GLOBALS disableWP_MDL(unsigned int *ssdt,unsigned int nServices)
{
WP_GLOBALS wpGbs;
DbgPrint("[disableWP_MDL] SSDT=%\n",ssdt);
DbgPrint("[disableWP_MDL] nServices=%\n",nServices);
wpGbs.pMDL = MmCreateMdl(NULL, (PVOID)ssdt, (SIZE_T)nServices*4);
if(wpGbs.pMDL==NULL)
{
DbgPrint("[disableWP_MDL] %\n","call to MmCreateMdl failed");
return (wpGbs);
}
MmBuildMdlForNonPagedPool(wpGbs.pMDL);
wpGbs.pMDL->MdlFlags = wpGbs.pMDL->MdlFlags | MDL_MAPPED_TO_SYSTEM_VA;
wpGbs.callTable = (unsigned char*)MmMapLockedPages(wpGbs.pMDL, KernelMode);
if(wpGbs.callTable==NULL)
{
DbgPrint("[disableWP_MDL] %\n","call to MmMapLockedPages failed");
return (wpGbs);
}
return (wpGbs);
}
这个例程返回一个结构,它只是一个指向我们MDL和SSDT指针的包装器。
typedef struct _WP_GLOBALS
{
unsigned char *callTable;
PMDL pMDL;
}WP_GLOBALS;
我们通过前面的函数返回该结构,以便访问可写版本的SSDT,而且当不再需要MDL缓冲区时,我们可以复原事务的原始状态。为复原系统状态,我们使用以下函数:
void enableWP_MDL(PMDL mdlPtr,unsigned char *callTable)
{
if(mdlPtr != NULL)
{
MmUnmapLockedPages((PVOID)callTable, mdlPtr);
IoFreeMdl(mdlPtr);
}
}
二、 挂钩SSDT项
一旦禁用了写保护,我们可以使用以下例程将新的函数地址换人SSDT
unsigned char* hookSSDT(unsigned char *apiCall,unsigned char *newAddr,unsigned int *callTable)
{
PLONG target;
unsigned int indexValue;
indexValue = getSSDTIndex(apiCall);
target = (PLONG)&(callTable[indexValue]);
return ((unsigned char*)InterlockedExchange(target,(LONG)newAddr));
}
该例程接收挂钩例程的地址、现有例程的地址以及指向SSDT的指针,返回现有例程的地址(以便完成任务后恢复SSDT)。
给定某个Nt*()函数,他的地址在SSDT中的什么位置?我们在调试器中使用u nt!Zw*命令查看汇编代码,发现这种函数开始处的汇编代码都是这种格式:mov eax,xxxH,这个xxx就是系统调用的索引号。
unsigned int getSSDTIndex(unsigned char *address)
{
unsigned char *addressOfIndex;
unsigned int indexValue;
addressOfIndex = address + 1;
indexValue = *((PULONG)addressOfIndex);
return (indexValue);
}
一旦有了索引值,定位表项的地址并且将其换出就是一件简单的事情。但是请注意,我们必须使用InterlockedExchange()锁定对此项的访问,以便我们暂时拥有独占的访问权。与IDT或GDT这样基于处理器的结构不同,不论多少个处理器正在运行,只有一个单独的SSDT。
对SSDT中的系统调用脱钩使用同样的基本机制。唯一的不同之处在于我们不向调用例程返回值。
void unHookSSDT(unsigned char *apiCall,unsigned char *oldAddr,unsigned int *callTable)
{
PLONG target;
unsigned int indexValue;
indexValue = getSSDTIndex(apiCall);
target = (PLONG)&(callTable[indexValue]);
InterlockedExchange(target,(LONG)oldAddr);
}
三、 SSDT示例
既然已经分析了组成这首乐曲的各种和弦,让我们将其演奏起来听听看。挂钩ZwTerminateProcess系统调用。
NTSTATUS DriverEntry (
IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
DbgPrint("Load SSDT Driver \n");
DriverObject ->DriverUnload = UnLoad;
wpGlobals = disableWP_MDL(KeServiceDescriptorTable.ServiceTableBase, KeServiceDescriptorTable.NumberOfServices);
if(wpGlobals.pMDL==NULL || wpGlobals.callTable==NULL)
{
return (STATUS_UNSUCCESSFUL);
}
pMDL = wpGlobals.pMDL;
systemCallTable = (PVOID *)wpGlobals.callTable;
Old_ZwTerminateProcess =(ZwTerminateProcessPtr)hookSSDT((unsigned char*)ZwTerminateProcess, (unsigned char*)NewZwTerminateProcess, (unsigned int*)systemCallTable);
return STATUS_SUCCESS;
}
KeServiceDescriptorTable是由ntoskrnl.exe导出的符号。要想访问它,我们必须为声明加上__declspec(dllimport)前缀,以便编译器知道我们在做什么。导出的内核符号为我们提供了内存中一个位置的地址。我们提供的数据类型定义(即typedef struct ServiceDescriptorEntry)把某个复合结构强加于这个地址的内存。通过这种通用方法,你能够修改由操作系统导出的任意变量。
我们将返回值保存到三个全局变量中(pMDL,systemCallTable,Old_ZwTerminateProcess),以便能够脱钩系统调用,并且重新启用写保护。
VOID
UnLoad (
IN PDRIVER_OBJECT DriverObject
)
{
DbgPrint("Unload SSDT Driver \n");
unHookSSDT((unsigned char*)ZwTerminateProcess, (unsigned char*)Old_ZwTerminateProcess, (unsigned int*)systemCallTable);
enableWP_MDL(pMDL,(unsigned char*)systemCallTable);
}
每当ZwTerminateProcess被调用,我们挂钩的函数都会被调用。为了存储实现此接口的现有系统调用的地址,定义以下的函数指针数据类型。
typedef NTSTATUS(*ZwTerminateProcessPtr)(
IN HAndLE ProcessHAndle OPTIONAL,
IN NTSTATUS ExitStatus );
要做的事情只剩下实现挂钩例程。我们通过打印输出字符串跟踪调用,然后调用原始的系统调用。
NTSTATUS NewZwTerminateProcess(
IN HAndLE ProcessHAndle OPTIONAL,
IN NTSTATUS ExitStatus )
{
NTSTATUS ntStatus;
DbgPrint("NewZwTerminateProcess Called \n");
ntStatus = ((ZwTerminateProcessPtr)(Old_ZwTerminateProcess))(ProcessHAndle,ExitStatus);
if(!NT_SUCCESS(ntStatus))
{
DbgPrint("[NewZwTerminateProcess] %\n","Call to Old_ZwTerminateProcess failed");
}
return STATUS_SUCCESS;
}
我们在这个示例中建立的是挂钩SSDT的标准操作程序。不论我们正在拦截哪个例程,挂钩与脱钩的技术细节保持相同。从现在起,无论我们何时想要跟踪或过滤系统调用,必须做的所有工作只有以下几点
- 声明原始系统调用原型(例如ZwTerminateProcess())
- 声明相应的函数指针数据类型(例如ZwTerminateProcessPtr)
- 定义函数指针(例如Old_ZwTerminateProcess)
- 实现挂钩例程(例如NewZwTerminateProcess())
四、加载驱动
我们用INSTDRV加载编译好的驱动程序DCSSSDT.sys
我们用PCHunter32查看挂钩是否成功
我们用Dbgview查看调试信息
没有问题都成功了。
转自 http://www.dcscms.com/article/content.php?seq=13
转载于:https://www.cnblogs.com/mule/p/3812665.html
挂钩SSDT详解附源代码相关推荐
- 蓝牙:CRC原理详解(附crc16校验代码)
CRC原理详解(附crc16校验代码) 参考链接: https://www.cnblogs.com/esestt/archive/2007/08/09/848856.html Cyclic Redun ...
- 《前端》权限链接--vue前端权限控制方案详解附demo_feiyu_may的博客-CSDN博客_vue 前端权限
前端权限控制 - 潘正 - 博客园 https://www.cnblogs.com/guchengnan/p/11800947.html vue前端权限控制方案详解附demo_feiyu_may的博 ...
- Win+TexLive2020+TexStudio安装过程详解附ElsevierLatex模板下载并使用
Win+TexLive2020+TexStudio安装过程详解附ElsevierLatex模板下载并使用 一.下载并安装Texlive2020 1.下载TexLive2020 2.安装过程 解压之后运 ...
- 计算机排名的985大学排名,2019年985大学名单排名,985大学详解(附全榜单)
中国最有名的就是211大学和985大学了.2019年211大学名单排名已经为大家公布了,相比之下985大学更加少,全国只有39所985大学,可见985是比211更加有含金量的学校了.下面排行榜123网 ...
- .user.ini上传详解附CTF例题
.user.ini上传详解附CTF例题 题目 解法 https://buuoj.cn/challenges#[SUCTF%202019]CheckIn [SUCTF 2019]CheckIn 题目 解 ...
- 数学规划详解(附例题及部分Python实现)
数学规划详解(附例题及Python实现) 例题来自于清风老师的数学建模课,个人认为讲的非常好,欢迎大家购买 一.概述 1.1 定义 数学规划是运筹学的一个分支,在约束条件下,按照目标函数来寻求计划管理 ...
- python直线拟合_RANSAC算法详解(附Python拟合直线模型代码)
之前只是简单了解RANSAC模型,知道它是干什么的.然后今天有个课程设计的报告,上去讲了一下RANSAC,感觉这个东西也没那么复杂,所以今天就总结一些RASAC并用Python实现一下直线拟合. RA ...
- java远程_java实现电脑远程控制详解,附完整源代码
Java JDK1.4 的Robot对象,该对象可以完成屏幕图像截取操作,控制鼠标,键盘,如此便可以轻而易举地实现远程服务器的控制.本文向大家介绍如何用Java Robot对象实现远程服务器的控制,并 ...
- Windows7旗舰版磁盘分区详解—附分区步骤截图
最近工作中配置使用联想的Thinkpad TL系列本本.当然原装的系统时刚发布的Windows RTM旗舰版.在考虑买之前也参考了戴尔 苹果的等等, 但个人私下也是一直在用Tinkpad系列, 相比其 ...
- Windows WMIC命令使用详解(附实例)
第一次执行WMIC命令时,Windows首先要安装WMIC,然后显示出WMIC的命令行提示符.在WMIC命令行提示符上,命令以交互的方式执行执行"wmic"命令启动WMIC命令行环 ...
最新文章
- redis集群搭建及设置账户(转)
- Applese 涂颜色(欧拉定理降幂+快速幂)
- 2013\National _C_C++_A\4.约数倍数选卡片
- Android笔记(六十七) 自定义控件
- zblog php 标题优化,Zblog分类页标题重复的优化 - 张力博客
- ubuntu php.ini 配置,ubuntu下配置PHP+JSON模块(apache) | 学步园
- 带领国产数据库走向世界,POLARDB底层逻辑是什么?
- 关于c/s vs web 程序的并发问题
- 对我帮助很大的ESXCLI命令
- Struts2 之 对xwork的理解
- 垂直居中之父元素高度确定的文本
- 日常开发中的几个常用跨域处理方式
- .NET 2.0泛型集合类与.NET 1.1集合类的区别(一)
- 自动跳动滑动门html,jQuery 滑动门自动滑动实现代码
- 16元日薪,从阿里云雇佣一个专家阿里云中小企业AI产品码栈解析
- Socket长连接和短连接的区别
- java类定义初成员变量赋值_Java中成员变量初始化
- 机器视觉怎么和plc通讯
- 海南信用社计算机试题,2021年海南农村信用社计算机笔试内容17
- 微信小程序:页面有内容却不显示原因
热门文章
- linux一个数据页多少,复习——Linux
- bcb quickrep保存为 图片_干货|SCI论文中图片与组图技巧
- python读取properties文件_读取properties文件
- html 给一个无限宽,html – CSS div与其内容一样宽
- 十大排序算法——快速排序法【挖坑法、左右指针法、前后指针法和优化方式三路快排】(C语言)
- python 列表的行 列长度_Python连载|Pandas手册(上)
- OpenCV : 投影变换
- C/C++[1928, ]日期处理
- 阿里云云计算 32 PolarDB的概念
- 易筋SpringBoot 2.1 | 第六篇:JdbcTemplate访问MySQL