Subtle information disclosure in WIN32K.SYS syscall return values

While performing some random research related to the WIN32K.SYS driver syscalls a few months ago, I stumbled on an interesting finding – when examining the full 32-bit (or in the case of the original research – 64) return values, some of the services seemed to return unusual numbers, for example 0xfffffa8000ea0000. After investigating the subject for a while, it turned out that a part of the WIN32K syscall interface indeed does not always return a value of the native CPU size (as opposed to the core NT kernel). Instead, some of the graphical services seem to be explicitly declared as:

VOID NtUserRandomService( ... )

 or

USHORT NtUserRandomService( ... )

Funny enough to find such types of quirks in the Windows kernel, eh? A list of the flawed syscall names is as follows:

  • ETHREAD (full disclosure)
    • NtUserAlterWindowStyle
    • NtUserSetThreadState
    • NtUserNotifyWinEvent
    • NtUserModifyUserStartupInfoFlags
    • NtUserSetThreadLayoutHandles
    • NtUserNotifyIMEStatus
    • NtUserSetThreadLayoutHandles
    • NtUserSetRipFlags (Windows XP only)
    • NtUserSetDbgTag (Windows XP only)
  • ETHREAD (partial disclosure)
    • NtUserRegisterClassExWOW
    • NtUserGetKeyState
    • NtUserGetAsyncKeyState
    • NtUserVkKeyScanEx
    • NtUserSetWindowWord
    • NtUserSetClassWord
  • W32THREAD (full disclosure)
    • NtGdiEngUnlockSurface
    • NtGdiPATHOBJ_vEnumStartClipLines
    • NtGdiEngDeletePath
    • NtGdiEngDeleteClip
    • NtGdiFONTOBJ_vGetInfo
  • Other (unknown?)
    • NtGdiFlush
    • NtGdiEngUnlockSurface

The above list was created based on the Windows 7 64-bit graphical system call table, although I do not guarantee it is by any means complete (system calls might have been added, removed, or altered since then). As can be seen, a user-mode application is primarily able to read the addresses of two, internal kernel-mode structures (assigned to the currently executed thread, thus no serious information disclosure is going on, here). These are:

  • ETHREAD – the very standard NT kernel structure, assigned to every single thread running in the system,
  • W32THREAD – an internal, and pretty much unexplored structure, allocated on demand (i.e. when the thread calls a win32k service for the first time). For more details, you’re advised to take a look at the Mysteries of win32k & GDI post on the Woodmann RCE Forums.

In order to confirm the story a little, let’s take a look at the example NtUserGetKeyState system call epilogue:

mov     [ecx+48h], edx
mov     eax, [eax+70h]
mov     [ecx+4Ch], eax
call    _LeaveCrit@0    ; LeaveCrit()
mov     ax, di
pop     edi
pop     esi
pop     ebp
retn    4

Clearly, the code doesn’t care about the upper 16 bits of the return value (leading to a partial disclosure) – that’s also the case for the rest of the aforementioned routines. A complete log from a Windows 7 64-bit instance of a Proof of Concept code follows:

--- 64bit Kernel-address disclosure:
[+] [ETHREAD] NtUserAlterWindowStyle          : 0xfffffa8000ea80b0
[+] [ETHREAD] NtUserSetThreadState            : 0xfffffa8000ea80b0
[+] [ETHREAD] NtUserNotifyWinEvent            : 0xfffffa8000ea80b0
[+] [ETHREAD] NtUserModifyUserStartupInfoFlags: 0xfffffa8000ea80b0
[+] [ETHREAD] NtUserSetThreadLayoutHandles    : 0xfffffa8000ea80b0
[+] [ETHREAD] NtUserNotifyIMEStatus           : 0xfffffa8000ea80b0
[+] [ETHREAD] NtUserSetThreadLayoutHandles    : 0xfffffa8000ea80b0
[+] [W32THREAD] NtGdiEngUnlockSurface         : 0xfffff900c216f010
[+] [W32THREAD] NtGdiPATHOBJ_vEnumStartClipLines: 0xfffff900c216f010
[+] [W32THREAD] NtGdiEngDeletePath            : 0xfffff900c216f010
[+] [W32THREAD] NtGdiEngDeleteClip            : 0xfffff900c216f010
[+] [W32THREAD] NtGdiFONTOBJ_vGetInfo         : 0xfffff900c216f010

