当前位置: 首页 > news >正文

通过KiSystemServiceUser获取SSDT基址

通过KiSystemServiceUser获取SSDT基址

技术原理

KeServiceDescriptorTable

在64位Windows中,SSDT被隐藏起来了,但是通过windbg调试发现,nt中导出了KeServiceDescriptorTable,该数组的第一个元素的第一个字段就是SSDT的基址。

下图是KeServiceDescriptorTable数组中元素的类型定义:

下图是KeServiceDescriptorTable数组的定义:

DECLSPEC_CACHEALIGN KSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable[NUMBER_SERVICE_TABLES];DECLSPEC_CACHEALIGN KSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTableShadow[NUMBER_SERVICE_TABLES];

所以我们如果可以找到KeServiceDescriptorTable,就可以找到SSDT。

KiSystemServiceUser

调试发现,KiSystemServiceUser中有这样一段代码:

fffff804`7e8bc3d4 4c8d15e554b400 lea r10,[nt!KeServiceDescriptorTable (fffff804`7f4018c0)] fffff804`7e8bc3db 4c8d1ddeae9000 lea r11,[nt!KeServiceDescriptorTableShadow (fffff804`7f1c72c0)]

那么我们就可以扫描KiSystemServiceUser的字节码,定位到上面的指令,就可以得到KeServiceDescriptorTable的地址了。

问题是,KiSystemServiceUser并不是一个文档化的函数,在ntoskrnl中也没有被导出,如何得到KiSystemServiceUser的地址?

我们需要从一个可以确定读出的地址,加上偏移得到KiSystemServiceUser的地址。

MSR(0xC0000082)

这个MSR就是syscall之后执行的首条指令的地址,可以通过rdmsr指令读出:

0: kd> u fffff8047edbc200 nt!KiSystemCall64Shadow: fffff804`7edbc200 0f01f8 swapgs fffff804`7edbc203 654889242510b00000 mov qword ptr gs:[0B010h],rsp fffff804`7edbc20c 65488b242500b00000 mov rsp,qword ptr gs:[0B000h] fffff804`7edbc215 650fba242518b0000001 bt dword ptr gs:[0B018h],1 fffff804`7edbc21f 7203 jb nt!KiSystemCall64Shadow+0x24 (fffff804`7edbc224) fffff804`7edbc221 0f22dc mov cr3,rsp fffff804`7edbc224 65488b242508b00000 mov rsp,qword ptr gs:[0B008h] fffff804`7edbc22d 6a2b push 2Bh

就是KiSystemCall64Shadow的首地址。然后我们查看KiSystemServiceUser的地址:

0: kd> u KiSystemServiceUser nt!KiSystemServiceUser: fffff804`7e8bc2e2 c645ab02 mov byte ptr [rbp-55h],2 fffff804`7e8bc2e6 c645a801 mov byte ptr [rbp-58h],1 fffff804`7e8bc2ea 65488b1c2588010000 mov rbx,qword ptr gs:[188h] fffff804`7e8bc2f3 c6833202000001 mov byte ptr [rbx+232h],1 fffff804`7e8bc2fa 0f0d8b90000000 prefetchw [rbx+90h] fffff804`7e8bc301 0fae5dac stmxcsr dword ptr [rbp-54h] fffff804`7e8bc305 650fae142580010000 ldmxcsr dword ptr gs:[180h] fffff804`7e8bc30e 4c8945c8 mov qword ptr [rbp-38h],r8

用两者的首地址相减,就得到了KiSystemServiceUser相对于KiSystemCall64Shadow的偏移:

0: kd> ? fffff804`7edbc200 - fffff804`7e8bc2e2 Evaluate expression: 5242654 = 00000000`004fff1e

在利用前面扫描到的加载KeSystemServiceUser的指令,就可以得到SDT,进而得到SSDT。


为什么要这样麻烦?其实还是因为ASLR,每次系统启动后,KiSystemCall64Shadow、KiSystemServiceUser等的地址都会发生变化,所以得这样层层计算得到SSDT地址。

下面看看代码实现。

示例代码

/* fffff804`7e8bc3d4 4c8d15e554b400 lea r10,[nt!KeServiceDescriptorTable (fffff804`7f4018c0)] fffff804`7e8bc3db 4c8d1ddeae9000 lea r11,[nt!KeServiceDescriptorTableShadow (fffff804`7f1c72c0)] */#include<ntifs.h>#include<intrin.h>#include<windef.h>#pragmaintrinsic(__readmsr)constULONGLONG offset=0x4fff1e;staticULONGLONG sg_ullSSDTAddr=0;// 获取nt!KeServiceDescriptorTableULONGLONGGetSDTAddr(){ULONGLONG addr=0;ULONGLONG startAddr=__readmsr(0xC0000082)-offset;// 搜索起始地址,就是KiSystemServiceUser开始的地址ULONGLONG endAddr=startAddr+0x1000;// 搜索结束地址UCHAR c1,c2,c3;ULONGLONG ullSDTOffset=0;for(PUCHAR cursor=(PUCHAR)startAddr;cursor<(PUCHAR)endAddr;cursor++){if(MmIsAddressValid(cursor)&&MmIsAddressValid(cursor+1)&&MmIsAddressValid(cursor+2)){c1=*cursor;c2=*(cursor+1);c3=*(cursor+2);if(c1==0x4c&&c2==0x8d&&c3==0x15){memcpy_s(&ullSDTOffset,sizeof(ULONGLONG),cursor+3,4);addr=ullSDTOffset+(ULONGLONG)cursor+7;// 相对寻址指令的偏移量是相对于下一条指令的地址计算的}}}returnaddr;}ULONGLONGGetSSDTFuncAddr(DWORD dwIndex){DWORD dwOffset=((DWORD*)sg_ullSSDTAddr)[dwIndex*4];ULONGLONG ullFuncAddr=sg_ullSSDTAddr+(dwOffset>>4);returnullFuncAddr;}VOIDDriverUnload(PDRIVER_OBJECT pDriverObj){UNREFERENCED_PARAMETER(pDriverObj);}EXTERN_C NTSTATUSDriverEntry(PDRIVER_OBJECT pDriverObj,PUNICODE_STRING pRegPath){UNREFERENCED_PARAMETER(pRegPath);pDriverObj->DriverUnload=DriverUnload;ULONGLONG sdtBase=GetSDTAddr();DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_INFO_LEVEL,"SDT Address: %p",(PVOID)sdtBase);sg_ullSSDTAddr=*((ULONGLONG*)sdtBase);DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_INFO_LEVEL,"SSDT Address: %p",(PVOID)sg_ullSSDTAddr);ULONGLONG ullFuncAddr=GetSSDTFuncAddr(0);DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_INFO_LEVEL,"nt!NtAccessCheck Address: %p",(PVOID)ullFuncAddr);returnSTATUS_SUCCESS;}

