Skip to content

CVE-2012-2553: Windows Kernel VDM use-after-free in win32k.sys

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

Relations between WOW structures and pointers after a single xxxRegisterUserHungAppHandlers call.

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.

Relations between all WOW structures after calling xxxRegisterUserHungAppHandlers twice, and initializing TBD structures with NtUserInitTask.

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

Inconsistent thread pointer in the TDB structure upon terminating the offending VDM container process and freeing internal structures.

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:

  1. Spawn an NTVDM.EXE subsystem by running a 16-bit application.
  2. Inject code into NTVDM and execute it:
    1. Call win32k!xxxRegisterUserHungAppHandlers to register a WOWPROCESSINFO structure used later for exploitation.
    2. 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)
    3. Call win32k!xxxRegisterUserHungAppHandlers again to overwrite ppiCurrent->pwpi and insert a second item into gpwpiFirstWow.
    4. Call ExitProcess, resulting in destroying current thread, and freeing the WOWPROCESSINFO structure allocated in step 2.3. (but not the one from 2.1.)
  3. 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!

{ 4 } Comments

  1. tobi | 19-Dec-12 at 15:42:21 | Permalink

    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.

  2. tobi | 19-Dec-12 at 15:43:30 | Permalink

    This security bug exists because this code was never reviewed according to modern guidelines. This code is obviously crap and would have been detected.

  3. Yuhong Bao | 19-Dec-12 at 23:40:24 | Permalink

    The good news is that NTVDM is finally disabled by default in Win8!

  4. tkonce | 22-Apr-13 at 19:10:06 | Permalink

    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?

Post a Comment

Your email is never published nor shared. Required fields are marked *