6.2 Windows驱动开发:内核枚举SSSDT表基址
在Windows内核中,SSSDT(System Service Shadow Descriptor Table)是SSDT(System Service Descriptor Table)的一种变种,其主要用途是提供Windows系统对系统服务调用的阴影拷贝。SSSDT表存储了系统调用的函数地址,类似于SSDT表,但在某些情况下,Windows系统会使用SSSDT表来对系统服务进行引导和调用。
SSSDT表的存在是为了加强系统的安全性和稳定性。通过使用SSSDT表,操作系统可以在运行时检查系统服务的合法性,并确保其不被非法修改。这有助于防止恶意软件或恶意行为修改系统服务地址,提高系统的整体安全性。
在笔者上一篇文章《枚举完整SSDT地址表》
实现了针对SSDT
表的枚举功能,本章继续实现对SSSDT
表的枚举,ShadowSSDT
中文名影子系统服务描述表
,SSSDT其主要的作用是管理系统中的图形化界面,其Win32
子系统的内核实现是Win32k.sys
驱动,属于GUI线程的一部分,其自身没有导出表,枚举SSSDT
表其与SSDT
原理基本一致。
如下是闭源ARK工具的枚举效果:
首先需要找到SSSDT
表的位置,通过《Win10内核枚举SSDT表基址》
文章中的分析可知,SSSDT就在SSDT的下面,只需要枚举4c8d1dde1e3a00
特征即可,如果你找不到上一篇具体分析流程了,那么多半你是看到了转载文章。
先实现第一个功能,得到SSSDT
表的基地址以及SSDT
函数个数,完整代码如下所示。
#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;PSYSTEM_SERVICE_TABLE KeServiceDescriptorTableShadow = 0;
ULONG64 ul64W32pServiceTable = 0;// 获取 KeServiceDescriptorTableShadow 首地址
ULONGLONG GetKeServiceDescriptorTableShadow()
{// 设置起始位置PUCHAR StartSearchAddress = (PUCHAR)__readmsr(0xC0000082) - 0x1808FE;// 设置结束位置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 kd> u KiSystemServiceRepeatnt!KiSystemServiceRepeat:fffff802`7c1d2b94 4c8d15e59c3b00 lea r10,[nt!KeServiceDescriptorTable (fffff802`7c58c880)]fffff802`7c1d2b9b 4c8d1dde1e3a00 lea r11,[nt!KeServiceDescriptorTableShadow (fffff802`7c574a80)]fffff802`7c1d2ba2 f7437880000000 test dword ptr [rbx+78h],80hfffff802`7c1d2ba9 7413 je nt!KiSystemServiceRepeat+0x2a (fffff802`7c1d2bbe)fffff802`7c1d2bab f7437800002000 test dword ptr [rbx+78h],200000hfffff802`7c1d2bb2 7407 je nt!KiSystemServiceRepeat+0x27 (fffff802`7c1d2bbb)fffff802`7c1d2bb4 4c8d1d051f3a00 lea r11,[nt!KeServiceDescriptorTableFilter (fffff802`7c574ac0)]fffff802`7c1d2bbb 4d8bd3 mov r10,r11*/if (OpCodeA == 0x4c && OpCodeB == 0x8d && OpCodeC == 0x1d){// 获取高位地址fffff802memcpy(&templong, ByteCode + 3, 4);// 与低位64da4880地址相加得到完整地址addr = (ULONGLONG)templong + (ULONGLONG)ByteCode + 7;return addr;}}}return 0;
}// 得到SSSDT个数
ULONGLONG GetSSSDTCount()
{PSYSTEM_SERVICE_TABLE pWin32k;ULONGLONG W32pServiceTable;pWin32k = (PSYSTEM_SERVICE_TABLE)((ULONG64)KeServiceDescriptorTableShadow + sizeof(SYSTEM_SERVICE_TABLE));W32pServiceTable = (ULONGLONG)(pWin32k->ServiceTableBase);// DbgPrint("Count => %d \n", pWin32k->NumberOfServices);return pWin32k->NumberOfServices;
}VOID UnDriver(PDRIVER_OBJECT driver)
{DbgPrint(("驱动程序卸载成功! \n"));
}NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{DbgPrint("hello lyshark \n");KeServiceDescriptorTableShadow = (PSYSTEM_SERVICE_TABLE)GetKeServiceDescriptorTableShadow();DbgPrint("[LyShark] SSSDT基地址 = 0x%p \n", KeServiceDescriptorTableShadow);ULONGLONG count = GetSSSDTCount();DbgPrint("[LyShark] SSSDT个数 = %d \n", count);DriverObject->DriverUnload = UnDriver;return STATUS_SUCCESS;
}
这段代码运行后即可得到SSSDT
表基地址,以及该表中函数个数。
在此基础之上增加枚举计算过程即可,完整源代码如下所示。
SSSDT 函数起始index是0x1000
,但W32pServiceTable
是从基址开始记录的,这个误差则需要(index-0x1000)
来得到,至于+4
则是下一个元素与上一个元素的偏移。
计算公式:
- W32pServiceTable + 4 * (index-0x1000)
#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;PSYSTEM_SERVICE_TABLE KeServiceDescriptorTableShadow = 0;
ULONG64 ul64W32pServiceTable = 0;// 获取 KeServiceDescriptorTableShadow 首地址
ULONGLONG GetKeServiceDescriptorTableShadow()
{// 设置起始位置PUCHAR StartSearchAddress = (PUCHAR)__readmsr(0xC0000082) - 0x1808FE;// 设置结束位置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 kd> u KiSystemServiceRepeatnt!KiSystemServiceRepeat:fffff802`7c1d2b94 4c8d15e59c3b00 lea r10,[nt!KeServiceDescriptorTable (fffff802`7c58c880)]fffff802`7c1d2b9b 4c8d1dde1e3a00 lea r11,[nt!KeServiceDescriptorTableShadow (fffff802`7c574a80)]fffff802`7c1d2ba2 f7437880000000 test dword ptr [rbx+78h],80hfffff802`7c1d2ba9 7413 je nt!KiSystemServiceRepeat+0x2a (fffff802`7c1d2bbe)fffff802`7c1d2bab f7437800002000 test dword ptr [rbx+78h],200000hfffff802`7c1d2bb2 7407 je nt!KiSystemServiceRepeat+0x27 (fffff802`7c1d2bbb)fffff802`7c1d2bb4 4c8d1d051f3a00 lea r11,[nt!KeServiceDescriptorTableFilter (fffff802`7c574ac0)]fffff802`7c1d2bbb 4d8bd3 mov r10,r11*/if (OpCodeA == 0x4c && OpCodeB == 0x8d && OpCodeC == 0x1d){// 获取高位地址fffff802memcpy(&templong, ByteCode + 3, 4);// 与低位64da4880地址相加得到完整地址addr = (ULONGLONG)templong + (ULONGLONG)ByteCode + 7;return addr;}}}return 0;
}// 得到SSSDT个数
ULONGLONG GetSSSDTCount()
{PSYSTEM_SERVICE_TABLE pWin32k;ULONGLONG W32pServiceTable;pWin32k = (PSYSTEM_SERVICE_TABLE)((ULONG64)KeServiceDescriptorTableShadow + sizeof(SYSTEM_SERVICE_TABLE));W32pServiceTable = (ULONGLONG)(pWin32k->ServiceTableBase);// DbgPrint("Count => %d \n", pWin32k->NumberOfServices);return pWin32k->NumberOfServices;
}VOID UnDriver(PDRIVER_OBJECT driver)
{DbgPrint(("驱动程序卸载成功! \n"));
}NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{DbgPrint("hello lyshark \n");KeServiceDescriptorTableShadow = (PSYSTEM_SERVICE_TABLE)GetKeServiceDescriptorTableShadow();DbgPrint("[LyShark] SSSDT基地址 = 0x%p \n", KeServiceDescriptorTableShadow);ULONGLONG count = GetSSSDTCount();DbgPrint("[LyShark] SSSDT个数 = %d \n", count);// 循环枚举SSSDTfor (size_t Index = 0; Index < count; Index++){PSYSTEM_SERVICE_TABLE pWin32k;ULONGLONG W32pServiceTable;pWin32k = (PSYSTEM_SERVICE_TABLE)((ULONG64)KeServiceDescriptorTableShadow + sizeof(SYSTEM_SERVICE_TABLE));W32pServiceTable = (ULONGLONG)(pWin32k->ServiceTableBase);// 获取SSSDT地址//ln win32k!W32pServiceTable+((poi(win32k!W32pServiceTable+4*(1-1000))&0x00000000`ffffffff)>>4)-10000000//u win32k!W32pServiceTable+((poi(win32k!W32pServiceTable+4*(Index-0x1000))&0x00000000`ffffffff)>>4)-0x10000000//u poi(win32k!W32pServiceTable+4*(1-0x1000))//u poi(win32k!W32pServiceTable+4*(1-0x1000))&0x00000000`ffffffff//u (poi(win32k!W32pServiceTable+4*(1-0x1000))&0x00000000`ffffffff)>>4//u win32k!W32pServiceTable+((poi(win32k!W32pServiceTable+4*(1-0x1000))&0x00000000`ffffffff)>>4)-0x10000000ULONGLONG qword_temp = 0;LONG dw = 0;// SSSDT 下标从1000开始,而W32pServiceTable是从0开始// + 4 则是每次向下4字节就是下一个地址qword_temp = W32pServiceTable + 4 * (Index - 0x1000);dw = *(PLONG)qword_temp;// dw = qword_temp & 0x00000000ffffffff;dw = dw >> 4;qword_temp = W32pServiceTable + (LONG64)dw;DbgPrint("[LyShark] ID: %d | SSSDT: 0x%p \n", Index, qword_temp);}DriverObject->DriverUnload = UnDriver;return STATUS_SUCCESS;
}
枚举效果如下图所示所示,注意这一步必须要在GUI线程中执行,否则会异常,建议将枚举过程写成DLL文件,注入到explorer.exe
进程内执行;
相关文章:

