Microsoft addressed several Windows kernel vulnerabilities in the MS12-075 security bulletin released in November this year, some of them residing in every version of the win32k.sys driver shipped with the NT family line systems. Apart from the obviously extremely interesting remote web browser => ring-0 arbitrary code execution issue, there have also been two other Local Privilege Escalation bugs, at least one of which was directly related to the management of legacy 16-bit applications running within a VDM (Virtual DOS Machine). Since the topics of use-after-free vulnerabilities in the Windows kernel – and especially in old and poorly understood functionality – seems to be very appealing for most, this post aims to cover some of the technical details related to that particular security flaw. In addition to (hopefully) having some didactic and entertainment value, this write-up and the very existence of that bug illustrates how trivial it still is to find elevation of privileges vulnerabilities in undocumented, rarely used features present in the Windows operating system since more than fifteen or twenty years by now. As a side note, a similar (yet unrelated) issue in the same code area has been previously found by Tarjei Mandt back in 2010 and documented in his excellent “CVE-2010-3941: Windows VDM Task Initialization Vulnerability” post over two years ago. There is also more evidence of Windows VDM and its kernel-mode support (WOW32) being subject of in-depth security research in the past: for example, see “Microsoft Windows NT #GP Trap Handler Allows Users to Switch Kernel Stack” (Pwnie Award Winner) or “EEYE: Windows VDM Zero Page Race Condition Privilege Escalation“.
The vulnerability
Some basic information regarding internal structures used by the WOW-related win32k.sys system calls has already been described by Tarjei in his article. In short, GUI threads can call a “public” win32k!xxxRegisterUserHungAppHandlers routine (being a part of the win32k!apfnSimpleCall interface) either through the win32k!NtUserCallTwoParam system call or user32!RegisterUserHungAppHandlers – a convenient user-mode wrapper. The service is purposed to be used within the NTVDM.EXE process, a container for 16-bit DOS apps. It is primarily responsible for allocating a WOWPROCESSINFO kernel structure, assigning it to an internal ppiCurrent->pwpi field within PROCESSINFO (corresponding to the current process) and inserting into a linked list pointed to by a global win32k!gpwpiFirstWow symbol as shown on the following listing:
.text:BF9785DA push 'pwsU' ; Tag .text:BF9785DF push 28h ; NumberOfBytes .text:BF9785E1 call _Win32AllocPoolWithQuotaTagZInit@8 .text:BF9785E6 mov esi, eax [...] .text:BF97861F call ds:__imp__PsGetCurrentProcessWin32Process@0 [...] .text:BF97862E mov [eax+PROCESSINFO.pwpi], esi .text:BF978634 mov eax, _gpwpiFirstWow .text:BF978639 mov [esi], eax .text:BF97863B mov _gpwpiFirstWow, esi
As you can imagine, the code quality of the vulnerable routine wasn’t (perhaps still isn’t) exceptionally good; it assumed that a process would only call it once during its entire lifespan (the exact same root cause as with Tarjei’s bug), hence it wouldn’t verify whether a WOWPROCESSINFO had already been allocated before, but would simply overwrite the existing ppiCurrent->pwpi pointer and push “duplicate” entries onto the gpwpiFirstWow list, in case xxxRegisterUserHungAppHandlers was called more than one time.
Further on, when the NTVDM.EXE process terminates and its internal structures are freed in win32k!DestroyProcessInfo, only the ppiCurrent->pwpi WOWPROCESSINFO structure is removed from the system-wide linked list, leaving all previous allocations untouched (effectively causing a memory leak):
.text:BF8D8DE6 mov eax, offset _gpwpiFirstWow .text:BF8D8DEB cmp _gpwpiFirstWow, edx .text:BF8D8DF1 jz short loc_BF8D8E05 .text:BF8D8DF3 .text:BF8D8DF3 loc_BF8D8DF3: ; CODE XREF: DestroyProcessInfo(x)+26D j .text:BF8D8DF3 mov ecx, [eax] .text:BF8D8DF5 cmp ecx, edi .text:BF8D8DF7 jz short loc_BF8D8E01 .text:BF8D8DF9 mov eax, ecx .text:BF8D8DFB cmp [eax], edx .text:BF8D8DFD jz short loc_BF8D8E05 .text:BF8D8DFF jmp short loc_BF8D8DF3 .text:BF8D8E01 ; --------------------------------------------------------------------------- .text:BF8D8E01 .text:BF8D8E01 loc_BF8D8E01: ; CODE XREF: DestroyProcessInfo(x)+265 j .text:BF8D8E01 mov ecx, [edi] .text:BF8D8E03 mov [eax], ecx .text:BF8D8E05 .text:BF8D8E05 loc_BF8D8E05: ; CODE XREF: DestroyProcessInfo(x)+25F j .text:BF8D8E05 ; DestroyProcessInfo(x)+26B j .text:BF8D8E05 push edx ; Tag .text:BF8D8E06 push edi ; P .text:BF8D8E07 call ebx ; ExFreePoolWithTag(x,x) ; ExFreePoolWithTag(x,x) .text:BF8D8E09 xor edi, edi .text:BF8D8E0B mov [esi+0A4h], edi .text:BF8D8E11 jmp short loc_BF8D8E15
The stale WOWPROCESSINFO structures in gpwpiFirstWow are still publicly accessible thanks to the win32k!NtUserPostThreadMessage system service, which is implemented in such a way that it lists all items in gpwpiFirstWow, then for each of them iterates through their corresponding TDB structures (starting from WOWPROCESSINFO.ptdbHead), and if ptdb->hTaskWow is equal to the user-specified “id” parameter, the function takes ptdb->pti as the THREADINFO structure to operate on.
Without going into much detail, the ppiCurrent->pwpi->ptdbHead field can be populated by win32k!InsertTask, called by win32k!zzzInitTask, called by win32k!NtUserInitTask. The overall list of steps required to trigger the vulnerability is as follows:
- Spawn an NTVDM.EXE subsystem by running a 16-bit application.
- Inject code into NTVDM and execute it:
- Call win32k!xxxRegisterUserHungAppHandlers to register a WOWPROCESSINFO structure used later for exploitation.
- Call win32k!NtuserInitTask to populate ppiCurrent->pwpi->ptdbHead with a TDB structure referencing the current thread, and hTaskWow set to a custom value (e.g. 0x1337)
- Call win32k!xxxRegisterUserHungAppHandlers again to overwrite ppiCurrent->pwpi and insert a second item into gpwpiFirstWow.
- Call ExitProcess, resulting in destroying current thread, and freeing the WOWPROCESSINFO structure allocated in step 2.3. (but not the one from 2.1.)
- Call win32k!NtuserPostThreadMessage with the “id” parameter set to 0x1337, resulting in the routine using a PTHREADINFO pti address pointing at a freed structure describing the thread destroyed in step 2.1.
Practical exploitation of the vulnerability has proven to be non-trivial, but definitely feasible. Considering the nature of the flaw, it requires that the attacker fills the memory region previously allocated for the THREADINFO structure with controlled data, requiring a high degree of control over the kernel session pools’ management and layout. Reproducing a system bugcheck and potentially developing a reliable exploit is left as an excercise for the reader :-) I hope you enjoyed the post, and Merry Christmas to everyone!
I’d like to know why old code like this that sits at the boundary of the ABI to user-mode is never audited. We *know* that code like this produces security bugs. New code in Windows is of much higher quality than the old stuff.
The same goes for file format parsers. Why did nobody audit the ANI and LNK formats? Other examples exist.
This security bug exists because this code was never reviewed according to modern guidelines. This code is obviously crap and would have been detected.
The good news is that NTVDM is finally disabled by default in Win8!
Hello. I’ve tried to build a poc of this vuln. When I use systemcall to call NtUserInitTask, I don’t know how the argument is filled. Could you give me some suggestion?