MS12-008 : win32k.sys Keyboard Layout Use After Free vulnerability

MS12-008 : win32k.sys Keyboard Layout Use After Free vulnerability

Related CVE : CVE-2012-0154
Before Patch Binary : win32k.sys 6.1.7600.16920 (win7_gdr.111123-1510)
After Patch Binary : win32k.sys 6.1.7600.16948 (win7_gdr.120113-1505)

If you remember MS10-073 (stuxnet), then MS12-008 may sound familiar. Refer these articles. The analysis by Vupen, and a public exploit.

Once I diffed win32k.sys with DarunGrim, I simply searched for the patched functions which include the word “keyboard”. This led me to the function, xxxLoadKeyboardLayoutEx(), and the differences on the basic block looks interesting. For your better understanding, don’t forget this: we are going after use-after-free bug.

.text:BF815284 push eax ; Tag
.text:BF815285 push dword ptr [ebx+0Ch] ; P
.text:BF815288 call ds:__imp__ExFreePoolWithTag@8 ; ExFreePoolWithTag(x,x)
.text:BF81528E push ebx
.text:BF81528F call _HMMarkObjectDestroy@4 ; HMMarkObjectDestroy(x)
.text:BF815294 push ebx
.text:BF815295 call _HMUnlockObject@4 ; HMUnlockObject(x)

This is before the patch.

.text:BF815293 push edi ; Address
.text:BF815294 call _DestroyKF@4 ; DestroyKF(x)

This is after the patch. As you can see, the destructor routines has been wrapped up into DestroyKF() in the patch. Let’s see the inside of DestroyKF() function then.

.text:BF8E3F59 ; int __stdcall DestroyKF(PVOID Address)
.text:BF8E3F59 _DestroyKF@4 proc near ; CODE XREF: xxxLoadKeyboardLayoutEx(x,x,x,x,x,x,x,x,x)+1D6p
.text:BF8E3F59 ; DestroyKL(x)+37p ...
.text:BF8E3F59
.text:BF8E3F59 Address = dword ptr 8
.text:BF8E3F59
.text:BF8E3F59 mov edi, edi
.text:BF8E3F5B push ebp
.text:BF8E3F5C mov ebp, esp
.text:BF8E3F5E push esi
.text:BF8E3F5F mov esi, [ebp+Address]
.text:BF8E3F62 push esi
.text:BF8E3F63 call _HMMarkObjectDestroy@4 ; HMMarkObjectDestroy(x)
.text:BF8E3F68 test eax, eax
.text:BF8E3F6A jz short loc_BF8E3F83
.text:BF8E3F6C push esi
.text:BF8E3F6D call _RemoveKeyboardLayoutFile@4 ; RemoveKeyboardLayoutFile(x)
.text:BF8E3F72 push 0 ; Tag
.text:BF8E3F74 push dword ptr [esi+0Ch] ; P
.text:BF8E3F77 call ds:__imp__ExFreePoolWithTag@8 ; ExFreePoolWithTag(x,x)
.text:BF8E3F7D push esi ; Address
.text:BF8E3F7E call _HMFreeObject@4 ; HMFreeObject(x)
.text:BF8E3F83
.text:BF8E3F83 loc_BF8E3F83: ; CODE XREF: DestroyKF(x)+11j
.text:BF8E3F83 pop esi
.text:BF8E3F84 pop ebp
.text:BF8E3F85 retn 4
.text:BF8E3F85 _DestroyKF@4 endp

Oh yes. Though there’s some changes in the order to invoke the free/destructor functions, but the most interesting point should be _RemoveKeyboardLayoutFile@4(): _RemoveKeyboardLayoutFile@4() wasn’t invoked in the old version, but the new version invoked.

Let’s see what _RemoveKeyboardLayoutFile() does.

