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

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:

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:

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@12this 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----|------------------------------------ VxxxUserCreateWindowEx | VUserFindAtom win32k.sys

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

RtlLookupAtomInAtomTable | VRtlpGetIntegerAtomThis 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).