驱动开发:Win10内核枚举SSDT表基址
三年前面朝黄土背朝天的我,写了一篇如何在Windows 7
系统下枚举内核SSDT
表的文章《驱动开发:内核读取SSDT表基址》
三年过去了我还是个单身狗
,开个玩笑,微软的Windows 10
系统已经覆盖了大多数个人PC终端,以前的方法也该进行迭代更新了,或许在网上你能够找到类似的文章,但我可以百分百肯定都不能用,今天LyShark
将带大家一起分析Win10 x64
最新系统SSDT
表的枚举实现。
看一款闭源ARK工具的枚举效果:
直接步入正题,首先SSDT
表中文为系统服务描述符表,SSDT表的作用
是把应用
层与内核
层联系起来
起到桥梁
的作用,枚举SSDT表
也是反内核
工具最基本的功能,通常在64位
系统中要想找到SSDT
表,需要先找到KeServiceDescriptorTable
这个函数,由于该函数没有被导出,所以只能动态的查找它的地址,庆幸的是我们可以通过查找msr(c0000082)
这个特殊的寄存器来替代查找KeServiceDescriptorTable
这一步,在新版系统中查找SSDT可以归纳为如下这几个步骤。
- rdmsr c0000082 -> KiSystemCall64Shadow -> KiSystemServiceUser -> SSDT
首先第一步通过rdmsr C0000082
MSR寄存器得到KiSystemCall64Shadow
的函数地址,计算KiSystemCall64Shadow
与KiSystemServiceUser
偏移量,如下图所示。
- 得到相对偏移
6ed53180(KiSystemCall64Shadow) - 6ebd2a82(KiSystemServiceUser) = 1806FE
- 也就是说
6ed53180(rdmsr) - 1806FE = KiSystemServiceUser
如上当我们找到了KiSystemServiceUser
的地址以后,在KiSystemServiceUser
向下搜索可找到KiSystemServiceRepeat
里面就是我们要找的SSDT
表基址。
其中fffff8036ef8c880
则是SSDT表
的基地址,紧随其后的fffff8036ef74a80
则是SSSDT表
的基地址。
那么如果将这个过程通过代码的方式来实现,我们还需要使用《驱动开发:内核枚举IoTimer定时器》
中所使用的特征码定位技术,如下我们查找这段特征。
// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com
#include <ntifs.h>
#pragma intrinsic(__readmsr)
ULONGLONG ssdt_address = 0;
// 获取 KeServiceDescriptorTable 首地址
ULONGLONG GetLySharkCOMKeServiceDescriptorTable()
{
// 设置起始位置
PUCHAR StartSearchAddress = (PUCHAR)__readmsr(0xC0000082) - 0x1806FE;
// 设置结束位置
PUCHAR EndSearchAddress = StartSearchAddress + 0x100000;
DbgPrint("[LyShark Search] 扫描起始地址: %p --> 扫描结束地址: %p \n", StartSearchAddress, EndSearchAddress);
PUCHAR ByteCode = NULL;
UCHAR OpCodeA = 0, OpCodeB = 0, OpCodeC = 0;
ULONGLONG addr = 0;
ULONG templong = 0;
for (ByteCode = StartSearchAddress; ByteCode < EndSearchAddress; ByteCode++)
{
// 使用MmIsAddressValid()函数检查地址是否有页面错误
if (MmIsAddressValid(ByteCode) && MmIsAddressValid(ByteCode + 1) && MmIsAddressValid(ByteCode + 2))
{
OpCodeA = *ByteCode;
OpCodeB = *(ByteCode + 1);
OpCodeC = *(ByteCode + 2);
// 对比特征值 寻找 nt!KeServiceDescriptorTable 函数地址
/*
nt!KiSystemServiceRepeat:
fffff803`6ebd2b94 4c8d15e59c3b00 lea r10,[nt!KeServiceDescriptorTable (fffff803`6ef8c880)]
fffff803`6ebd2b9b 4c8d1dde1e3a00 lea r11,[nt!KeServiceDescriptorTableShadow (fffff803`6ef74a80)]
fffff803`6ebd2ba2 f7437880000000 test dword ptr [rbx+78h],80h
fffff803`6ebd2ba9 7413 je nt!KiSystemServiceRepeat+0x2a (fffff803`6ebd2bbe) Branch
*/
if (OpCodeA == 0x4c && OpCodeB == 0x8d && OpCodeC == 0x15)
{
// 获取高位地址fffff802
memcpy(&templong, ByteCode + 3, 4);
// 与低位64da4880地址相加得到完整地址
addr = (ULONGLONG)templong + (ULONGLONG)ByteCode + 7;
return addr;
}
}
}
return 0;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("驱动程序卸载成功! \n"));
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com");
ssdt_address = GetLySharkCOMKeServiceDescriptorTable();
DbgPrint("[LyShark] SSDT = %p \n", ssdt_address);
DriverObject->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
如上代码中所提及的步骤我想不需要再做解释了,这段代码运行后即可输出SSDT表的基址。
如上通过调用GetLySharkCOMKeServiceDescriptorTable()
得到SSDT
地址以后我们就需要对该地址进行解密操作。
得到ServiceTableBase
的地址后,就能得到每个服务函数的地址。但这个表存放的并不是SSDT
函数的完整地址,而是其相对于ServiceTableBase[Index]>>4
的数据,每个数据占四个字节,所以计算指定Index
函数完整地址的公式是;
- 在x86平台上: FuncAddress = KeServiceDescriptorTable + 4 * Index
- 在x64平台上:FuncAddress = [KeServiceDescriptorTable+4*Index]>>4 + KeServiceDescriptorTable
如下汇编代码就是一段解密代码,代码中rcx
寄存器传入SSDT的下标,而rdx
寄存器则是传入SSDT表基址。
48:8BC1 | mov rax,rcx | rcx=index
4C:8D12 | lea r10,qword ptr ds:[rdx] | rdx=ssdt
8BF8 | mov edi,eax |
C1EF 07 | shr edi,7 |
83E7 20 | and edi,20 |
4E:8B1417 | mov r10,qword ptr ds:[rdi+r10] |
4D:631C82 | movsxd r11,dword ptr ds:[r10+rax*4] |
49:8BC3 | mov rax,r11 |
49:C1FB 04 | sar r11,4 |
4D:03D3 | add r10,r11 |
49:8BC2 | mov rax,r10 |
C3 | ret |
有了解密公式以后代码的编写就变得很容易,如下是读取SSDT的完整代码。
// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com
#include <ntifs.h>
#pragma intrinsic(__readmsr)
typedef struct _SYSTEM_SERVICE_TABLE
{
PVOID ServiceTableBase;
PVOID ServiceCounterTableBase;
ULONGLONG NumberOfServices;
PVOID ParamTableBase;
} SYSTEM_SERVICE_TABLE, *PSYSTEM_SERVICE_TABLE;
ULONGLONG ssdt_base_aadress;
PSYSTEM_SERVICE_TABLE KeServiceDescriptorTable;
typedef UINT64(__fastcall *SCFN)(UINT64, UINT64);
SCFN scfn;
// 解密算法
VOID DecodeSSDT()
{
UCHAR strShellCode[36] = "\x48\x8B\xC1\x4C\x8D\x12\x8B\xF8\xC1\xEF\x07\x83\xE7\x20\x4E\x8B\x14\x17\x4D\x63\x1C\x82\x49\x8B\xC3\x49\xC1\xFB\x04\x4D\x03\xD3\x49\x8B\xC2\xC3";
/*
48:8BC1 | mov rax,rcx | rcx=index
4C:8D12 | lea r10,qword ptr ds:[rdx] | rdx=ssdt
8BF8 | mov edi,eax |
C1EF 07 | shr edi,7 |
83E7 20 | and edi,20 |
4E:8B1417 | mov r10,qword ptr ds:[rdi+r10] |
4D:631C82 | movsxd r11,dword ptr ds:[r10+rax*4] |
49:8BC3 | mov rax,r11 |
49:C1FB 04 | sar r11,4 |
4D:03D3 | add r10,r11 |
49:8BC2 | mov rax,r10 |
C3 | ret |
*/
scfn = ExAllocatePool(NonPagedPool, 36);
memcpy(scfn, strShellCode, 36);
}
// 获取 KeServiceDescriptorTable 首地址
ULONGLONG GetKeServiceDescriptorTable()
{
// 设置起始位置
PUCHAR StartSearchAddress = (PUCHAR)__readmsr(0xC0000082) - 0x1806FE;
// 设置结束位置
PUCHAR EndSearchAddress = StartSearchAddress + 0x8192;
DbgPrint("扫描起始地址: %p --> 扫描结束地址: %p \n", StartSearchAddress, EndSearchAddress);
PUCHAR ByteCode = NULL;
UCHAR OpCodeA = 0, OpCodeB = 0, OpCodeC = 0;
ULONGLONG addr = 0;
ULONG templong = 0;
for (ByteCode = StartSearchAddress; ByteCode < EndSearchAddress; ByteCode++)
{
// 使用MmIsAddressValid()函数检查地址是否有页面错误
if (MmIsAddressValid(ByteCode) && MmIsAddressValid(ByteCode + 1) && MmIsAddressValid(ByteCode + 2))
{
OpCodeA = *ByteCode;
OpCodeB = *(ByteCode + 1);
OpCodeC = *(ByteCode + 2);
// 对比特征值 寻找 nt!KeServiceDescriptorTable 函数地址
// LyShark.com
// 4c 8d 15 e5 9e 3b 00 lea r10,[nt!KeServiceDescriptorTable (fffff802`64da4880)]
// 4c 8d 1d de 20 3a 00 lea r11,[nt!KeServiceDescriptorTableShadow (fffff802`64d8ca80)]
if (OpCodeA == 0x4c && OpCodeB == 0x8d && OpCodeC == 0x15)
{
// 获取高位地址fffff802
memcpy(&templong, ByteCode + 3, 4);
// 与低位64da4880地址相加得到完整地址
addr = (ULONGLONG)templong + (ULONGLONG)ByteCode + 7;
return addr;
}
}
}
return 0;
}
// 得到函数相对偏移地址
ULONG GetOffsetAddress(ULONGLONG FuncAddr)
{
ULONG dwtmp = 0;
PULONG ServiceTableBase = NULL;
if (KeServiceDescriptorTable == NULL)
{
KeServiceDescriptorTable = (PSYSTEM_SERVICE_TABLE)GetKeServiceDescriptorTable();
}
ServiceTableBase = (PULONG)KeServiceDescriptorTable->ServiceTableBase;
dwtmp = (ULONG)(FuncAddr - (ULONGLONG)ServiceTableBase);
return dwtmp << 4;
}
// 根据序号得到函数地址
ULONGLONG GetSSDTFunctionAddress(ULONGLONG NtApiIndex)
{
ULONGLONG ret = 0;
if (ssdt_base_aadress == 0)
{
// 得到ssdt基地址
ssdt_base_aadress = GetKeServiceDescriptorTable();
}
if (scfn == NULL)
{
DecodeSSDT();
}
ret = scfn(NtApiIndex, ssdt_base_aadress);
return ret;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint(("驱动程序卸载成功! \n"));
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
DbgPrint("hello lyshark.com \n");
ULONGLONG ssdt_address = GetKeServiceDescriptorTable();
DbgPrint("SSDT基地址 = %p \n", ssdt_address);
// 根据序号得到函数地址
ULONGLONG address = GetSSDTFunctionAddress(51);
DbgPrint("[LyShark] NtOpenFile地址 = %p \n", address);
// 得到相对SSDT的偏移量
DbgPrint("函数相对偏移地址 = %p \n", GetOffsetAddress(address));
DriverObject->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}
运行后即可得到SSDT
下标为51
的函数也就是得到NtOpenFile
的绝对地址和相对地址。
你也可以打开ARK工具,对比一下是否一致,如下图所示,LyShark
的代码是没有任何问题的。
驱动开发:Win10内核枚举SSDT表基址的更多相关文章
- 驱动开发:内核枚举ShadowSSDT基址
在笔者上一篇文章<驱动开发:Win10枚举完整SSDT地址表>实现了针对SSDT表的枚举功能,本章继续实现对SSSDT表的枚举,ShadowSSDT中文名影子系统服务描述表,SSSDT其主 ...
- 驱动开发:内核枚举PspCidTable句柄表
在上一篇文章<驱动开发:内核枚举DpcTimer定时器>中我们通过枚举特征码的方式找到了DPC定时器基址并输出了内核中存在的定时器列表,本章将学习如何通过特征码定位的方式寻找Windows ...
- 驱动开发:内核枚举Registry注册表回调
在笔者上一篇文章<驱动开发:内核枚举LoadImage映像回调>中LyShark教大家实现了枚举系统回调中的LoadImage通知消息,本章将实现对Registry注册表通知消息的枚举,与 ...
- 驱动开发:内核枚举DpcTimer定时器
在笔者上一篇文章<驱动开发:内核枚举IoTimer定时器>中我们通过IoInitializeTimer这个API函数为跳板,向下扫描特征码获取到了IopTimerQueueHead也就是I ...
- 驱动开发:内核枚举LoadImage映像回调
在笔者之前的文章<驱动开发:内核特征码搜索函数封装>中我们封装实现了特征码定位功能,本章将继续使用该功能,本次我们需要枚举内核LoadImage映像回调,在Win64环境下我们可以设置一个 ...
- 驱动开发:内核枚举IoTimer定时器
今天继续分享内核枚举系列知识,这次我们来学习如何通过代码的方式枚举内核IoTimer定时器,内核定时器其实就是在内核中实现的时钟,该定时器的枚举非常简单,因为在IoInitializeTimer初始化 ...
- 驱动开发:内核枚举Minifilter微过滤驱动
Minifilter 是一种文件过滤驱动,该驱动简称为微过滤驱动,相对于传统的sfilter文件过滤驱动来说,微过滤驱动编写时更简单,其不需要考虑底层RIP如何派发且无需要考虑兼容性问题,微过滤驱动使 ...
- 驱动开发:内核监控Register注册表回调
在笔者前一篇文章<驱动开发:内核枚举Registry注册表回调>中实现了对注册表的枚举,本章将实现对注册表的监控,不同于32位系统在64位系统中,微软为我们提供了两个针对注册表的专用内核监 ...
- 驱动开发:内核枚举进程与线程ObCall回调
在笔者上一篇文章<驱动开发:内核枚举Registry注册表回调>中我们通过特征码定位实现了对注册表回调的枚举,本篇文章LyShark将教大家如何枚举系统中的ProcessObCall进程回 ...
随机推荐
- Win10系统下基于Docker构建Appium容器连接Android模拟器Genymotion完成移动端Python自动化测试
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_196 Python自动化,大概也许或者是今年最具热度的话题之一了.七月流火,招聘市场上对于Python自动化的追捧热度仍未消减,那 ...
- 使用python3.7和opencv4.1来实现人脸识别和人脸特征比对以及模型训练
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_126 OpenCV4.1已经发布将近一年了,其人脸识别速度和性能有了一定的提高,这里我们使用opencv来做一个实时活体面部识别的 ...
- MMDetection 使用示例:从入门到出门
前言 最近对目标识别感兴趣,想做一些有趣目标识别项目自己玩耍,本来选择的是 YOLOV5 的,但无奈自己使用 YOLOV5 环境训练模型时,不管训练多少次 mAP 指标总是为 0,而其它 pytorc ...
- (原创)[C#] GDI+ 之鼠标交互:原理、示例、一步步深入、性能优化
一.前言 "GDI+"与"鼠标交互",乍一听好像不可能,也无从下手,但是实现原理比想象中要简单很多. 基于"GDI+"的"交互&q ...
- Apache DolphinScheduler使用规范与使用技巧分享
本次分享来源2021年9月4日杨佳豪同学,给大家带来的分享是基于 Apache DolphinScheduler 使用规范与使用技巧分享,分享的内容主要为以下五点: " DolphinSch ...
- Luogu P1903 [国家集训队]数颜色 / 维护队列 (带修莫队)
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> ...
- 创建Prism项目
1.创建Prism Prism是一个用于WPF.Xamarin Form.Uno平台和 WinUI 中构建松散耦合.可维护和可测试的XAML应用程序框架 通过以下方式访问.使用.学习它: https: ...
- 哔哩哔哩b站提取Cookie方法,bilibili获取Cookie教程
大家可能对Cookie很陌生,甚至不知道是干嘛用,没关系,今天小编详细给大家讲解! Cookie是保存在客户端的纯文本文件,比如txt文件,所谓的客户端就是我们自己的本地电脑,当我们使用自己的电脑通过 ...
- Go语言知识查漏补缺|基本数据类型
前言 学习Go半年之后,我决定重新开始阅读<The Go Programing Language>,对书中涉及重点进行全面讲解,这是Go语言知识查漏补缺系列的文章第二篇,前一篇文章则对应书 ...
- MQ系列4:NameServer 原理解析
MQ系列1:消息中间件执行原理 MQ系列2:消息中间件的技术选型 MQ系列3:RocketMQ 架构分析 1 关于NameServer 上一节的 MQ系列3:RocketMQ 架构分析,我们大致介绍了 ...