首页 > WinDriver > WD-SSDT、SSTDSHADOW简介
2013五月29

WD-SSDT、SSTDSHADOW简介

[隐藏]

1.SSDT

SSDT 的全称是 System Services Descriptor Table,系统服务描述符表

这个表就是一个把 Ring3 的 Win32 API 和 Ring0 的内核 API 联系起来。Ring3下调用的所有函数最终都会先进入到ntdll里面

这个表可以在WRK中找到定义:

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:KeServiceDescriptorTable

SSDT 主要是处理来自 Ring3层的Kernel32.dll 中的系统调用,比如函数 OpenProcess ReadFile 等函数。

从kernel32.dll—>ntdll.dll—>系统中断进入内核 ntoskrml.exe

如何准确的判断系统用的是ntoskrnl.exe还是ntkrnlpa.exe文件呢:NtQuerySystemInformation,class 11 第一个模块就是,看名字就可以了

更简便的方式,直接:

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即可跳到:

ssdt-export.png

双击:

sstd-data.png

发现这货就是一个全局变量!(更确切的说是变局数组)

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

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

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

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

ntkrpamp.exe+xuetr:

ZWreadfile.png

0xB7=183为服务号

ntdll.dll:

ntdll-readfile.png

0xB7=183为服务号,其余的一样,比如zwcreatefile为0x25

当我们的应用程序调用 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

它只是把系统服务员0x25放到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*
8055d6c0 nt!KeServiceDescriptorTableShadow = <no type information>
8055d700 nt!KeServiceDescriptorTable = <no type information>
kd> dd nt!KeServiceDescriptorTable
8055d700  80505450 00000000 0000011c 805058c4
8055d710  00000000 00000000 00000000 00000000
8055d720  00000000 00000000 00000000 00000000
8055d730  00000000 00000000 00000000 00000000
8055d740  00000002 00002710 bf80c0b6 00000000
8055d750  bad85a80 ba506b60 8a9278f8 806f70c0
8055d760  00000000 00000000 c4ff4bc0 00000002
8055d770  e944db00 01cf7bb3 00000000 00000000
kd> dds 80505450 l1
80505450  805a5614 nt!NtAcceptConnectPort

   

2.SSTDSHADOW

ShadowSSDT:KeServiceDescriptorTableShadow

KeServiceDescriptorTableShadow主要处理来自 User32.dll 和 GDI32.dll 中的系统调用,比如常见的PostMessage,SendMessage,FindWindow。Win32k.sys

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

kd> dd nt!KeServiceDescriptorTableShadow
8055d6c0  80505450 00000000 0000011c 805058c4//第一张表是SSTD
8055d6d0  bf999b80 00000000 0000029b bf99a890//第二张表是SSTDSHADOW
 
dds bf999b80 l1//未切换时
bf999b80  ????????
kd> !process 0 0 calc.exe
PROCESS 89f15020  SessionId: 0  Cid: 0560	Peb: 7ffd8000  ParentCid: 0620
	DirBase: 0b0803e0  ObjectTable: e205fcf0  HandleCount:  42.
	Image: calc.exe
 
kd> .process 89f15020  //切换至calc.exe
Implicit process is now 89f15020
WARNING: .cache forcedecodeuser is not enabled
 
bf999b84  bf947b29 win32k!NtGdiAbortPath
bf999b88  bf88ca52 win32k!NtGdiAddFontResourceW
bf999b8c  bf93f6f0 win32k!NtGdiAddRemoteFontToDC
bf999b90  bf949140 win32k!NtGdiAddFontMemResourceEx
bf999b94  bf936212 win32k!NtGdiRemoveMergeFont
bf999b98  bf9362b7 win32k!NtGdiAddRemoteMMInstanceToDC
bf999b9c  bf83b4cd win32k!NtGdiAlphaBlend
bf999ba0  bf948a67 win32k!NtGdiAngleArc
bf999ba4  bf934a17 win32k!NtGdiAnyLinkedFonts
bf999ba8  bf94905f win32k!NtGdiFontIsLinked
bf999bac  bf90f2f4 win32k!NtGdiArcInternal
bf999bb0  bf902318 win32k!NtGdiBeginPath
bf999bb4  bf809fdf win32k!NtGdiBitBlt

    

3.最后测试

3.1.遍历ssdt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#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;
}

3.2.遍历shadowssdt

1.查找到KeServiceDescriptorShadowTable的地址,因为不是导出的,所以只能特征码查找(wrk可以确认哪个函数有它)

2.KeServiceDescriptorShadowTable[0]内容和KeServiceDescriptorTable相同,这可以做为比对标志

3.查找一个GUI进程,并附加上去:如explorer.exe

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#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;
}

对于shadowssdt:

1:shadowSSDT是在KeServiceDescriptorTableShadow的第二个表,也就是查看win32K系统服务,第一个表就是ssdt

2:如果我们要查看win32K系统服务,必须切换到GUI线程的上下文,不然win32k无法被加载

shadowsstd.png



文章作者:hgy413
本文地址:https://hgy413.com/1439.html
版权所有 © 转载时必须以链接形式注明作者和原始出处!

本文的评论功能被关闭了.