This post is also available in: 日本語 (Japanese)
Table of Contents
Table of Contents: Figures
Figure 33. Line 546-549 of the PoC.
Figure 34. Line 552 of the PoC.
Figure 35. MSDN documentation for SetWindowLongPrtA’s nIndex parameter.
Figure 36. MSDN definition for the hMenu parameter.
Figure 37. Lines 355-385 of the PoC.
Figure 38. Parent tagWND structure.
Figure 39. Disassembly of xxxSetWindowData (called from SetWindowLongPtrA).
Figure 40. WinDBG output showing value of spmenu before being changed.
Figure 41. Register values just before the return from SetWindowLongPtrW.
Figure 42. Line 566 of the PoC.
Figure 43. Line 546 of the PoC.
Figure 44. WinDbg output of Wnd1’s child tagWND structure.
Figure 45. Kernel desktop heap + 0x3a850.
Figure 46. Wnd1’s parent TagWND structure + 0xa8.
12. Use SetWindowLongPtrA to leak Wnd1’s spmenu kernel address and replace it with a fake spmenu object
After the two calls to SetWindowLongW there are several calls to SetWindowLongPtrA. SetWindowLongW is an older, deprecated function that operates on 32-bit LONG integers, whereas SetWindowLongPtrA supersedes SetWindowLongW and allows for operations on both 32-bit and 64-bit LONG_PTR integers.
The first call to SetWindowLongPtrA, shown in Figure 33 (lines 546 through 549), changes the tagWND.dwExStyle of Wnd1 to 0x40000000 (WS_CHILD). This makes the window a child window. Pay close attention to the parameters and do the math. The function is given Wnd0’s handle, and we know SetWindowLongPtrA operates on the window’s pExtraBytes field, which now points to the start of Wnd0’s tagWND structure.
If we subtract Wnd0’s desktop heap base offset (0x2ad30) from Wnd1’s desktop heap base offset (0x38390) and add (0x18), we get 0xd678. If we add this to the start of Wnd0’s tagWND structure (0xffff8e820102ad30), we get 0xffff8e820102010383a8. This is offset 0x18 into Wnd1’s tagWND structure, or its tagWND.dwExStyle.
Changing the style of Wnd1’s window to a child window is important for the next call to SetWindowLongPtrA.
The next call (shown in Figure 34) passes Wnd1’s handle directly, sets the nIndex parameter to -12, and g_pMem4 is passed as the dwNewLong parameter.
Referring to the MSDN documentation for SetWindowLongPtrA’s nIndex parameter (shown in Figure 35) indicates that -12 refers to the GWL_ID, which is the macro for a child window. Because changing the GWL_ID cannot be done on a top-level window, the previous call to SetWindowLongPtrA was made to prepare Wnd1 for this call.
To better understand what is going on here, it is important to remember that hMenu (defined during the window creation in step 8) refers to a handle to a menu or specifies a child window identifier. The MSDN definition for the hMenu parameter is shown in Figure 36.
Recall from step 8 that the hMenu parameter was set to a call to CreateMenu for each window. Therefore, for each window created, a handle to a menu object (essentially a pointer to the object in the kernel) was assigned to the hMenu parameter. But because Wnd1 is now a child window, the exploit can use SetWindowLongPtrA to change the child window identifier to that of g_pMem4. Changing the hMenu handle would not have been possible if Wnd1 was still a parent window.
Lastly, g_qwExploit is set to the return value of SetWindowLongPtrA, which is equal to the value of hMenu before the call – the handle assigned during window creation. Handles are simply memory locations used by the Windows kernel to track objects. Therefore, g_qwExploit now contains the kernel address to Wnd1’s spmenu object.
It wasn’t discussed earlier, but g_pMem4 was previously defined in the exploit code. Lines 355 through 385 set up five memory allocations – g_pMem1 through g_pMem5 – shown in Figure 37.
These allocations, along with the subsequent code, are setting up a fake menu object to replace the real menu object of Wnd1. The fake menu object is shown in Figure 58. We go into more detail on this a bit later.
Also, because SetWindowLongPtrA returns the old value of nIndex, a pointer to the hMenu object is returned. In fact, the pointer returned is the *spmenu entry within the parent tagWND, which is a kernel pointer. It resides at offset 0xa8 in the tagWND structure, shown in Figure 38 as a refresher.
The call to SetWindowLongPtrA with GWL_ID triggers an execution path that changes the *spmenu in both the parent tagWND and child/user-mode copy (offset 0x98) of the tagWND structure, shown in Figure 39, where r15 is equivalent to g_pMem4.
In Figure 39, rsi is the parent tagWND. A pointer to the child/copy tagWND is located at offset 0x28, and it is copied to the rax register. Then the spmenu entry for each tagWND structure is changed to the value in r15 (g_pMem4).
The value returned after this call to SetWindowLongPtrA is the value of the parent tagWND + 0xa8 (spmenu) prior to being changed. This would be rsi+0xa8 at 0xffff8eac5a15c386 in the Figure 39 above. Setting a breakpoint in WinDbg at this point, we can see the register contents and a dump of rsi+0xa8 (0xfff8e82008218c0) in Figure 40.
Just to verify that the return value is indeed what we think it should be, Figure 41 shows the registers at the end of the SetWindowLongPtrW call.
NOTE: To avoid confusion, the reason why some of the figures have SetWindowLongPtrW instead of SetWindowLongPtrA is because Windows only deals with Unicode characters directly. These are functions that end in W. To accommodate ASCII, Microsoft created wrapper functions. These functions, which end in A, simply do the conversions and then call the Unicode functions directly. Therefore, when debugging the calls to SetWindowLongPtrA, the actual heavy lifting is being done in SetWindowLongPtrW.
Notice that rax is the same value as rsi+0xa8 in Figure 40. This is the kernel address of the original menu item created in step 8. This address will be used in a future step to find the kernel EPROCESS structure and copy the System token for privilege escalation. This pointer is saved in the g_qwExploit variable in the PoC, shown in Figure 34 above.
The next call (line 566) to SetWindowLongPtrA, shown in Figure 42, resets Wnd1’s tagWND.dwExStyle to the previous value. This value was saved in line 546 of the PoC and is shown in Figure 43. Therefore, Wnd1 is no longer a child window.
One might ask why we would need to go through all of this work to leak the spmenu pointer when we’re able to write to kernel memory using the SetWindowLong functions. Couldn’t we just use GetWindowLong to read the kernel address of the menu object?
This could give us the menu object. However, it would read from the child tagWND structure, which does not store a kernel address for the menu object. Figure 44 shows the WinDbg output of Wnd1’s child tagWND structure. At offset 0x98 (spmenu), we can see it’s definitely not a kernel pointer. As we’ll see in a bit, this is an offset from the desktop heap to the spmenu object.
If you look at the kernel desktop heap base that we calculated in step 7, plus the value in the child tagWND+0x98, we find what’s shown in Figure 45.
When we look at the address leaked from the parent tagWND (offset 0xa8), we see the following, shown in Figure 46.
We can see that the first value is the same. There might also be some kind of user-mode friendly spmenu object, just like there is a user-mode friendly copy of the tagWND structure.
But now that we have this kernel address, what can we do with it? We will explore this in the next step in Section 6 with step 13.
Continue Reading ➠ Section 6 – Detailed Analysis, Step 13