6.2 Windows驱动开发:内核枚举SSSDT表基址
在Windows内核中,SSSDT(System Service Shadow Descriptor Table)是SSDT(System Service Descriptor Table)的一种变种,其主要用途是提供Windows系统对系统服务调用的阴影拷贝。SSSDT表存储了系统调用的函数…...

实时LCM的ImgPilot搭建部署
ImgPilot是具有实时潜在一致性模型(LCM)功能的图像试点 下载源码 GitHub - leptonai/imgpilot: Image pilot with the power of Real-Time Latent Consistency Modelhttps://github.com/leptonai/imgpilot安装前端web cd imgpilot npm install 安装…...
开源与闭源:大模型未来的发展之争
在当今数字化时代,开源与闭源软件一直是技术界争论的热点话题。随着人工智能技术的快速发展,特别是大模型(如GPT-4等)的广泛应用,这个辩论在大模型技术的背景下变得更加引人注目。本文将探讨开源与闭源的优劣势比较&am…...

linux系统初始化本地git,创建ssh-key
step1, 在linux系统配置你的git信息 sudo apt install -y git//step1 git config --global user.name your_name // github官网注册的用户名 git config --global user.email your_email //gitub官网注册绑定的邮箱 git config --list //可以查看刚才你的配置内容…...
JDBC 操作 SQL Server 时如何传入列表参数
本文是作为将要对 PostgreSQL 的 in, any() 操作的一个铺垫,也是对先前用 JDBC 操作 SQL Server 的温习。以此记录一下用 JDBC 查询 SQL Server 时如何传递一个列表参数。比如想像一下查询语句 select * from users where id in (?) 我们是否能给这里的问题参数传递…...
[算法总结] - 蓄水池采样算法
问题描述 在长度为N的数组中,随机等概率选取K个元素,如何实现这个随机算法。 思路很简单,生成一个[0, N]的随机数index,然后返回index上的数值即可。 但是,如果输入是一个长度未知的数组比如stream,先遍历…...

