驱动开发:内核枚举ShadowSSDT基址

驱动,开发,内核,枚举,shadowssdt,基址 · 浏览次数 : 441

小编点评

**生成内容时需要带简单的排版** **具体步骤如下:** 1. 将枚举过程写成一个DLL文件。 2. 在GUI线程中执行这个DLL文件。 3. 在DLL文件中返回一个结果。 4. 将结果写入到一个指定的文件或变量中。 5. 在主线程中读取该文件或变量中的内容。 **示例代码:** **DLL文件(枚举过程):** ```c int EnumProcess(void* Param) { // ...枚举逻辑 ... return 0; } ``` **GUI线程中的代码:** ```c void RunGUIThread() { // ...创建DLL文件 ... // ...加载DLL文件 ... // ...调用DLL文件中的EnumProcess函数 ... // ...释放DLL文件 ... } ``` **主线程中的代码:** ```c void ReadFromDisk() { // ...读取文件或变量 ... // ...写入结果到指定文件或变量 ... } ``` **最终结果:** 该程序将生成一个文件或变量,内容包含枚举结果。 **注意:** * 在GUI线程中执行 DLL文件时,必须确保线程安全。 * 使用DLL文件时,必须确保DLL文件已正确注入到GUI线程中。

正文

在笔者上一篇文章《驱动开发:Win10枚举完整SSDT地址表》实现了针对SSDT表的枚举功能,本章继续实现对SSSDT表的枚举,ShadowSSDT中文名影子系统服务描述表,SSSDT其主要的作用是管理系统中的图形化界面,其Win32子系统的内核实现是Win32k.sys驱动,属于GUI线程的一部分,其自身没有导出表,枚举SSSDT表其与SSDT原理基本一致。

如下是闭源ARK工具的枚举效果:

首先需要找到SSSDT表的位置,通过《驱动开发:Win10内核枚举SSDT表基址》文章中的分析可知,SSSDT就在SSDT的下面,只需要枚举4c8d1dde1e3a00特征即可,如果你找不到上一篇具体分析流程了,那么多半你是看到了转载文章。

