5.ring0-SSDT-SSTDSHADOW原理分析、遍历随手代码

SSDT 的全称是 System Services Descriptor Table,系统服务描述符表
这个表就是一个把 Ring3 的 Win32 API 和 Ring0 的内核 API 联系起来。Ring3下调用的所有函数最终都会先进入到ntdll里面的

typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
	PULONG_PTR ServiceTableBase;<span style="white-space: pre;">		//SSTD基地址
	PULONG Count;				//包含着 SSDT 中每个服务被调用次数的计数器。这个计数器一般由sysenter 更新
	ULONG TableSize;			//由 ServiceTableBase 描述的服务的数目
	PUCHAR ArgumentTable;		//包含每个系统服务参数字节数表的基地址-系统服务参数表
} KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;</span>

SSDT:KeServiceDescriptorTable
ShadowSSDT:KeServiceDescriptorTableShadow

extern KSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable[NUMBER_SERVICE_TABLES];
extern KSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTableShadow[NUMBER_SERVICE_TABLES];

KeServiceDescriptorTable SSDT 主要是处理来自 Ring3 层的Kernel32.dll 中的系统调用,比如函数 OpenProcess ReadFile 等函数。从kernel32.dll—>ntdll.dll—>系统中断进入内核 ntoskrml.exe
KeServiceDescriptorTableShadow 则主要处理来自 User32.dll 和 GDI32.dll 中的系统调用,比如常见的PostMessage,SendMessage,FindWindow。Win32k.sys
如何准确的判断系统用的是ntoskrnl.exe还是ntkrnlpa.exe文件呢:NtQuerySystemInformation,class 11 第一个模块就是,看名字就可以了
更简便的方式,直接:

<pre style="widows: 1;">kd> lm vm nt
start	end		module name
804d8000 806e5000   nt		 (pdb symbols)		  c:\mysymbol\winxp\ntkrpamp.pdb\7D6290E03E32455BB0E035E38816124F1\ntkrpamp.pdb
	Loaded symbol image file: ntkrpamp.exe
	Image path: ntkrpamp.exe
	Image name: ntkrpamp.exe

ntkrpamp.exe只是内部名称,可以通过ntoskrnl.exe、ntkrnlpa.exe的属性–版本–内部名称看到!
直接用IDA查看ntkrpamp.exe
在Export中搜索keservic即可跳到:

 

双击:

发现这货就是一个全局变量!(更确切的说是变量数组,这个从WRK也可以看出)既然KeServiceDescriptorTable是一个导出的全局变量(数组),那么我们来看wrk,大家都知道在编写代码的时候,要导出一个函数,通常使用def文件。所以ntoskrnl在编写的时候,同样也用到了def来导出,我们翻看wrk

extern KSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable[NUMBER_SERVICE_TABLES];
extern KSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTableShadow[NUMBER_SERVICE_TABLES];
#define NUMBER_SERVICE_TABLES 2

SSTD只用了一张表(KeServiceDescriptorTable[0]),SSTDSHADOW有两张表,第一张和KeServiceDescriptorTable[0]相同,即SSDT,第二张才是SSTDSHADOW

在应用层 ntdll.dll 中的 API 在这个系统服务描述表(SSDT)中都存在一个与之相对应的服务号,要说明这个,可以再用IDA加载ring3的NTDLL.dll,随便选个函数,如NTCreateFile:
ntkrpamp.exe+xuetr:


0xB7=183为服务号
ntdll.dll:


0xB7=183为服务号,其余的一样,比如zwcreatefile为0×25
当我们的应用程序调用 ntdll.dll 中的 API 时,最终会调用内核中与之相对应的系统服务,由于有了 SSDT,所以我们只需要告诉内核需要调用的服务所在 SSDT 中的索引(即服务员)就 OK 了,然后内核根据这个索引值就可以在 SSDT 中找到相对应的服务了,然后再由内核调用服务完成应用程序 API 的调用请求即可

ring3下zw和nt是同一套函数的两个别名
ring0下zw只是做一个过渡分发,而nt才是真正的函数主体,可以看下zwcreatefile的实现:

kd> u nt!zwcreatefile
nt!ZwCreateFile:
80501010 b825000000	  mov	 eax,25h
80501015 8d542404		lea	 edx,[esp+4]
80501019 9c			  pushfd
8050101a 6a08			push	8
8050101c e830140400	  call	nt!KiSystemService (80542451)
80501021 c22c00		  ret	 2Ch
它只是把系统服务员0×25放到eax,然后开始调用系统分发函数走到ntcreatfile中.调用堆栈如下:
kd> kb
ChildEBP RetAddr  Args to Child
bacfbbe0 8054261c bacfbcf0 00100180 bacfbcd4 nt!NtCreateFile
bacfbbe0 80501021 bacfbcf0 00100180 bacfbcd4 nt!KiFastCallEntry+0xfc
bacfbc84 8061f69d bacfbcf0 00100180 bacfbcd4 nt!ZwCreateFile+0x11
最后用windbg来查看下SSDT:
kd> x nt!*servicedes*
80553f60 nt!KeServiceDescriptorTableShadow = <no type information>
80553fa0 nt!KeServiceDescriptorTable = <no type information>
kd> dd 80553f60 L2
80553f60  80502b8c 00000000
kd> dds 80502b8c L2
80502b8c  8059a948 nt!NtAcceptConnectPort
80502b90  805e7db6 nt!NtAccessCheck

