This post is also available in: 日本語 (Japanese)
Table of Contents
10. Call NtUserMessageCall to trigger the hooked function on WndMagic
11. Use SetWindowLongA to create arbitrary write primitive (lines 534 through 542 in the PoC)
Table of Contents: Figures
Figure 25. Line 530 of the PoC.
Figure 26. WndMagic’s tagWND structure before the call to NtUserMessageCall.
Figure 27. WndMagic’s tagWND structure after the call to NtUserMessageCall.
Figure 28. Wnd0’s tagWND structure.
Figure 29. Memory layout after the call NtUserMessageCall.
Figure 30. SetWindowLongW function prototype.
Figure 31. Line 534 of the PoC.
Figure 32. Line 542 of the PoC.
10. Call NtUserMessageCall to trigger the hooked function on WndMagic
Having set the stage to get a potential kernel desktop heap pointer to an arbitrary window object (Wnd0 in this case, as discussed in step 9), all that is left is to locate a function that calls the hooked user-mode callback xxxClientAllocWindowClassExtraBytes.
It’s useful to note that xxxClientAllocWindowClassExtraBytes was the same user-mode callback that was abused in CVE-2021-1732. In that case, the xxxClientAllocWindowClassExtraBytes was hooked before it was called within xxxCreateWindowEX. xxxCreateWindowEX is called after a call to any of the CreateWindow family of functions.
As a part of the patch to mitigate that vulnerability, Microsoft added a check within xxxCreateWindowEX to ensure that the tagWND.dwExtraFlag (discussed in step 7) was not changed during the user-mode callback. But what if there were other functions that called xxxClientAllocWindowClassExtraBytes that Microsoft did not patch?
In the case of CVE-2022-21882, NtUserMessageCall was subsequently identified as such a function. In fact, it’s likely that whoever discovered CVE-2022-21882 simply reviewed the patch for CVE-2021-1732, identified the cause of the vulnerability, and checked for other functions that called xxxClientAllocWindowClassExtraBytes in hopes that the other functions were not patched with the same check to verify tagWND.dwExtraFlag.
The Call to NtUserMessageCall (line 530) is shown in Figure 25. The parameters used ultimately lead to execution of xxxWrapSwitchWndProc, then xxxSwitchWindowProc, which then lead to a call to xxxClientAllocWindowClassExtraBytes.
Now that xxxClientAllocWindowClassExtraBytes has been hooked with the malicious function, the call to NtUserMessageCall will lead to a call to g_newxxxClientAllocWindowClassExtraBytes. This results in WndMagic being converted to a console window and its pExtraBytes being set to the kernel desktop heap base offset of Wnd0. This means that any references to WndMagic’s tagWND.pExtraBytes value will point to the kernel desktop heap location of Wnd0.
If there is a way to modify a window’s pExtraBytes field, then that same method would now enable the exploit to modify Wnd0’s tagWND structure, at least up to the length of the tagWND.pExtraBytes stored in the tagWND.cbWndExtra field. And in fact, the SetWindowLong family of functions was created for just this purpose.
The memory layout of WndMagic’s tagWND structure before and after the call to NtUserMessageCall is shown in Figures 26 and 27 respectively.
WndMagic’s tagWND.dwExtraFlag is now 0x100100818, indicating it was successfully converted to a console window. The tagWND.pExtraBytes value has been changed to 0x2ad30.
Because WndMagic is a console window now, this represents an offset from the kernel desktop heap base. However, remember this is also the same offset where Wnd0 is located. Therefore WndMagic’s tagWND.pExtraBytes field now points to Wnd0’s tagWND structure on the kernel desktop heap.
Figure 28 shows Wnd0’s tagWND structure and its OffsetToDesktopHeap value of 0x2ad30, which is now where WndMagic’s tagWND.pExtraBytes field is pointing to.
The memory layout, and field references of the windows at this stage of the exploit, is shown in Figure 29.
11. Use SetWindowLongA to create arbitrary write primitive (lines 534 through 542 in the PoC)
As shown in Figure 30, the SetWindowLongW function takes a window handle as the first parameter. The parameter nIndex is an index into the tagWND.pExtraBytes memory allocation.
In the case of a standard window, the index would be referenced as an offset from the pointer stored within tagWND.pExtraBytes. However, in a console window, the index is referenced from the offset value stored within tagWND.pExtraBytes and the kernel desktop heap base. The last parameter dwNewLong is the value to change the memory at nIndex to.
Therefore, to change the contents of a window’s extra bytes, we could call SetWindowLongW. When SetWindowLongW is called for WndMagic, it will be referencing the offset in WndMagic’s tagWND.pExtraBytes as the memory space to accomplish its operations.
So in essence, a call to SetWindowLongW with WndMagic as the HWND parameter will result in changing memory within Wnd0’s tagWND base address plus whatever offset nIndex is set to. This is because WndMagic’s pExtraBytes is now pointing to Wnd0’s tagWND base.
In the first call to SetWindowLongW (line 534) shown in Figure 31, the handle to WndMagic is passed and offset 0x128 + 0x10 is being changed to kernel_desktop_heap_base_offset_Min, which was defined in step 6 as the offset for Wnd0. This offset is really just the offset of 0x128 – see the following note for why the 0x10 is included.
However, because WndMagic’s pExtraBytes offset refers to Wnd0, this call to SetWindowLongW is changing offset 0x128 (tagWND.pExtraBytes) of Wnd0 to the offset of its own tagWND.OffsetToDesktopHeap. Therefore, any future calls to the SetWindowLong family of functions with Wnd0 will functionally operate on offsets relative to the start of Wnd0’s tagWND structure.
It should also be noted that calls to SetWindowLongW and SetWindowLongPtr return the previous value at the specified offset (nIndex) before the function call was made. Therefore, the dwRet variable (shown in Figure 31) will be equal to the tagWND.pExtraBytes of Wnd0 (0x2ae80 shown in Figure 16 above) before the call to SetWindowLongW.
NOTE: The addition of 0x10 is to account for a subtraction that takes place within xxxSetWindowLongW. This may seem strange, but remember these functions are not expected to be called by third-party code, so Microsoft doesn’t feel the need to make adjustments to make the code user friendly.
The next call (line 542) shown in Figure 32, to SetWindowLongW, is changing Wnd0’s tagWND.cbWndExtra field to 0xFFFFFFF. Remember that the cbWndExtra field defines what the size of the pExtraBytes field is. Therefore, this makes the size of the extra bytes field a very large number. This will enable an overflow condition and allow the exploit to write to adjacent kernel memory.
Our next step details the use of SetWindowLongPtrA to leak Wnd1’s spmenu kernel address and replace it with a fake spmenu object.
Continue Reading ➠ Section 5 – Detailed Analysis, Step 12