【Dockerfile】将自己的项目构建成镜像部署运行
目录 1.Dockerfile 2.镜像结构 3.Dockerfile语法 4.构建Java项目 5.基于Java8构建项目 1.Dockerfile 常见的镜像在DockerHub就能找到,但是我们自己写的项目就必须自己构建镜像了。 而要自定义镜像,就必须先了解镜像的结构才行。 2.镜像结构 镜…...

flink和机器学习模型的常用组合方式
背景 flink是一个低延迟高吞吐的系统,每秒处理的数据量高达数百万,而机器模型一般比较笨重,虽然功能强大,但是qps一般都比较低,日常工作中,我们一般是如何把flink和机器学习模型组合起来一起使用呢? fli…...

自动驾驶学习笔记(十二)——定位技术
#Apollo开发者# 学习课程的传送门如下,当您也准备学习自动驾驶时,可以和我一同前往: 《自动驾驶新人之旅》免费课程—> 传送门 《Apollo Beta宣讲和线下沙龙》免费报名—>传送门 文章目录 前言 卫星定位 RTK定位 IMU定位 GNSS定…...

【MySQL系列】PolarDB入门使用
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

第二节HarmonyOS DevEco Studio创建项目以及界面认识
一、创建项目 如果你是首次打开DevEco Studio,那么首先会进入欢迎页。 在欢迎页中单击Create Project,进入项目创建页面。 选择‘Application’,然后选择‘Empty Ability’,单击‘Next’进入工程配置页。 配置页中,详…...

