In June 2019, ESET researchers identified a zero-day exploit being used in a highly targeted attack in Eastern Europe.

The exploit abuses a local privilege escalation vulnerability in Microsoft Windows, specifically a NULL pointer dereference in the win32k.sys component. Once the exploit was discovered and analyzed, it was reported to the Microsoft Security Response Center, who promptly fixed the vulnerability and released a patch.

The vulnerability affects the following Windows versions:

  • Windows 7 for 32-bit Systems Service Pack 1
  • Windows 7 for x64-based Systems Service Pack 1
  • Windows Server 2008 for 32-bit Systems Service Pack 2
  • Windows Server 2008 for Itanium-Based Systems Service Pack 2
  • Windows Server 2008 for x64-based Systems Service Pack 2
  • Windows Server 2008 R2 for Itanium-Based Systems Service Pack 1
  • Windows Server 2008 R2 for x64-based Systems Service Pack 1

This blog post focuses on the technical details of the vulnerability and its exploitation. Another post delves into the malware sample and its broader implications.

Exploitation

As with a number of other Microsoft Windows win32k.sys vulnerabilities disclosed in recent years, this exploit uses popup menu objects. For example, the Sednit group’s local privilege escalation exploit that we analyzed in 2017 used menu objects and techniques very similar to the current exploit.

This exploit creates two windows; one for the first stage and another one for the second stage of the exploitation. For the first window, it creates popup menu objects and appends menu items using the CreatePopupMenu and AppendMenu functions. In addition, the exploit sets up WH_CALLWNDPROC and EVENT_SYSTEM_MENUPOPUPSTART hooks.

Then the exploit displays a menu using the TrackPopupMenu function. At this point the code hooked to EVENT_SYSTEM_MENUPOPUPSTART gets executed. This code attempts to open as the first available item in the menu, by sending a sequence of MN_SELECTITEM, MN_SELECTFIRSTVALIDITEM and MN_OPENHIERARCHY messages to the menu.

The next step is very important for triggering this vulnerability. The exploit must catch the moment in time when the initial menu is already created, but the sub-menu is only about to be created. For that, the exploit has code that handles the WM_NCCREATE message in the WH_CALLWNDPROC hook. When the exploit code detects the system is in this state, it sends MN_CANCELMENUS (0x1E6) message to the first menu, which cancels that menu. However, its sub-menu is still about to be created.

Now if we check this sub-menu object in kernel mode, we would see that tagPOPUPMENU‑>ppopupmenuRoot equals 0. This state allows the attacker to use that element in this kernel structure as a NULL pointer dereference. The exploit allocates a new page at address 0x0 and this address will be treated as a tagPOPUPMENU object (see Figure 1) by the kernel.

Figure 1. The tagPOPUPMENU kernel structure

At this point, the attackers use the second window. The main exploit goal is to flip the bServerSideWindowProc bit in the tagWND structure of the second window. This causes the execution of a WndProc procedure in kernel mode.

To perform that, the attackers leak the kernel memory address of the tagWND structure of the second window by calling the non-exported HMValidateHandle function in the user32.dll library. Then the exploit crafts a fake tagPOPUPMENU object at the NULL page and sends a MN_BUTTONDOWN message to a sub-menu.

After that, the kernel will eventually execute the win32k!xxxMNOpenHierarchy function.

Figure 2. Disassembled code of the win32k!xxxMNOpenHierarchy function

This function passes a crafted object at the NULL page to win32k!HMAssignmentLock. The bServerSideWindowProc bit is set inside the win32k!HMDestroyUnlockedObject function, which is located a few calls deeper inside win32k!HMAssignmentLock.

Figure 3. Disassembled code of the win32k!HMDestroyUnlockedObject function

Everything is done! Now the exploit can send a specific message to the second window in order to execute WndProc in kernel mode.

As a final step, the exploit replaces the token of the current process with the system token.

The published patch, among others, added a check for a NULL pointer in win32k!xxxMNOpenHierarchy function.

Figure 4. Code differences between two win32k.sys versions – original (left) and patched (right)

Conclusion

The exploit only works against older versions of Windows, because since Windows 8 a user process is not allowed to map the NULL page. Microsoft back-ported this mitigation to Windows 7 for x64-based systems.

People who still use Windows 7 for 32-bit systems Service Pack 1 should consider updating to newer operating systems, since extended support of Windows 7 Service Pack 1 ends on January 14th, 2020. Which means that Windows 7 users won’t receive critical security updates. Thus, vulnerabilities like this one will stay unpatched forever.

Indicators of Compromise (IoCs)

SHA-1 hash ESET detection name
CBC93A9DD769DEE98FFE1F43A4F5CADAF568E321 Win32/Exploit.CVE-2019-1132.A