--- 48bit Kernel-address disclosure (the AX value is uncontrolled):
[+] [ETHREAD] NtUserRegisterClassExWOW        : 0xfffffa8000ea0000
[+] [ETHREAD] NtUserGetKeyState               : 0xfffffa8000ea0000
[+] [ETHREAD] NtUserGetAsyncKeyState          : 0xfffffa8000ea0000
[+] [ETHREAD] NtUserVkKeyScanEx               : 0xfffffa8000ea0332
[+] [ETHREAD] NtUserSetWindowWord             : 0xfffffa8000ea0000
[+] [ETHREAD] NtUserSetClassWord              : 0xfffffa8000ea0000

Since the disclosed addresses do not pose a direct threat to the system security, the MSRC team did not consider the issue worth fixing. Well, although I do agree that it’s a minor issue, it is still amusing for me to find such peculiarities in the main Windows graphical kernel module ;) No source code this time, although I do encourage you to fire your IDA and verify the findings by yourself (Windows WIN32K.SYS System Call Table might come in handy).

Take care!

PS. It seems that I wasn’t the only one coming across this matter – Tavis Ormandy has apparently tweeted about it a year ago, as well ;) Nice one.

PS2. I received the first, valid submission for Pimp CrackMe – a reversing challenge by Gynvael and me (see: link). Well done!

11 thoughts on “Subtle information disclosure in WIN32K.SYS syscall return values”

  1. Interesting stuff.
    It would be nice if some win32k syscall handlers allowed to store in rax bytes from a caller-controlled address.

  2. @Crash: Yeah, it would certainly be nice ;)
    In such a case, I guess MSRC would take the time and issue a patch, as it’d be quite a robust Information Disclosure vuln (which I think win32k.sys has plenty of, by the way).

  3. Nice post!!!, as jf said, it could be very useful in some scenarios where you need to do a transition ring3->ring0.

  4. @jf @NCR: Yeah, this kind of information might definitely come in handy in ring-0 exploitation ;) There are quite a few curiosities in the kernel user/gdi implementation, more coming soon.

    It is worth to note that the ETHREAD address can be currently obtained using a NtQuerySystemInformation(SystemHandleInformation) undocumented call. AFAIK, there’s no such “official” method for the second internal structure, W32THREAD.

  5. This useless information, have a legal way to get a reference to the object:

    QueryObject proc uses ebx ObjectHandle:HANDLE, Object:PVOID
    Local SystemInformation:PVOID, SystemInformationLength:ULONG
    Local ProcessInformation:PROCESS_BASIC_INFORMATION
    Local HandleInformation[sizeof(SYSTEM_HANDLE_INFORMATION) + 4]:BYTE
    invoke ZwQueryInformationProcess, NtCurrentProcess, ProcessBasicInformation, addr ProcessInformation, sizeof(PROCESS_BASIC_INFORMATION), NULL
    test eax,eax
    jnz Exit
    mov SystemInformation,eax
    invoke ZwQuerySystemInformation, SystemHandleInformation, addr HandleInformation, sizeof(SYSTEM_HANDLE_INFORMATION) + 4, addr SystemInformationLength
    cmp eax,STATUS_INFO_LENGTH_MISMATCH
    jne Exit
    cmp SystemInformationLength,NULL
    je Exit
    add SystemInformationLength,50*sizeof(SYSTEM_HANDLE_INFORMATION)
    invoke ZwAllocateVirtualMemory, NtCurrentProcess, addr SystemInformation, 0, addr SystemInformationLength, MEM_COMMIT, PAGE_READWRITE
    test eax,eax
    jnz Exit
    invoke ZwQuerySystemInformation, SystemHandleInformation, SystemInformation, SystemInformationLength, Eax
    test eax,eax
    mov edx,SystemInformation
    jnz Error
    mov ebx,ProcessInformation.UniqueProcessId
    mov ecx,dword ptr [edx]
    mov eax,ObjectHandle
    add edx,4
    assume edx:PSYSTEM_HANDLE_INFORMATION
    Next:
    cmp [edx].ProcessId,ebx
    jne @f
    cmp [edx].Handle,ax
    je Objext
    @@:
    add edx,sizeof(SYSTEM_HANDLE_INFORMATION)
    loop Next
    mov eax,STATUS_NOT_FOUND
    jmp Error
    Objext:
    mov edx,[edx].Object
    mov ebx,Object
    xor eax,eax
    mov dword ptr [ebx],edx
    Error:
    push eax
    invoke ZwFreeVirtualMemory, NtCurrentProcess, addr SystemInformation, addr SystemInformationLength, MEM_RELEASE
    pop eax
    Exit:
    ret
    QueryObject endp

  6. @Indy: Yeah, I do realize that it is possible to retrieve the ETHREAD address by other means (namely, the NtQuerySystemInformation service). I even mentioned it in one of my comment replies:

    It is worth to note that the ETHREAD address can be currently obtained using a NtQuerySystemInformation(SystemHandleInformation) undocumented call.

Comments are closed.