.text:BF8E3F8D ; __stdcall RemoveKeyboardLayoutFile(x)
.text:BF8E3F8D _RemoveKeyboardLayoutFile@4 proc near ; CODE XREF: DestroyKF(x)+14p
.text:BF8E3F8D
.text:BF8E3F8D arg_0 = dword ptr 8
.text:BF8E3F8D
.text:BF8E3F8D mov edi, edi
.text:BF8E3F8F push ebp
.text:BF8E3F90 mov ebp, esp
.text:BF8E3F92 mov ecx, [ebp+arg_0]
.text:BF8E3F95 mov eax, _gpKbdTbl
.text:BF8E3F9A cmp eax, [ecx+10h]
.text:BF8E3F9D jnz short loc_BF8E3FA9
.text:BF8E3F9F mov _gpKbdTbl, offset _KbdTablesFallback
.text:BF8E3FA9
.text:BF8E3FA9 loc_BF8E3FA9: ; CODE XREF: RemoveKeyboardLayoutFile(x)+10j
.text:BF8E3FA9 mov eax, _gpKbdNlsTbl
.text:BF8E3FAE cmp eax, [ecx+18h]
.text:BF8E3FB1 jnz short loc_BF8E3FBA
.text:BF8E3FB3 and _gpKbdNlsTbl, 0
.text:BF8E3FBA
.text:BF8E3FBA loc_BF8E3FBA: ; CODE XREF: RemoveKeyboardLayoutFile(x)+24j
.text:BF8E3FBA mov eax, _gpkfList
.text:BF8E3FBF cmp ecx, eax
.text:BF8E3FC1 jnz short loc_BF8E3FCD
.text:BF8E3FC3 mov eax, [ecx+8]
.text:BF8E3FC6 mov _gpkfList, eax
.text:BF8E3FCB jmp short loc_BF8E3FDC
.text:BF8E3FCD ; ---------------------------------------------------------------------------
.text:BF8E3FCD
.text:BF8E3FCD loc_BF8E3FCD: ; CODE XREF: RemoveKeyboardLayoutFile(x)+34j
.text:BF8E3FCD ; RemoveKeyboardLayoutFile(x)+47j
.text:BF8E3FCD mov edx, eax
.text:BF8E3FCF mov eax, [eax+8]
.text:BF8E3FD2 cmp ecx, eax
.text:BF8E3FD4 jnz short loc_BF8E3FCD
.text:BF8E3FD6 mov eax, [eax+8]
.text:BF8E3FD9 mov [edx+8], eax
.text:BF8E3FDC
.text:BF8E3FDC loc_BF8E3FDC: ; CODE XREF: RemoveKeyboardLayoutFile(x)+3Ej
.text:BF8E3FDC pop ebp
.text:BF8E3FDD retn 4
.text:BF8E3FDD _RemoveKeyboardLayoutFile@4 endp

Given the data structure as an argument, RemoveKeyboardLayoutFile() loops over the linked list in which the head pointer is stored the global variable _gpkfList. While traversing over the linked list, RemoveKeyboardLayoutFile() tries to find the identical entry with the argument, then remove it from the linked list. Ok, fair enough. This logic also conforms with the function name.

Then our question should be “which function initialize (or update) this linked list ? By searching for the cross reference on _gpkfList, you will find LoadKeyboardLayoutFile() is updating this data structure. With the given argument, it first checks whether the given one is already loaded. If it’s not, then insert into the linked list.

Alright. Now we can see why this is a use-after-free bug. When xxxLoadKeyboardLayoutEx() is trying to destroy the target data structure, it forgot to clean up the linked list. Thus, even after the actual buffer is freed, the linked list still says there’s a target data structure. So, if we try to access that data structure with the same information, it will use the buffer pointer, which would be already freed.

The last question is how to reach to the vulnerable point (destructor rouintes). Again, going back to the destructor routine, then you will find it is reachable when HMAllocObject() fails.

.text:BF815271 push 44h ; NumberOfBytes
.text:BF815273 push 0Dh ; char
.text:BF815275 push 0 ; int
.text:BF815277 push 0 ; int
.text:BF815279 call _HMAllocObject@16 ; HMAllocObject(x,x,x,x)
.text:BF81527E mov esi, eax
.text:BF815280 test esi, esi
.text:BF815282 jnz short loc_BF8152B4

How to make the allocation fail ? Refer Tarjei Mandt’s comment on twitter: “You can exhaust the user handle table or try to exceed the handle quota limit (win32k!gUserProcessHandleQuota)”. Note that he’s name is on MS12-008 vulnerability description 🙂

I’ve shortly tried this, but it doesn’t work @.@ Cause I don’t want to waste my time by doing a brute force like approaches, I simply bought a Windows Internals book ! 🙂

Update : Forgot to mention the upcoming patch on another keyboard layout vulnerability (still zero-day!!), which is disclosed by the 0day exploit in wild. The description on this bug is as follows.The keyboard layout file (just like .dll file) can be loaded into the kernel. Interestingly, a keyboard layout file contains the hard-coded address pointer 🙂 Later, the kernel accesses the user space address by creating the mapping, but the kernel does not validate the user address pointer located in the keyboard layout file.This eventually crashes the system. Yes, this is a place holder for MSxx-xxx.

2 thoughts on “MS12-008 : win32k.sys Keyboard Layout Use After Free vulnerability

  1. JJ

    Nice analysis!
    Seems like the “0-day” however wouldn’t work on newer systems, as they check if the keyboard files were from system folder. It does seems like someone is fuzzing it though.

    Reply

Leave a comment