计算SSDT中函数的实际地址

在 x64 Windows 系统中,从 SDT(KeServiceDescriptorTable)获取的 SSDT 地址(ServiceTableBase)指向的不是直接的函数指针数组,而是一个经过编码的 32 位偏移数组。因此需要“解密”才能得到真实的函数地址。这是 x64 与 x86 在 SSDT 实现上的关键区别。

根本原因:x64 地址是 64 位(8 字节),直接存储会占用双倍内存,且影响缓存效率。Windows 内核采用了一种压缩存储方案:

  • 将 64 位函数地址转换为 32 位有符号偏移(相对于 SSDT 基地址)。

  • 存储时进行 4 位右移编码(即 偏移 = (实际地址 - SSDT基地址) << 4)。

  • 读取时反向操作:实际地址 = SSDT基地址 + (偏移 >> 4)。

所以计算函数真实地址的公式是:

在x64平台上:FuncAddress = [KeServiceDescriptorTable+4*Index]>>4 + KeServiceDescriptorTable

更小的 SSDT 表能更好利用 CPU 缓存,加速系统调用查找。偏移相对于 SSDT 基地址计算,与 KASLR(内核地址空间布局随机化)​ 兼容,即使内核基址随机化,相对偏移保持不变。

http://www.rkmt.cn/news/1482782.html

相关文章:

  • 寄快递省钱别乱点!2026高性价比渠道实测推荐 - 快递物流资讯
  • 村长团队GTA5 EUP服装模组从零搭建教程SP单机 + FiveM
  • HarmonyOS 天气服务:让你的应用轻松获取天气数据
  • 2026/6/7
  • EBGaramond12字体完整指南:专业排版与学术引用的完美解决方案
  • 非戈替尼200mg每日治类风湿关节炎,上呼吸道感染及带状疱疹常见
  • 【发动机】基于matlab模拟火花-点火发动机循环采用单区模型和Wiebe热释放定律求解进气压力、排气温度和燃烧分数
  • 别再混编了!用Halcon引擎(.hdvp)重构你的C#机器视觉项目,内存泄漏拜拜
  • 从1小时3次到无限次:12款转换工具免费策略实测(3款顶配免费方案详解) - 时时资讯
  • 每日算法快闪赛技术文章大纲
  • PlantUML类图:用代码思维讲清楚UML六大关系(含Java语法对照与记忆口诀)
  • 2026格雷斯代理商合作参考:行业服务与技术支持解析 - 品牌排行榜
  • ㉖ 总结篇:AI副业全景图与行动路线
  • Java线程学习心得
  • TapinRadio Pro(全球电台收音机)
  • 【单相交流电压控制器】模拟带有两个背靠背连接的晶闸管的单相交流电压控制器附Simulink仿真
  • 南充第三方CMACNAS甲醛检测治理口碑名单:清诚CMA检测中心等5家深度测评 - aZJ-111
  • Windows下C++程序崩溃:Critical error c0000374的三种触发时机与实战排查指南
  • CSDN AI营销文案百度首页命中率仅11.7%(实测217篇),而加入这1个权威信源锚点后飙升至83.6%
  • 3步掌握Adobe-GenP:设计师必备的Adobe全家桶激活完整攻略
  • 如何高效配置Zotero GPT插件:3步搭建智能文献助手
  • 南京CMA甲醛检测治理口碑名单:国康CMA检测中心等5家深度测评 - aZJ-111
  • Video2X终极指南:如何免费将低清视频无损放大到4K画质
  • 开箱即用的Python+Selenium+Firefox自动化测试环境(含geckodriver)
  • 手把手跑通扩散模型:S型曲线动态演示Notebook(纯CPU可运行)
  • 汕头甲醛检测治理除甲醛公司口碑名单:康之居等5家深度测评 - AZJ888
  • 基于鱼鹰优化算法(OOA)优化CNN-BiGUR-Attention风电功率预测研究附Matlab代码
  • 金华CMA甲醛检测治理口碑名单:国康CMA检测中心等5家深度测评 - aZJ-111
  • 书匠策AI官网www.shujiangce.com|别再熬夜肝期刊了!
  • 浙江GEO优化五大品牌2025-2026实测报告:AI防信任断层+适用场景+价格决策全指南 - 玖叁鹿