先实现第一个功能,得到SSSDT表的基地址以及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;

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.com kd> u KiSystemServiceRepeat
				nt!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],80h
				fffff802`7c1d2ba9 7413            je      nt!KiSystemServiceRepeat+0x2a (fffff802`7c1d2bbe)
				fffff802`7c1d2bab f7437800002000  test    dword ptr [rbx+78h],200000h
				fffff802`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)
			{
				// 获取高位地址fffff802
				memcpy(&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.com \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)
// 署名权
// 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;

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.com kd> u KiSystemServiceRepeat
			nt!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],80h
			fffff802`7c1d2ba9 7413            je      nt!KiSystemServiceRepeat+0x2a (fffff802`7c1d2bbe)
			fffff802`7c1d2bab f7437800002000  test    dword ptr [rbx+78h],200000h
			fffff802`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)
			{
				// 获取高位地址fffff802
				memcpy(&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.com \n");

	KeServiceDescriptorTableShadow = (PSYSTEM_SERVICE_TABLE)GetKeServiceDescriptorTableShadow();

	DbgPrint("[LyShark] SSSDT基地址 = 0x%p \n", KeServiceDescriptorTableShadow);

	ULONGLONG count = GetSSSDTCount();

	DbgPrint("[LyShark] SSSDT个数 = %d \n", count);

	// 循环枚举SSSDT
	for (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)-0x10000000

		ULONGLONG 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进程内执行。

与驱动开发:内核枚举ShadowSSDT基址相似的内容:

驱动开发:内核枚举ShadowSSDT基址

在笔者上一篇文章`《驱动开发:Win10枚举完整SSDT地址表》`实现了针对`SSDT`表的枚举功能,本章继续实现对`SSSDT`表的枚举,ShadowSSDT中文名`影子系统服务描述表`,SSSDT其主要的作用是管理系统中的图形化界面,其`Win32`子系统的内核实现是`Win32k.sys`驱动,属于GUI线程的一部分,其自身没有导出表,枚举`SSSDT`表其与`SSDT`原理基本一致。

驱动开发:内核枚举LoadImage映像回调

在笔者之前的文章`《驱动开发:内核特征码搜索函数封装》`中我们封装实现了特征码定位功能,本章将继续使用该功能,本次我们需要枚举内核`LoadImage`映像回调,在Win64环境下我们可以设置一个`LoadImage`映像加载通告回调,当有新驱动或者DLL被加载时,回调函数就会被调用从而执行我们自己的回调例程,映像回调也存储在数组里,枚举时从数组中读取值之后,需要进行位运算解密得到地址。

驱动开发:内核枚举Registry注册表回调

在笔者上一篇文章`《驱动开发:内核枚举LoadImage映像回调》`中`LyShark`教大家实现了枚举系统回调中的`LoadImage`通知消息,本章将实现对`Registry`注册表通知消息的枚举,与`LoadImage`消息不同`Registry`消息不需要解密只要找到`CallbackListHead`消息回调链表头并解析为`_CM_NOTIFY_ENTRY`结构即可实现枚举。

驱动开发:内核枚举进程与线程ObCall回调

在笔者上一篇文章`《驱动开发:内核枚举Registry注册表回调》`中我们通过特征码定位实现了对注册表回调的枚举,本篇文章`LyShark`将教大家如何枚举系统中的`ProcessObCall`进程回调以及`ThreadObCall`线程回调,之所以放在一起来讲解是因为这两中回调在枚举是都需要使用通用结构体`_OB_CALLBACK`以及`_OBJECT_TYPE`所以放在一起来讲解最好不过。

驱动开发:内核注册并监控对象回调

在笔者上一篇文章`《驱动开发:内核枚举进程与线程ObCall回调》`简单介绍了如何枚举系统中已经存在的`进程与线程`回调,本章`LyShark`将通过对象回调实现对进程线程的`句柄`监控,在内核中提供了`ObRegisterCallbacks`回调,使用这个内核`回调`函数,可注册一个`对象`回调,不过目前该函数`只能`监控进程与线程句柄操作,通过监控进程或线程句柄,可实现保护指定进程线程不被终止

驱动开发:内核监控Register注册表回调

在笔者前一篇文章`《驱动开发:内核枚举Registry注册表回调》`中实现了对注册表的枚举,本章将实现对注册表的监控,不同于32位系统在64位系统中,微软为我们提供了两个针对注册表的专用内核监控函数,通过这两个函数可以在不劫持内核API的前提下实现对注册表增加,删除,创建等事件的有效监控,注册表监视通常会通过`CmRegisterCallback`创建监控事件并传入自己的回调函数,与该创建对应的是

驱动开发:内核监控进程与线程回调

在前面的文章中`LyShark`一直在重复的实现对系统底层模块的枚举,今天我们将展开一个新的话题,内核监控,我们以`监控进程线程`创建为例,在`Win10`系统中监控进程与线程可以使用微软提供给我们的两个新函数来实现,此类函数的原理是创建一个回调事件,当有进程或线程被创建或者注销时,系统会通过回调机制将该进程相关信息优先返回给我们自己的函数待处理结束后再转向系统层。

驱动开发:内核测试模式过DSE签名

微软在`x64`系统中推出了`DSE`保护机制,DSE全称`(Driver Signature Enforcement)`,该保护机制的核心就是任何驱动程序或者是第三方驱动如果想要在正常模式下被加载则必须要经过微软的认证,当驱动程序被加载到内存时会验证签名的正确性,如果签名不正常则系统会拒绝运行驱动,这种机制也被称为驱动强制签名,该机制的作用是保护系统免受恶意软件的破坏,是提高系统安全性的一种手段

驱动开发:内核无痕隐藏自身分析

在笔者前面有一篇文章`《驱动开发:断链隐藏驱动程序自身》`通过摘除驱动的链表实现了断链隐藏自身的目的,但此方法恢复时会触发PG会蓝屏,偶然间在网上找到了一个作者介绍的一种方法,觉得有必要详细分析一下他是如何实现的驱动隐藏的,总体来说作者的思路是最终寻找到`MiProcessLoaderEntry`的入口地址,该函数的作用是将驱动信息加入链表和移除链表,运用这个函数即可动态处理驱动的添加和移除问题。

驱动开发:内核监视LoadImage映像回调

在笔者上一篇文章`《驱动开发:内核注册并监控对象回调》`介绍了如何运用`ObRegisterCallbacks`注册`进程与线程`回调,并通过该回调实现了`拦截`指定进行运行的效果,本章`LyShark`将带大家继续探索一个新的回调注册函数,`PsSetLoadImageNotifyRoutine`常用于注册`LoadImage`映像监视,当有模块被系统加载时则可以第一时间获取到加载模块信息,需要