网页设计--第5次课后作业
1、快速学习JavaScript的基本知识第11-14章 JavaScript入门 - 绿叶学习网 2、使用所学的知识完成以下练习。 1)点击 “点亮”按钮 点亮灯泡,点击“熄灭”按钮 熄灭灯泡 2)输入框鼠标聚焦后,展示小写;鼠标离焦后…...

Spring Cache框架,实现了基于注解的缓存功能。
个人简介:Java领域新星创作者;阿里云技术博主、星级博主、专家博主;正在Java学习的路上摸爬滚打,记录学习的过程~ 个人主页:.29.的博客 学习社区:进去逛一逛~ Spring Cache框架 简介Spring Cache 环境准备S…...
CSS-鼠标属性篇
属性名:cursor 功能:设置鼠标光标的样式 属性值: pointer:小手move:移动图标text:文字选择器crosshair:十字架wait:等待help:帮助 eg.html{ cursor: wait;}(此处使用css改…...

Fiddler弱网测试究竟该怎么做?
前言 使用Fiddler对手机App应用进行抓包,可以对App接口进行测试,也可以了解App传输中流量使用及请求响应情况,从而测试数据传输过程中流量使用的是否合理。 抓包过程: 1、Fiddler设置 1)启动Fiddler->Tools->…...

蓝桥杯-平方和(599)
【题目】平方和 【通过测试】代码 import java.util.Scanner; import java.util.ArrayList; import java.util.List; // 1:无需package // 2: 类名必须Main, 不可修改public class Main {public static void main(String[] args) {Scanner scan new Scanner(System.in);//在此…...

从零构建属于自己的GPT系列1:预处理模块(逐行代码解读)、文本tokenizer化
1 训练数据 在本任务的训练数据中,我选择了金庸的15本小说,全部都是txt文件 数据打开后的样子 数据预处理需要做的事情就是使用huggingface的transformers包的tokenizer模块,将文本转化为token 最后生成的文件就是train_novel.pkl文件&a…...
STM32内存介绍
ROM是一种只读存储器,经历了从NOR Flash到NAND Flash再到现在的eMMC的发展。为了便于使用和大批量生产,ROM进一步分为了4种类型:PROM、EPROM、EEPROM和Flash。PROM只能被编程一次,EPROM可擦写可编程且可达1000次,EEPRO…...
Qt::Window 、Qt::Tool是 Qt 框架中的一个窗口标志(Window Flag),用于指定窗口的类型和行为
Qt::Window Qt::Window 是 Qt 框架中的一个窗口标志(Window Flag),用于指定窗口的类型和行为。 在 Qt 中,窗口标志用于控制窗口的外观、行为和交互方式。通过使用不同的窗口标志组合,可以定制窗口的特性,…...

东胜物流软件 SQL注入漏洞复现
0x01 产品简介 东胜物流软件是一款致力于为客户提供IT支撑的 SOP, 帮助客户大幅提高工作效率,降低各个环节潜在风险的物流软件。 0x02 漏洞概述 东胜物流软件 TCodeVoynoAdapter.aspx、/TruckMng/MsWlDriver/GetDataList、/MvcShipping/MsBaseInfo/Sav…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互
物理引擎(Physics Engine) 物理引擎 是一种通过计算机模拟物理规律(如力学、碰撞、重力、流体动力学等)的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互,广泛应用于 游戏开发、动画制作、虚…...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
Robots.txt 文件
什么是robots.txt? robots.txt 是一个位于网站根目录下的文本文件(如:https://example.com/robots.txt),它用于指导网络爬虫(如搜索引擎的蜘蛛程序)如何抓取该网站的内容。这个文件遵循 Robots…...

QT: `long long` 类型转换为 `QString` 2025.6.5
在 Qt 中,将 long long 类型转换为 QString 可以通过以下两种常用方法实现: 方法 1:使用 QString::number() 直接调用 QString 的静态方法 number(),将数值转换为字符串: long long value 1234567890123456789LL; …...

学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...

【数据分析】R版IntelliGenes用于生物标志物发现的可解释机器学习
禁止商业或二改转载,仅供自学使用,侵权必究,如需截取部分内容请后台联系作者! 文章目录 介绍流程步骤1. 输入数据2. 特征选择3. 模型训练4. I-Genes 评分计算5. 输出结果 IntelliGenesR 安装包1. 特征选择2. 模型训练和评估3. I-Genes 评分计…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...

Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...
省略号和可变参数模板
本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...