Kernel Address Space Layout Randomization (KASLR ) has been improved significantly by Windows 10, which raises the price of exploitation, especially for remote code execution exploits. There are numerous kernel virtual address space (VAS ) locations, such as system PTEs, pools, and kernel stacks. are chosen randomly. The KUSER_SHARED_DATA structure, a page of memory that has always been conventionally mapped at an established virtual address in the kernel, is arguably the well-known exception to this rule. By removing the final blind-write target structure in the kernel that remote code execution exploits can use, this post highlights the work we have done to strengthen KASLR in Windows.
Without the close collaboration between MSRC and teams from across the Windows organization, none of this work would have been possible.
Background
A single page ( 4096 bytes ) of memory known as the KUSER_SHARED_DATA structure is mapped to a specific, hardcoded address on the kernel and user sides of VAS. Every process is mapped to KUSER_SHARED_DATA, which offers a quick method for obtaining frequently required global data ( interrupt time, version, debugger state, processor extensions, etc. ). system calls or interrupts are used to switch from the kernel to kernel mode without the user being involved. The page’s virtual address in kernel mode on 64-bit Windows is 0xFFFFF78000000, and both Read and Write permissions are used to map it. The page permissions are read-only, and the user-mode mapping is located at address 0x7FFE0000. During and after boot, the NT Kernel and related drivers use the kernel-mode mapping to dynamically write to the various fields in the KuSER_SHARED_DATA structure, and user mode code can read these values.
The KUSER_SHARED_DATA structure is mapped at a hardcoded virtual address, which has been helpful for remote, kernel-mode exploitation even though it does n’t contain any pointers ( thanks to prior work on this ). KASLR serves as a barrier and requires the remote attacker to determine where to write to in order to gain access to the “write” primitive. Attackers are typically compelled by a blind-write primitive like this to take advantage of an additional vulnerability that can leak information in order to reveal the kernel-mode structure’s memory address, preferably its remote-controllable contents. Attackers can, however, use the same blind-write primitive and create some specially crafted data structures inside KUSER_SHARED_DATA instead of using another bug in an exploit chain. They can then point to a different code path in the kernel using the address of this data, which is fixed across reboots. Attackers may be able to read arbitrary regions of the kernel VAS as a result of this in some circumstances, allowing them to extend the initial blind-write primitive they obtained ( by activating the vulnerability ). A payload ( forged MDL structures ) is written inside KUSER_SHARED_DATA and its address is given to a specific code path, which then reads from arbitrary memory as specified by the payment. This technique is described in an article from Ricerca Security. This was used in the proof-of-concept exploit for the Remote Code Execution vulnerability, which was fixed as CVE-2020-07096, and allows the blind-write primitive to be extended to an arbitrary memory read primitive.
Design for mitigation
A backwards-compatible structure called KUSER_SHARED_DATA has been around since the very beginning of Windows. The fields on the page have remained at constant offsets, and over time, a few more fields have been added. Since so much third-party kernel-mode code running on Windows depends on reading from this page’s fixed address. How can we randomize this without violating the shared page’s static nature?
We made the decision to change the page protections for this mapping to READ- ONLY and create a new virtual mapping of the same underlying physical page at the randomized address in order to ensure compatibility. Read and Write protections are used in this new randomized mapping. In essence, the old, fixed address can only be read from and not written into; any writing to the actual page would have to pass through this new, randomized virtual address.
This design makes sure that we do n’t violate the address’s current usage while also minimizing the previously mentioned technique where KASLR can be circumvented by remote kernel-mode exploits.
The change is shown in the following numbers:
Before the Mitigation, in Figure 1.
Figure 2: Following Mitigation
Let’s quickly examine how Windows implements this randomization and page protection.
All existing Windows code that depends on the previous design must be modified in order to be compatible, as can be seen from Figure 2 above. To use the new randomized virtual address, all current locations in Windows kernel-mode code that writes to the KUSER_SHARED_DATA structure had to be changed.
The new mapping’s randomized address is stored in a new global variable called MmWriteableSharedUserData. Only NT kernel code can view this; it cannot be exported. This global variable’s location is chosen randomly because it is located inside the NT kernel, which is also chosen at random.
This new global variable contains the address of the fixed (old ) KUSERSHARED_DATA structure early on during boot before” Phase 0″ initialization. Phase 0 initialization of the OS ( initializing system page table entries, memory management paging, etc. ) The _MiProtectSharedUserPage _ function is responsible for safeguarding the mapping of KUSER_SHARED_DATA. Previously, this function only changed the permissions for the KUSERSHARED_DATA page, rendering it inoperable. The following actions are now carried out by _MiProtectSharedUserPage _ ( the flow has been changed for clarity, and they are not listed in chronological order ):-
- The existing static mapping of the KUSER_SHARED_DATA page can be changed to read-only ( no Write or Execute permissions ). The page has RWX permissions when it is first mapped during boot.
- Make a fresh randomized mapping of the physical page containing the KUSER_SHARED_DATA. Set READWRITE as the mapping’s permissions.
- Only the NTOS global variable MmWriteableSharedUserData is used to write the new randomized mapping’s virtual address.
Since the static address is now read-only and the writeable address has been randomized, this mitigation prevents remote attackers from using the KUSER_SHARED_DATA structure to exploit blind-write primitives.
Compatibility with applications
Only the internal drivers and the core OS are intended to update the KUSERSHARED_DATA structure. This structure should n’t be written or updated by external, third-party drivers. To make sure that their products are compatible, we have been collaborating with outside vendors and driver developers who are testing their code on Windows Insiders builds. A system crash will result from an incompatible third-party driver that relies on writing to the current, static address of KUSER_SHARED_DATA. Please test against current Windows Insider Preview builds and update any legacy code if you create Windows drivers in order to keep up with the most recent OS!
Release
Only 64-bit Windows ( AMD64 and ARM64 ) can use this feature, and you can test it right now using a build of the Windows Insider Program ( thanks to the external security researchers who have already noticed these changes! ). This mitigation will be included in a subsequent Windows release.
Microsoft Security Response Center ( MSRC ) employee Rohit Mothe
Thank you especially to Gov. Maharaj and Joe Bialek.