The recent Microsoft security patch uncovered a critical privilege escalation vulnerability within win32k, which has already been exploited in real-world attacks. While this vulnerability seems to be non-exploitable on the Win11 system version, it poses a significant risk to earlier systems. Exploitation of such vulnerabilities has a notorious track record, and in this in-depth analysis, we delve into the methods employed by threat actors to exploit this specific vulnerability, taking into account evolving mitigation measures.
Our comprehensive analysis was performed on Server 2016.
Delving into the patch, a closer examination reveals a rather intriguing issue that initially eludes immediate identification. It appears that the reference count of a particular object undergoes multiple increments:
However, due to the antiquity of the win32k code, we found some early source code comments:
This valuable insight provided us with a clearer understanding of the underlying issue. It appears that the previous code implementation focused solely on locking the window object, inadvertently neglecting to lock the menu object nested within the window object.
To comprehend how incorrect referencing of the menu object within this window can occur, we conducted a thorough analysis of the vulnerability function context. During our investigation, we uncovered a notable issue that warrants attention.
While the menu passed to xxxEnableMenuItem() is typically locked in the higher-level function, determining precisely which menu object should be safeguarded in this particular scenario poses a crucial question that demands clarification.
Continuing our in-depth analysis within the xxxEnableMenuItem function, we carefully examined the potential processing of the menu object. Our persistent investigation has revealed a significant finding: the menu returned by the MenuItemState function can encompass not only the main menu within the window but also extend to submenus, and in some cases, even sub-submenus.
To demonstrate the exploit in a proof-of-concept (POC) scenario, we meticulously constructed a customized menu structure consisting of three hierarchical levels, each comprising four distinct menus.
The menus exhibit a parent-child relationship, wherein menu D serves as a submenu of menu C. These menus possess specific characteristics that hold relevance to the detection and assessment within the xxxEnableMenuItem function, offering insights into the root cause of this vulnerability. Let’s examine these characteristics in detail:
- Menu D requires an ID type aligned with one of the system menu types, such as 0xf060 (close menu). This specific ID type is critical for subsequent menu traversal processes within the xxxEnableMenuItem function.
- Menu A also needs to be a system menu to facilitate the identification of the specified system menu type (0xf060) within the submenu of menu C. The presence of the desired system menu type in menu A prematurely terminates the menu search process. To ensure accurate detection, we must remove the corresponding system menu type (0xf060) from menu A.
- To achieve complete release of menu C during the return to the user layer process, any references to menu C within menu B must be eliminated. This step is essential to ensure proper memory deallocation.
- The necessity and underlying principle of menu B remain uncertain. However, omitting menu B seems to impede the smooth release of menu C, suggesting its potential role in resolving difficulties encountered during the release process.
During the return process to the user layer within xxxRedrawTitle, a critical step involves removing the reference relationship between menu C and menu B. This action effectively releases menu C. As we reach the return point of xxxRedrawTitle within the kernel function xxxEnableMenuItem, the subsequent reference to the menu C object becomes invalid.
A. Overall Approach
When approaching the exploitation of this vulnerability, it is essential to conduct preliminary assessments to avoid investing significant time in solutions that may not bypass critical issues. This process is common when analyzing other vulnerability Proof of Concepts (PoCs) or exploits. Prior to constructing the exploit for this particular vulnerability, we primarily considered two directions:
Based on earlier CVE-2017-0263 and CVE-2016-0167 exploits, one approach would involve executing shellcode. However, we decided against pursuing this approach due to the potential challenges associated with executing shellcode entry points and security mechanisms like SMEP in higher versions of Windows where this vulnerability exists.
Modifying Token Address Using Read-Write Primitives
In the past two years, publicly available exploits have demonstrated the ability to modify token addresses using read-write primitives. These exploits have shown a persistent generality in terms of desktop heap memory layout and read-write primitives in the desktop heap.
The primary challenge lies in understanding how to control the value of cbwndextra, particularly when reusing UAF (Use-After-Free) memory. Consequently, we divided the entire exploit into two distinct problems: (1) controlling the value of cbwndextra using the UAF vulnerability, and (2) establishing a stable approach for read-write primitives after gaining control of the cbwndextra value.
B. Writing the First Data
When triggering the vulnerability initially, the system may not always crash. This is because our triggering method eliminates all other associations of the reused vulnerability in the system. The system may erroneously employ the window object data of the memory we control only within the functions MNGetPopupFromMenu() and xxxMNUpdateShownMenu() of xxxEnableMenuItem.
To occupy the released menu object during the vulnerability trigger, we utilize the window name object in the window class WNDClass. The first data write occurs within this process, where we identify a suitable location within the address structure we construct. This location allows us to write data arbitrarily, even if it’s just a single byte (which can be written to the high bits of cbwndextra).
This intricate process can be likened to navigating through a complex maze, where every step requires careful consideration and analysis. However, due to the complexity and intricacy involved, it is not feasible to delve into the details of this process within the scope of this discussion.
In the xxxRedrawWindow function, we have identified two alternative approaches to address the manipulation of adjacent memory data. Let’s explore these approaches in more detail:
Approach Using GreCreateRectRgnIndirect
One option involves utilizing GreCreateRectRgnIndirect, but it presents two challenges. Firstly, controlling the preceding 8 bits of cbwndextra proves to be difficult as it only briefly becomes 1 under specific limited conditions. Secondly, when employing this approach, other relative offsets of cbwndextra will point to the last 8 bits of the preceding object (regardless of the object type). These last 8 bits typically represent a secure byte at the end of a heap linked list and are not easily manipulatable.
Approach Relying on flag bit’s AND 2 Operation
Given the difficulties associated with the first approach, we opted for an alternative approach that involved manipulating a flag bit using the AND 2 operation. However, controlling the secure byte at the end of the heap linked list remained a challenge. To overcome this obstacle, we modified our strategy. Instead of writing to the cb-extra of the window object, we directed our write operation to the cb-extra of HWNDClass. The latter’s cb-extra has a smaller offset relative to the former’s cb-extra, allowing us to manipulate the memory data of the preceding object as a parameter for flag checking in the xxxRedrawWindow function.
C. Ensuring Stable Memory Layout
To achieve a stable memory layout, we carefully design the memory structure with specific considerations. Here are the steps involved:
- Consecutive HWND Objects: We arrange the memory layout to have a minimum of three consecutive HWND objects, each with a size of 0x250.
- Releasing and Occupying Objects: We release the middle HWND object and then construct an HWNDClass object with a size of 0x250.
- Parameter for Flag Validation: The tail data of the preceding HWND object, which we have released and occupied with the HWNDClass object, serves as the parameter for flag validation in the xxxRedrawWindow function.
- Read and Write Primitives: The menu object of the succeeding HWND object, accompanied by its corresponding HWNDClass object, serves as a medium for stable read and write primitives.
- Sizing and Order Validation: To maintain consistency and stability, we aim to make the window object and our HWNDClass object of the same size. The size of the window object’s extended data should be large enough to modify the additional data size parameter of the first window class object, as mentioned previously.
- Verifying Object Order: To ensure the allocated window objects are in the expected order, we utilize the leaked kernel handle address present in the heap memory.
D. Modification to Read and Write Primitives
To facilitate our exploit, we continue to utilize GetMenuBarInfo() as an arbitrary read primitive, allowing us to retrieve information from memory locations of our choosing. For the arbitrary write primitive, we leverage SetClassLongPtr() to perform memory writes. However, it’s important to note that the replacement of token write operation, necessary for achieving our objectives, relies on the second window’s class object.
The State of Win32k: The history of Win32k vulnerabilities is well-documented. However, in the latest Windows 11 preview version, Microsoft has taken steps to refactor this specific portion of the kernel code using the Rust programming language. This refactoring effort holds the potential to mitigate or even eliminate such vulnerabilities in the future, showcasing Microsoft’s commitment to enhancing system security.
Exploiting the Vulnerability: Exploiting this particular vulnerability does not generally pose significant challenges. Apart from diligently exploring different methods to gain control over the first write operation using the reoccupied data from freed memory, there is typically no need for novel exploitation techniques. This type of vulnerability heavily relies on leaked desktop heap handle addresses. While there may have been some modifications, if this issue is not thoroughly addressed, it remains a security risk for older systems.
Discovery of the Vulnerability: Analyzing this vulnerability leads us to speculate that its discovery may depend on comprehensive code coverage testing. By ensuring that the system API reaches the deepest vulnerability point along the target function’s execution path and considering the nested referencing nature of the window object, this vulnerability could potentially be identified through fuzzing techniques. Conducting thorough code analysis and testing plays a crucial role in discovering similar vulnerabilities of this nature.
Detecting Vulnerability Exploitation: In addition to identifying critical points within the vulnerability-triggering function, detecting abnormal offset reads and writes related to memory layout, window objects, or window class extra data can be an effective approach for detecting and mitigating similar vulnerabilities. Focusing on these aspects during vulnerability exploitation detection efforts can aid in proactively addressing vulnerabilities of this kind.
Here is a demonstration made by our lab team of the exploit: