三年前面朝黄土背朝天的我,写了一篇如何在Windows 7
系统下枚举内核SSDT
表的文章《驱动开发:内核读取SSDT表基址》
三年过去了我还是个单身狗
,开个玩笑,微软的Windows 10
系统已经覆盖了大多数个人PC终端,以前的方法也该进行迭代更新了,或许在网上你能够找到类似的文章,但我可以百分百肯定都不能用,今天LyShark
将带大家一起分析Win10 x64
最新系统SSDT
表的枚举实现。
看一款闭源ARK工具的枚举效果:
直接步入正题,首先SSDT
表中文为系统服务描述符表,SSDT表的作用
是把应用
层与内核
层联系起来
起到桥梁
的作用,枚举SSDT表
也是反内核
工具最基本的功能,通常在64位
系统中要想找到SSDT
表,需要先找到KeServiceDescriptorTable
这个函数,由于该函数没有被导出,所以只能动态的查找它的地址,庆幸的是我们可以通过查找msr(c0000082)
这个特殊的寄存器来替代查找KeServiceDescriptorTable
这一步,在新版系统中查找SSDT可以归纳为如下这几个步骤。
首先第一步通过rdmsr C0000082
MSR寄存器得到KiSystemCall64Shadow
的函数地址,计算KiSystemCall64Shadow
与KiSystemServiceUser
偏移量,如下图所示。
6ed53180(KiSystemCall64Shadow) - 6ebd2a82(KiSystemServiceUser) = 1806FE
也就是说 6ed53180(rdmsr) - 1806FE = KiSystemServiceUser
如上当我们找到了KiSystemServiceUser
的地址以后,在KiSystemServiceUser
向下搜索可找到KiSystemServiceRepeat
里面就是我们要找的SSDT
表基址。
其中fffff8036ef8c880
则是SSDT表
的基地址,紧随其后的fffff8036ef74a80
则是SSSDT表
的基地址。
那么如果将这个过程通过代码的方式来实现,我们还需要使用《驱动开发:内核枚举IoTimer定时器》
中所使用的特征码定位技术,如下我们查找这段特征。
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
|
/
/
署名权
/
/
right to sign one's name on a piece of work
/
/
PowerBy: LyShark
/
/
Email: me@lyshark.com
#include <ntifs.h>
#pragma intrinsic(__readmsr)
ULONGLONG ssdt_address
=
0
;
/
/
获取 KeServiceDescriptorTable 首地址
ULONGLONG GetLySharkCOMKeServiceDescriptorTable()
{
/
/
设置起始位置
PUCHAR StartSearchAddress
=
(PUCHAR)__readmsr(
0xC0000082
)
-
0x1806FE
;
/
/
设置结束位置
PUCHAR EndSearchAddress
=
StartSearchAddress
+
0x100000
;
DbgPrint(
"[LyShark Search] 扫描起始地址: %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 函数地址
/
*
nt!KiSystemServiceRepeat:
fffff803`
6ebd2b94
4c8d15e59c3b00
lea r10,[nt!KeServiceDescriptorTable (fffff803`
6ef8c880
)]
fffff803`
6ebd2b9b
4c8d1dde1e3a00
lea r11,[nt!KeServiceDescriptorTableShadow (fffff803`
6ef74a80
)]
fffff803`
6ebd2ba2
f7437880000000 test dword ptr [rbx
+
78h
],
80h
fffff803`
6ebd2ba9
7413
je nt!KiSystemServiceRepeat
+
0x2a
(fffff803`
6ebd2bbe
) Branch
*
/
if
(OpCodeA
=
=
0x4c
&& OpCodeB
=
=
0x8d
&& OpCodeC
=
=
0x15
)
{
/
/
获取高位地址fffff802
memcpy(&templong, ByteCode
+
3
,
4
);
/
/
与低位
64da4880
地址相加得到完整地址
addr
=
(ULONGLONG)templong
+
(ULONGLONG)ByteCode
+
7
;
return
addr;
}
}
}
return
0
;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint((
"驱动程序卸载成功! \n"
));
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
DbgPrint(
"hello lyshark.com"
);
ssdt_address
=
GetLySharkCOMKeServiceDescriptorTable();
DbgPrint(
"[LyShark] SSDT = %p \n"
, ssdt_address);
DriverObject
-
>DriverUnload
=
UnDriver;
return
STATUS_SUCCESS;
}
|
如上代码中所提及的步骤我想不需要再做解释了,这段代码运行后即可输出SSDT表的基址。
如上通过调用GetLySharkCOMKeServiceDescriptorTable()
得到SSDT
地址以后我们就需要对该地址进行解密操作。
得到ServiceTableBase
的地址后,就能得到每个服务函数的地址。但这个表存放的并不是SSDT
函数的完整地址,而是其相对于ServiceTableBase[Index]>>4
的数据,每个数据占四个字节,所以计算指定Index
函数完整地址的公式是;
如下汇编代码就是一段解密代码,代码中rcx
寄存器传入SSDT的下标,而rdx
寄存器则是传入SSDT表基址。
1
2
3
4
5
6
7
8
9
10
11
12
|
48
:
8BC1
| mov rax,rcx | rcx
=
index
4C
:
8D12
| lea r10,qword ptr ds:[rdx] | rdx
=
ssdt
8BF8
| mov edi,eax |
C1EF
07
| shr edi,
7
|
83E7
20
|
and
edi,
20
|
4E
:
8B1417
| mov r10,qword ptr ds:[rdi
+
r10] |
4D
:
631C82
| movsxd r11,dword ptr ds:[r10
+
rax
*
4
] |
49
:
8BC3
| mov rax,r11 |
49
:C1FB
04
| sar r11,
4
|
4D
:
03D3
| add r10,r11 |
49
:
8BC2
| mov rax,r10 |
C3 | ret |
|
有了解密公式以后代码的编写就变得很容易,如下是读取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
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
|
/
/
署名权
/
/
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;
ULONGLONG ssdt_base_aadress;
PSYSTEM_SERVICE_TABLE KeServiceDescriptorTable;
typedef UINT64(__fastcall
*
SCFN)(UINT64, UINT64);
SCFN scfn;
/
/
解密算法
VOID DecodeSSDT()
{
UCHAR strShellCode[
36
]
=
"\x48\x8B\xC1\x4C\x8D\x12\x8B\xF8\xC1\xEF\x07\x83\xE7\x20\x4E\x8B\x14\x17\x4D\x63\x1C\x82\x49\x8B\xC3\x49\xC1\xFB\x04\x4D\x03\xD3\x49\x8B\xC2\xC3"
;
/
*
48
:
8BC1
| mov rax,rcx | rcx
=
index
4C
:
8D12
| lea r10,qword ptr ds:[rdx] | rdx
=
ssdt
8BF8
| mov edi,eax |
C1EF
07
| shr edi,
7
|
83E7
20
|
and
edi,
20
|
4E
:
8B1417
| mov r10,qword ptr ds:[rdi
+
r10] |
4D
:
631C82
| movsxd r11,dword ptr ds:[r10
+
rax
*
4
] |
49
:
8BC3
| mov rax,r11 |
49
:C1FB
04
| sar r11,
4
|
4D
:
03D3
| add r10,r11 |
49
:
8BC2
| mov rax,r10 |
C3 | ret |
*
/
scfn
=
ExAllocatePool(NonPagedPool,
36
);
memcpy(scfn, strShellCode,
36
);
}
/
/
获取 KeServiceDescriptorTable 首地址
ULONGLONG GetKeServiceDescriptorTable()
{
/
/
设置起始位置
PUCHAR StartSearchAddress
=
(PUCHAR)__readmsr(
0xC0000082
)
-
0x1806FE
;
/
/
设置结束位置
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
/
/
4c
8d
15
e5
9e
3b
00
lea r10,[nt!KeServiceDescriptorTable (fffff802`
64da4880
)]
/
/
4c
8d
1d
de
20
3a
00
lea r11,[nt!KeServiceDescriptorTableShadow (fffff802`
64d8ca80
)]
if
(OpCodeA
=
=
0x4c
&& OpCodeB
=
=
0x8d
&& OpCodeC
=
=
0x15
)
{
/
/
获取高位地址fffff802
memcpy(&templong, ByteCode
+
3
,
4
);
/
/
与低位
64da4880
地址相加得到完整地址
addr
=
(ULONGLONG)templong
+
(ULONGLONG)ByteCode
+
7
;
return
addr;
}
}
}
return
0
;
}
/
/
得到函数相对偏移地址
ULONG GetOffsetAddress(ULONGLONG FuncAddr)
{
ULONG dwtmp
=
0
;
PULONG ServiceTableBase
=
NULL;
if
(KeServiceDescriptorTable
=
=
NULL)
{
KeServiceDescriptorTable
=
(PSYSTEM_SERVICE_TABLE)GetKeServiceDescriptorTable();
}
ServiceTableBase
=
(PULONG)KeServiceDescriptorTable
-
>ServiceTableBase;
dwtmp
=
(ULONG)(FuncAddr
-
(ULONGLONG)ServiceTableBase);
return
dwtmp <<
4
;
}
/
/
根据序号得到函数地址
ULONGLONG GetSSDTFunctionAddress(ULONGLONG NtApiIndex)
{
ULONGLONG ret
=
0
;
if
(ssdt_base_aadress
=
=
0
)
{
/
/
得到ssdt基地址
ssdt_base_aadress
=
GetKeServiceDescriptorTable();
}
if
(scfn
=
=
NULL)
{
DecodeSSDT();
}
ret
=
scfn(NtApiIndex, ssdt_base_aadress);
return
ret;
}
VOID UnDriver(PDRIVER_OBJECT driver)
{
DbgPrint((
"驱动程序卸载成功! \n"
));
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
DbgPrint(
"hello lyshark.com \n"
);
ULONGLONG ssdt_address
=
GetKeServiceDescriptorTable();
DbgPrint(
"SSDT基地址 = %p \n"
, ssdt_address);
/
/
根据序号得到函数地址
ULONGLONG address
=
GetSSDTFunctionAddress(
51
);
DbgPrint(
"[LyShark] NtOpenFile地址 = %p \n"
, address);
/
/
得到相对SSDT的偏移量
DbgPrint(
"函数相对偏移地址 = %p \n"
, GetOffsetAddress(address));
DriverObject
-
>DriverUnload
=
UnDriver;
return
STATUS_SUCCESS;
}
|
运行后即可得到SSDT
下标为51
的函数也就是得到NtOpenFile
的绝对地址和相对地址。
你也可以打开ARK工具,对比一下是否一致,如下图所示,LyShark
的代码是没有任何问题的。
更多【 驱动开发:Win10内核枚举SSDT表基址】相关视频教程:www.yxfzedu.com