Saturday, May 5, 2012

Analysis of a Microsoft Windows Kernel "Win32k.sys" Local Denial of Service Vulnerability


A couple of days ago, a Microsoft Windows Kernel "Win32k.sys" Local Denial of Service Vulnerability (BID: 53343has been published on the exploit-db website: follow this link to get the poc code.
As I wanted to investigate this vulnerability, I compiled the code in Visual Studio and built the executable.
Then I ran it on a dedicated virtual machine, and…



Yeah… I got the dreaded Blue Screen Of Death! So… it worked :P

This screen gives us useful information; we can read: "PAGE_FAULT_IN_NONPAGED_AREA" and then "STOP: 0x00000050 (0xFF7C98CC, 0x00000000, 0x805D33A5, 0x00000000)".

Now, if we read the documentation we get "Bug Check 0x50: PAGE_FAULT_IN_NONPAGED_AREA". But, what does that mean?  Well, in the Windows Kernel, the memory is divided into two kinds: paged memory and nonpaged memory.
   
Basically the difference is that the former can be swapped to the disk while the latter can't. So, the error means that the kernel referenced an invalid memory location within the area reserved to the nonpaged memory.

The parameters of the Bug Check stand respectively for: memory address being referenced ("0xFF7C98CC"); read operation/write operation (0 in this case indicates that read operation mode is selected); address that referenced memory (0x805D33A5, the line of code where the crash happened); reserved (that is not set).

I opened ntkrnlpa.exe with IDA, I loaded the debug symbols and searched for the address that caused the crash, getting this:

PAGE:004FC3A5                 cmp     word ptr [ecx], 23h

This means that ecx cointans an invalid pointer.
Also, IDA tells us that we are inside the function:

PAGE:004FC370 ; int __stdcall RtlpGetIntegerAtom(ULONG CrashPointer,int)
  
Then, analyzing the code of the function:
   
PAGE:004FC372                 push    ebp
PAGE:004FC373                 mov     ebp, esp
PAGE:004FC375                 push    ecx
PAGE:004FC376                 push    ecx
PAGE:004FC377                 mov     ecx, [ebp+CrashPointer]
PAGE:004FC37A                 test    ecx, 0FFFF0000h
PAGE:004FC380                 jnz     short Crash_Dead
...
PAGE:004FC3A5 Crash_Dead:
PAGE:004FC3A5                 cmp     word ptr [ecx], 23h


we see that the invalid pointer is the first parameter of the function, and it is accessed without being properly validated.
   
Besides, we can see that the memory location being accessed is the same that is passed from the user mode POC code:
   

UINT c[]={ 
0x00000000,0x28001500,0xff7c98cc,0x23ffffff

Thus we know that the pointer from user mode is never validated or modified in any way.

From the cross-references we see that the previous function is called by these two functions:

RtlAddAtomToAtomTable(x,x,x)
RtlLookupAtomInAtomTable(x,x,x)

Both these functions don't validate the pointer either. In particular we can see it from the code of the second one:

PAGE:004FC692                 push    18h
PAGE:004FC694                 push    offset dword_4031A0
PAGE:004FC699                 call    __SEH_prolog
PAGE:004FC69E                 push    [ebp+arg_0]
PAGE:004FC6A1                 call    _RtlpLockAtomTable@4;
PAGE:004FC6A6                 test    al, al
PAGE:004FC6A8                 jnz     short loc_4FC6B4
PAGE:004FC6AA                 mov     eax, 0C000000Dh
PAGE:004FC6AF                 jmp     loc_4FC76C
PAGE:004FC6B4                 xor     edi, edi
PAGE:004FC6B6                 mov     [ebp+ms_exc.disabled], edi
PAGE:004FC6B9                 lea     eax, [ebp+var_20]
PAGE:004FC6BC                 push    eax    ; int
PAGE:004FC6BD                 mov     esi, [ebp+PointerCrash]
PAGE:004FC6C0                 push    esi   ; CrashPointer
PAGE:004FC6C1                 call    _RtlpGetIntegerAtom@8 ; 

This function is the one used in the exploit.

Again, from the cross-references we got that this function is called by NtFindAtom, which instead does valid proper validation:

PAGE:00535B81                 mov     ecx, [ebp+CopyOfCrashPointer]
PAGE:00535B87                 lea     eax, [ecx+ebx]
PAGE:00535B8A                 cmp     eax, ecx
PAGE:00535B8C                 jb      short Exception
PAGE:00535B8E                 cmp     eax, _MmUserProbeAddress
PAGE:00535B94                 jbe     short CodeOk
PAGE:00535B96                 call    _ExRaiseAccessViolation@0 ;

NtFindAtom is a dead end for us, so we switch the analysis on win32k.sys.

From the source code we see the code making a call to a function named NtUserCreateWindowEx. In particular, this code is a home made syscall to a Kernel API in win32k.sys:

       mov eax,0x1157
       mov edx,7FFE0300h
       call  dword ptr[edx]

Thus loading win32k.sys in IDA, and searching for this API, we find the function:

.text:BF8344E2 ; int __cdecl xxxUserCreateWindowEx(char,int,SIZE_T NumberOfBytes,int pData,int,int,int,int,int,int,int,int,int,int,int)

This function executes a little bit of code until it gets to the following lines:

.text:BF833E54                 mov     eax, [ebp+DataBlock]
.text:BF833E57                 test    eax, 0FFFF0000h
.text:BF833E5C                 jz      short Skip
.text:BF833E5E                 push    dword ptr [eax+8]
.text:BF833E61                 call    _UserFindAtom@4 ;

DataBlock corresponds to the buffer of data which is stored in the variable "c" in the source code.

The code takes a DWORD at the offset + 8 from such a buffer, and this DWORD is exactly 0xff7c98cc… that is... the vulnerable pointer!
   
We get into UserFindAtom, and we finally find the missing link:
   
.text:BF80DA19                 mov     edi, edi
.text:BF80DA1B                 push    ebp
.text:BF80DA1C                 mov     ebp, esp
.text:BF80DA1E                 push    ecx
.text:BF80DA1F                 and     [ebp+NtStatus], 0
.text:BF80DA23                 lea     eax, [ebp+NtStatus]
.text:BF80DA26                 push    eax
.text:BF80DA27                 push    [ebp+CrashPointer]
.text:BF80DA2A                 push    _UserAtomTableHandle
.text:BF80DA30                 call    ds:__imp__RtlLookupAtomInAtomTable@12 
   
this is where win32k.sys transfers control to the vulnerable function RtlLookupAtomInAtomTable in ntkrnlpa.exe passing the non-validated pointer.
   
This pointer was set in the exploit code to be 0xff7c98cc, which is a memory address that falls within the kernelspace region, and the problem is that nowhere in this series of calls this pointer is validated to make sure it is accessible.
  
   
  usermode app
----|------------------------------------
    V
  xxxUserCreateWindowEx
    |
    V
 UserFindAtom                win32k.sys

----|------------------------------------
    V                        ntkrnlpa.exe

 RtlLookupAtomInAtomTable
    |
    V
 RtlpGetIntegerAtom
  
   
This vulnerability does not seem to be exploitable for code execution, it looks like the security impact is a Denial Of Service.
However, we have seen that there are two functions potentially vulnerable:

RtlAddAtomToAtomTable(x,x,x)
RtlLookupAtomInAtomTable(x,x,x)
there are several calls from win32k.sys to them (I found 8 to UserFindAtom and 30 to UserAddAtom). Remember, these two functions won't validate the pointer they take in input, so it is the caller's responsibility to validate it.
   
I had a quick look at all the other functions that call these two, and at a first glance they seem to perform proper validation.
Thus I am not going to spend too much time on them, but if anyone wants and finds anything, please let me know!
   
P.S. As it is, the exploit won't work on 64 bit OSes (I tested Windows 7 Professional and Windows 8 Consumer Preview). This may be due to the pointers being 64bit sized, for one thing (or maybe win32k.sys syscall interface is not the same, I haven't checked).

No comments:

Post a Comment