要查看SSTDSHADOW需要先切换到GUI进程空间内

kd> dd nt!KeServiceDescriptorTableShadow L8
80553f60  80502b8c 00000000 0000011c 80503000
80553f70  bf999b80 00000000 0000029b bf99a890
kd> dds bf999b80 L1// 未切换时
bf999b80  ????????
kd> !process 0 0 calc.exe
PROCESS 861a9020  SessionId: 0  Cid: 068c    Peb: 7ffdb000  ParentCid: 05c8
    DirBase: 0c9801a0  ObjectTable: e21cdd28  HandleCount:  46.
    Image: calc.exe

kd> .process 861a9020  
Implicit process is now 861a9020
WARNING: .cache forcedecodeuser is not enabled
kd> dds bf999b80 L3//win32k
bf999b80  bf935f7e win32k!NtGdiAbortDoc
bf999b84  bf947b29 win32k!NtGdiAbortPath
bf999b88  bf88ca52 win32k!NtGdiAddFontResourceW

最后遍历SSDT测试代码:
#include <ntddk.h>
 
typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
	PULONG_PTR ServiceTableBase;//SSTD基地址
	PULONG Count;				//包含着 SSDT 中每个服务被调用次数的计数器。这个计数器一般由sysenter 更新
	ULONG TableSize;			//由 ServiceTableBase 描述的服务的数目
	PUCHAR ArgumentTable;		//包含每个系统服务参数字节数表的基地址-系统服务参数表
} KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;
 
//ssdt表已经导出了,这里例行公事下
extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;
 
//卸载函数
VOID DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
	DbgPrint("卸载完成!\n");
}
 
//入口函数
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath)
{
	int i = 0;
 
	DriverObject->DriverUnload = DriverUnload;
 
	for (i=0;i<KeServiceDescriptorTable->TableSize;i++)
	{
		DbgPrint("Number:%d Address:0x%08X\r\n\r\n",i,KeServiceDescriptorTable->ServiceTableBase[i]);
	}
 
	return STATUS_SUCCESS;
}


对于shadowsstd:
1:shadowSSDT是在KeServiceDescriptorTableShadow的第二个表,也就是查看win32K系统服务,第一个表就是ssdt
2:如果我们要查看win32K系统服务,必须切换到GUI线程的上下文,不然win32k无法被加载

遍历SSDTSHADOW:
1.查找到KeServiceDescriptorShadowTable的地址,因为不是导出的,所以只能特征码查找(wrk可以确认哪个函数有它)
2.KeServiceDescriptorShadowTable[0]内容和KeServiceDescriptorTable相同,这可以做为比对标志
3.查找一个GUI进程,并附加上去:如explorer.exe
示例代码:

#include <ntifs.h>
 
typedef struct _KSERVICE_TABLE_DESCRIPTOR
{
	PULONG_PTR ServiceTableBase;//SSTD基地址
	PULONG Count;				//包含着 SSDT 中每个服务被调用次数的计数器。这个计数器一般由sysenter 更新
	ULONG TableSize;			//由 ServiceTableBase 描述的服务的数目
	PUCHAR ArgumentTable;		//包含每个系统服务参数字节数表的基地址-系统服务参数表
} KSERVICE_TABLE_DESCRIPTOR, *PKSERVICE_TABLE_DESCRIPTOR;
//ssdt表已经导出了,这里例行公事下
extern PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorTable;
PKSERVICE_TABLE_DESCRIPTOR KeServiceDescriptorShadowTable;
 
PVOID GetShadowTableAddress()
{
	UNICODE_STRING functionName;
	ULONG i = 0;
	ULONG DwordAtByte = 0;
	PUCHAR p = NULL;
	RtlInitUnicodeString(&functionName, L"KeAddSystemServiceTable");
	p = MmGetSystemRoutineAddress(&functionName);
	if (NULL == p)
	{
		return NULL;
	}
 
	// 开始循环找,找一页,指针递增1
	for (i=0;i<0x1024;i++,p++)
	{
		try
		{
			DwordAtByte = *(PULONG)p;
		}
		__except (EXCEPTION_EXECUTE_HANDLER)
		{
			return NULL;
		}
 
		if (MmIsAddressValid((PVOID)DwordAtByte))
		{
			if (0 == memcmp((PVOID)DwordAtByte, KeServiceDescriptorTable, 16))//对比前16字节 相同则找到
			{
				if ((PVOID)DwordAtByte == KeServiceDescriptorTable)//排除sstd
				{
					continue;
				}
 
				return DwordAtByte;
			}
		}
 
	}
 
	return NULL;
}
 
