by Morten Schenk
Windows 10 1809 Kernel ASLR Bypass Evolution
When it is well-implemented, Kernel Address Space Layout Randomization (KASLR) makes Windows kernel exploitation extremely difficult by making it impractical to obtain the base address of a kernel driver directly. In an attempt to bypass this, researchers have historically focussed on kernel address leaks to retrieve these addresses, either through specific kernel-memory disclosure vulnerabilities or using a kernel-mode read primitive created through a kernel vulnerability like a pool overflow or a write-what-where. The focus of this article is the more generic approach of KASLR bypass using a kernel-mode read primitive.
As researchers develop new bypass techniques, Microsoft has consistently mitigated many of the resulting exploit vectors:
- A pair of exploitation techniques leveraged bitmaps and palette objects as kernel-mode read and write primitives. These techniques were mitigated by the Type Isolation protection of Windows 10 Release 1709 (aka Redstone 3) in 2017 and Release 1803 (aka Redstone 4) in 2018 as detailed in “The Life and Death of Kernel Object Abuse” by Saif El-Sherei and Ian Kronquist.
- Two noteworthy Windows 10 generic kernel exploitation talks — one by Saif El-Sherei and one by myself — highlight exploitable flaws in the Creators Update of Windows release 1703, (aka Redstone 2). Even though the findings of these presentations still applied to the subsequent Redstone 3 release, the findings were all mitigated by Redstone 4.
- A vector which used the tagWnd object as a kernel-mode read and write primitive was quietly mitigated in Redstone 4. Not a single public talk or paper seems to document this.
- By forging a kernel-mode read primitive, an attacker could bypass KASLR mitigation in a variety of ways (see my Black Hat USA 2017 presentation). However, the latest 1809 release of Windows 10 (aka Redstone 5 or RS5) squashed all remaining known KASLR bypass techniques.
At the time of this writing, it has been a year since the release of an undocumented, public, general Windows kernel exploitation technique which means KASLR is standing strong in modern versions of Windows 10.
On the surface, this seems like a good thing, but when I began another round of course development at Offensive Security, I faced a dilemma.
Our training and certifications are the most well-recognized and respected in the industry. We take pride in providing unique course offerings and unmatched learning experiences that highlight the latest ethical hacking tools and techniques. Although this sounds like sales fodder, it’s not. We take pride in our courses and as a course developer I hated the idea of starting with year-old techniques against older operating systems when I knew there must be new vectors against modern operating systems waiting to be uncovered.
So, I began my search for a new KASLR bypass on the current version of Windows 10, codenamed Redstone 5. Eventually, I found one.
In this post, I’ll take you through some of my key research steps that led to the discovery of a previously undocumented KASLR bypass.
First, note that KASLR can be trivially bypassed by an exploit executed at medium-integrity through the use of the well-known EnumDeviceDrivers and NtQuerySystemInformation APIs. Therefore, for the remainder of this post, I will assume that the execution context is either low-integrity or from within an application sandbox.
On previous versions of Windows 10, several generic methods could be used to force a kernel pointer leak, including using the Win32ThreadInfo pointer from Thread Environment Block (TEB), leaking a Compatible bitmap or using the Desktop heap. In this approach I focussed on using the Desktop heap.
In Windows 10 Redstone 2, the UserHandleTable containing the kernel-mode address of all objects allocated on the Desktop heap was removed, but luckily the Desktop heap itself was still mapped in user-mode, allowing us to search through it and locate a specific object. Since Desktop heap objects like tagWnd contain a header that can lead to a KTHREAD structure through specific dereference chains, we could trigger a kernel-mode pointer address leak, allowing us to bypass KASLR. A code snippet showing this logic is presented in Listing 1:
Listing 1 – Technique from Windows 10 release Redstone 2 to bypass KASLR
To test this under Windows 10 Redstone 5 I reused the technique I presented at Black Hat USA 2017 by creating a window through the CreateWindowEx API, grabbing the address of the user-mode mapped Desktop heap from offset 0x828 in the TEB, and performing a brute-force search for the Window handle on the user-mode mapped Desktop heap to obtain the offset into it.
Figure 1: Window handle and location in user-mode mapped Desktop heap
Before Redstone 5, the tagWnd address in Figure 1 would have pointed to a direct copy of the tagWnd kernel object containing a direct copy of the tagWnd object containing all relevant kernel-mode pointers, including the threadInfo pointer. However, as shown in Listing 2, the actual result was something very different:
Listing 2 – Windows object inside user-mode mapped desktop heap
In Redstone 2, the highlighted values in Listing 2 would have contained the UNICODE_STRING structure for the name of the window object. This means that the value mapped into the highlighted values above would normally contain the UNICODE_STRING structure for the name of the window, which is at offset 8, meaning the value mapped into the address 0x20a177ab800 would normally contain the kernel-mode pointer to that UNICODE string. But it didn’t. It was clear at this point that Microsoft had drastically altered the user-mode mapped Desktop heap.
At this point, to better understand what was going on, I had to perform some cross-checks in kernel space. To do that I tried to locate the Desktop heap kernel-mode base address. Before Redstone 5, this pointer could be found at offset 0x28 of the Desktop heap header which could be read from the user-mode mapped Desktop heap. Given this, I tried to manually gather this address in the debugger from offset 0x828 in the TEB:
Listing 3 – User-mode mapped Desktop heap header
From the listing above I could see that the QWORD at offset 0x28 in the user-mode mapped Desktop heap now only contains a NULL value. I can, however gather the Desktop heap kernel base at offset 0x80, so I continued my analysis by using that offset instead.
Listing 4 – Delta value for Desktop heap and Window kernel-mode address
Since the user-mode mapped Desktop heap is updated by a SYSTEM thread, I expected the content of the Desktop heap in kernel-space to be slightly different from the one presented in user-space. However, inspecting the memory of two Desktop heaps (Listing 3), WinDBG showed the exact same (unfiltered) content! This means that Microsoft had undertaken a radical change to both the Desktop heap and the associated APIs.
Bunch of Heaps
To figure out what kind of changes Microsoft had implemented and to determine if the attack vector was possible, I needed to learn more about how kernel-mode API’s operated on the modified Desktop heap. Since the window name, stored as a UNICODE string in the tagWnd object, is typically easy to recognize (and is user-controllable) I invoked the undocumented NtUserDefSetText API in an attempt to update it and obtain its true kernel-mode location. To follow the execution I placed a breakpoint on NtUserDefSetText in Win32kfull.sys displayed the first instructions:
Listing 5 – ValidateHwnd seen in prologue of NtUserDefSetTExt
I noticed a call to ValidateHwnd, which uses the window handle to locate the kernel-mode tagWnd object address. Normally, ValidateHwnd would return the tagWnd memory address on the kernel Desktop heap, but this presented yet another surprise:
Listing 6 – A second Desktop heap containing the tagWnd object
As shown in Listing 6, the kernel-mode address received from ValidateHwnd points to an object with the handle 0x30502, which matched the Window handle returned in user-space by the updated proof of concept. This object contained kernel-mode pointers and offset 0x28 contained a pointer to a separate allocation that also contains the same window handle.
The conclusion from this is that Microsoft had created multiple Desktop heaps and split object content across them while only mapping the kernel-mode pointer free version to user-mode. This is a very good way of mitigating both the abuse of kernel-mode objects from user-mode as well as protecting KASLR. While I did spent time further analyzing this split, I will continue to focus on the KASLR bypass for the purposes of this post.
As with everything related to ASLR, it is key to look at what is randomized across process restarts and reboots. When I performed these steps, I discovered important pieces of information. First, the base address of the Desktop heap that is mapped into user-mode always ends with 0x1400000, leaving only the upper 36 bits randomized after reboot.
Secondly, the location of the Desktop heaps that contain kernel-mode object pointers are randomized to the same upper 36 bits as the Desktop heap which is mapped into user-mode (shown below in Listing 7 in green). The following 4 bits always equate to 0x4 or 0x6 (shown in red) while the lower 24 bits are randomized (shown in blue), eliminating an easy way to predict the object’s kernel-mode only Desktop heap address:
Listing 7 – Header of tagWnd object in kernel-mode only Desktop heap
The division seems sound and only the kernel-mode Desktop heaps contain pointers to the user-mode Desktop heap.
The use of fixed values seemed interesting and revealed an incomplete randomization. Luckily I discovered one more important finding when looking across reboots. The kernel-mode only Desktop heap portion of the tagWnd object shown in Listing 7 reveals that the pointer at offset 0x50 is to yet another kernel-mode only Desktop heap and its content is an unknown object:
Listing 8 – Unknown object in separate Desktop heap
What makes this object interesting is that the lower 28 bits are fixed across reboots to the value 0x08308c0 (shown in blue) while the upper 36 bits are equal to the Desktop heap shared with user-mode (shown in green). In effect, we can leak and calculate the absolute address of this object, which contains kernel-mode pointers!
Even more valuable is the fact that the value at offset 0x10 from the beginning of this object is a pointer to the threadInfo structure, which brings us back to the old tagWnd KASLR bypass technique. Offset 0x0 of the threadInfo structure contains a pointer to the KTHREAD, which at offset 02A8 contains a pointer to ntoskrnl. The dereference sequence is given in Listing 9:
Listing 9 – Pointer into ntoskrnl from the unknown object
With Windows 10 release Redstone 5, Microsoft has performed an impressive engineering task by fragmenting the Desktop heap into multiple heaps and only allowing user-mode to view non-kernel specific content. However, the randomization used with the implementation contains flaws by using static values that can lead to a KASLR bypass if an attacker is able to read arbitrary kernel-mode memory.
The KASLR bypass manifests from the option by an attacker to disclose the randomized upper 36 bits of a kernel-mode object address, while the lower 28 bits are static. The location of the kernel-mode object used in conjunction with a kernel-mode read primitive leaks a pointer into ntoskrnl.
There is a lot going on here, but boiling it down to a single command, I present this undocumented KASLR bypass against Microsoft Windows 10 Redstone 5:
Listing 10 – KASLR bypass in single WinDBG command
Since this type of issue does not match any of the Microsoft bug reporting programs or guidelines, I have not reported this issue. However, I have confirmed that this issue does not work in the upcoming 19H1 release due to some design changes. In the meantime, I’ll be sure to include this development (or parts of it) in upcoming training courses (watch out, Advanced Windows Exploitation!) and look forward to the next round of updates as well as the inevitable research community workarounds.