UCHAR *PsGetProcessImageFileName(__in PEPROCESS eprocess);//导出下使用.
NTSTATUS LookupProcessByName(IN PCHAR pcProcessName,
							 OUT PEPROCESS *pEprocess)
{
	PEPROCESS pCurEprocess = NULL;
	PEPROCESS pNextEprocess = NULL;//做为一个标记,表示循环了一圈
	PLIST_ENTRY	pListActiveProcess = NULL;
	ULONG offset = 0;//ActiveProcessLinks的偏移值
	ULONG uLoopNum = 0;//查找的循环次数
	RTL_OSVERSIONINFOEXW osver = {sizeof(RTL_OSVERSIONINFOEXW)};
	char *lpszAttackProName = NULL;
 
 
	if (!ARGUMENT_PRESENT(pcProcessName)
		||!ARGUMENT_PRESENT(pEprocess))
	{
		KdPrint(("[LookupProcessByName]--invalid para\n"));
		return STATUS_INVALID_PARAMETER;
	}
 
	if (KeGetCurrentIrql()>PASSIVE_LEVEL)
	{
		KdPrint(("[LookupProcessByName]--invalid irql\n"));
		return STATUS_UNSUCCESSFUL;
	}
 
	if (STATUS_SUCCESS != RtlGetVersion((PRTL_OSVERSIONINFOW)&osver))
	{
		KdPrint(("[LookupProcessByName]--RtlGetVersion fail\n"));
		return STATUS_UNSUCCESSFUL;
	}
 
	// 仅对xp测试,自己扩展
	if (5 == osver.dwMajorVersion
		&&1 == osver.dwMinorVersion)
	{
		offset = 0x88;//可通过windbg查看eprocess中的偏移
	}
 
	if (0 == offset)
	{
		KdPrint(("[LookupProcessByName]--unknow os\n"));
		return STATUS_UNSUCCESSFUL;
	}
 
	// 遍历链表查询
	pCurEprocess = PsGetCurrentProcess();
	pNextEprocess = pCurEprocess;
 
	__try
	{
		while (TRUE)
		{
			// TODO.做想做的事吧...
			lpszAttackProName = (char *)PsGetProcessImageFileName(pCurEprocess);
			if (lpszAttackProName
				&& strlen(lpszAttackProName) == strlen(pcProcessName))
			{
				if (0 == _stricmp(lpszAttackProName, pcProcessName))
				{
					KdPrint(("[LookupProcessByName]--find\n"));
					*pEprocess = pCurEprocess;
					return STATUS_SUCCESS;
				}
			}
 
 
			//出口
			if (uLoopNum>=1
				&&pNextEprocess == pCurEprocess)
			{
				KdPrint(("[LookupProcessByName]--loop end\n"));
				*pEprocess = 0x00000000;
				return STATUS_NOT_FOUND;
			}
 
			pListActiveProcess = (PLIST_ENTRY)((ULONG)pCurEprocess+offset);//注意大括号,不用大括号会出错的
			(ULONG)pCurEprocess = (ULONG)pListActiveProcess->Flink;//pCurEprocess临时表示了前一个Active process
			(ULONG)pCurEprocess = (ULONG)pCurEprocess - offset;//对应的Eprocess基址
			KdPrint(("[LookupProcessByName]--pCurEprocess:%08x\n", pCurEprocess));
			uLoopNum ++;//循环次数+1
 
		}
 
	}
	__except(EXCEPTION_EXECUTE_HANDLER)
	{
		KdPrint(("[LookupProcessByName]--execption:%08x--end\n", GetExceptionCode()));
		*pEprocess = 0x00000000;
		return STATUS_NOT_FOUND;
	}
}
 
//卸载函数
VOID DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
	DbgPrint("卸载完成!\n");
}
 
//入口函数
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath)
{
	int i = 0;
	PEPROCESS eprocess_explorer;
	DriverObject->DriverUnload = DriverUnload;
	KeServiceDescriptorShadowTable = GetShadowTableAddress();
	if (KeServiceDescriptorShadowTable)
	{
 
		// 我们得到一个gui进程的对象,因为我们切换进程的时候需要用到
		if (STATUS_SUCCESS == LookupProcessByName("explorer.exe",&eprocess_explorer) )
		{
			KeAttachProcess(eprocess_explorer);//附加到目标进程
 
			//这里为什么要KeServiceDescriptorShadowTable[1],正如我们所说的,第二个表才是ShadowSSDT
			for (i = 0;i<KeServiceDescriptorShadowTable[1].TableSize;i++)
			{
				KdPrint(("Number:%d  Address:0x%08X\r\n",i,KeServiceDescriptorShadowTable[1].ServiceTableBase[i]));
			}
 
			KeDetachProcess();//解除附加
		}
	}
 
 
	return STATUS_SUCCESS;
}















  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值