Analysis of Windows kernel information disclosure from CVE-2017-0167
In CVE-2017-0167 Google Security found a Windows kernel information disclosure through the user mode callback user32!fnINLPUAHDRAWMENUITEM. A sub-function of CreateWindow issues a syscall that in return calls a user mode callback with a copy of a data structure from kernel space. This data structure is copied from the stack while executing in the kernel, and a portion of that structure is uninitialised, meaning that it can contain stale stack data that can be used to glean information from the kernel in order to defeat protections like KASLR.
Google Security included a brief write up of the bug and even supplied a PoC to demonstrate it, but didn't go into detail about how the bug actually works. Given that the bug is actually pretty straightforward I think it's worth going into a little bit deeper and getting a better idea of what's going on under the hood and how an oversight as simple as not initialising a structure lead to a kernel to userspace information disclosure.
The analysis will be done on Windows 10 x64 binaries.
First, let's look at the call stack from the PoC blurb:
a75e6a8c 81b63813 nt!memcpy
a75e6aec 9b1bb7bc nt!KeUserModeCallback+0x163
a75e6c10 9b14ff79 win32kfull!SfnINLPUAHDRAWMENUITEM+0x178
a75e6c68 9b1501a3 win32kfull!xxxSendMessageToClient+0xa9
a75e6d20 9b15361c win32kfull!xxxSendTransformableMessageTimeout+0x133
a75e6d44 9b114420 win32kfull!xxxSendMessage+0x20
a75e6dec 9b113adc win32kfull!xxxSendMenuDrawItemMessage+0x102
a75e6e48 9b1138f4 win32kfull!xxxDrawMenuItem+0xee
a75e6ecc 9b110955 win32kfull!xxxMenuDraw+0x184
a75e6f08 9b11084e win32kfull!xxxPaintMenuBar+0xe1
a75e6f34 819a8987 win32kfull!NtUserPaintMenuBar+0x7e
a75e6f34 77d74d50 nt!KiSystemServicePostCall
← Transition to kernel mode →
00f3f08c 7489666a ntdll!KiFastSystemCallRet
00f3f090 733ea6a8 win32u!NtUserPaintMenuBar+0xa
00f3f194 733e7cef uxtheme!CThemeWnd::NcPaint+0x1fc
00f3f1b8 733ef3c0 uxtheme!OnDwpNcActivate+0x3f
00f3f22c 733ede88 uxtheme!_ThemeDefWindowProc+0x800
00f3f240 75d8c2aa uxtheme!ThemeDefWindowProcW+0x18
00f3f298 75d8be4a USER32!DefWindowProcW+0x14a
00f3f2b4 75db53cf USER32!DefWindowProcWorker+0x2a
00f3f2d8 75db8233 USER32!ButtonWndProcW+0x2f
00f3f304 75d8e638 USER32!_InternalCallWinProc+0x2b
00f3f3dc 75d8e3a5 USER32!UserCallWinProcCheckWow+0x218
00f3f438 75da5d6f USER32!DispatchClientMessage+0xb5
00f3f468 77d74c86 USER32!__fnDWORD+0x3f
00f3f498 74894c3a ntdll!KiUserCallbackDispatcher+0x36
00f3f49c 75d9c1a7 win32u!NtUserCreateWindowEx+0xa
00f3f774 75d9ba68 USER32!VerNtUserCreateWindowEx+0x231
00f3f84c 75d9b908 USER32!CreateWindowInternal+0x157
00f3f88c 000d15b7 USER32!CreateWindowExW+0x38
The call stack is truncated above the memcpy call and below the CreateWindowExW call but tells us what we need to know. CreateWindowEx needs to draw a menu bar so it ends up syscalling into the kernel The kernel renders our menu bar, and in order to pass the information back into user space it dispatches a user mode callback. It's during this transition that the kernel lets a little bit of uninitialised stack data sneak out into user space.
As a side note, if you're curious as to why we end up rendering the menu bar in the kernel, a good article on the reasoning can be found at https://www.microsoft.com/resources/documentation/windowsnt/4/workstation/reskit/en-us/archi.mspx?mfr=true . Basically, in NT 3.5 there was a separate graphics subsystem in user space but this system had considerable overhead due to the frequency and volume of graphics calls. Windows NT 4 moved portions of window rendering into the kernel for these reasons, limiting the number of syscalls, context switches, and inter process communication and messaging that had to occur in order to do simple drawing.
Back to the bug - within the kernel, xxxSendMenuDrawItemMessage passes a pointer to a structure on its stack to MNInitDrawItemStruct for initialisation:
Within MNInitDrawItemStruct a pointer to the structure (which we now know is a DrawItemStruct structure) is stored in r12 and *some* members of the structure are initialised. If you walk through the whole function you'll see that the first 0x40 bytes of the structure are filled with some value.
xxxSendMenuDrawItemMessage then calls into xxxSendMessage with a pointer to the buffer in r9, which calls into xxxSendTransformableMessageTimeout
xxxSendTransformableMessageTimeout calls xxxSendMessageToClient with a pointer to the buffer in r9 as well.
Within xxxSendMessageToClient you might notice there's no call to SfnINLPUAHDRAWMENUITEM, the next function in the call stack. It's an indirect call! Toward the bottom of the function you might notice this bit of logic:
where there's a call to a pointer stored on the data section at 0x1c0355e70. The value stored at that address is 0x1c0152200
Which is a stub that is just 'jmp rax'!
So where does rax come from? rax comes from r10, and r10 is [r14+rdx*8+0x2e15f0], where r14 is 0x1c0000000, or the module's base address. So 0x1C02E03E0 + rdx*8. 0x1C02E03E0 is clearly supposed to be a function table, and if we check out what's there we see that that's true:
And we see that the function we're interested in, SfnINLPUAHDRAWMENUITEM, is in that table, so that's how we get to it. Conveniently, the pointer to that structure with the unitinitialized data is, again, passed to this function as the 4th argument - in r9.
Within SfnINLPUAHDRAWMENUITEM we see that a structure on the stack is memset to 0. var_f8 appears to be a structure of size 0xb8, and farther down the function we see elements from our structure from xxxSendMenuDrawItemMessage being copied into this structure.
We noted earlier that only the first 0x40 bytes of the structure in xxxSendMenuDrawItemMessage were initialised, but we can see 0x90 bytes being copied from the structure into this new structure. This new structure is then passed into KeUserModeCallback with API number 0x6A, a pointer to the new structure, and a structure size of 0xB8.
So at this point we have a structure with stale kernel stack data being passed back into user space. When Google Security tested this bug on Windows 10 x86 they found portions of an exception handler related structure in that stale stack data which included absolute pointers to kernel functions. This information could be used to infer the base address of a loaded module in kernel space and defeat KASLR.
The patch for this bug is in xxxSendMenuDrawItemMessage and it's very easy to spot. Near the beginning of the function we see a new call to memset that zeroes out a buffer of size 0x90 on the stack. This is the structure passed to MNInitDrawItemStruct that eventually makes its way through SfnINLPUAHDRAWMENUITEM and KeUserModeCallback back into user space that we've been following this whole time.
The vulnerable function is on the left, and the patched function is on the right. The vulnerability was patched by running the structure through memset.
I can't blame whoever wrote this function too much for this oversight. The structure is passed into MNInitDrawItemStruct, so it might have appeared to get properly initialised, and given that the information disclosure is done through an undocumented internal function called by CreateWindow it's no wonder that it went so long without being discovered.
Google Security included a brief write up of the bug and even supplied a PoC to demonstrate it, but didn't go into detail about how the bug actually works. Given that the bug is actually pretty straightforward I think it's worth going into a little bit deeper and getting a better idea of what's going on under the hood and how an oversight as simple as not initialising a structure lead to a kernel to userspace information disclosure.
The analysis will be done on Windows 10 x64 binaries.
First, let's look at the call stack from the PoC blurb:
a75e6a8c 81b63813 nt!memcpy
a75e6aec 9b1bb7bc nt!KeUserModeCallback+0x163
a75e6c10 9b14ff79 win32kfull!SfnINLPUAHDRAWMENUITEM+0x178
a75e6c68 9b1501a3 win32kfull!xxxSendMessageToClient+0xa9
a75e6d20 9b15361c win32kfull!xxxSendTransformableMessageTimeout+0x133
a75e6d44 9b114420 win32kfull!xxxSendMessage+0x20
a75e6dec 9b113adc win32kfull!xxxSendMenuDrawItemMessage+0x102
a75e6e48 9b1138f4 win32kfull!xxxDrawMenuItem+0xee
a75e6ecc 9b110955 win32kfull!xxxMenuDraw+0x184
a75e6f08 9b11084e win32kfull!xxxPaintMenuBar+0xe1
a75e6f34 819a8987 win32kfull!NtUserPaintMenuBar+0x7e
a75e6f34 77d74d50 nt!KiSystemServicePostCall
← Transition to kernel mode →
00f3f08c 7489666a ntdll!KiFastSystemCallRet
00f3f090 733ea6a8 win32u!NtUserPaintMenuBar+0xa
00f3f194 733e7cef uxtheme!CThemeWnd::NcPaint+0x1fc
00f3f1b8 733ef3c0 uxtheme!OnDwpNcActivate+0x3f
00f3f22c 733ede88 uxtheme!_ThemeDefWindowProc+0x800
00f3f240 75d8c2aa uxtheme!ThemeDefWindowProcW+0x18
00f3f298 75d8be4a USER32!DefWindowProcW+0x14a
00f3f2b4 75db53cf USER32!DefWindowProcWorker+0x2a
00f3f2d8 75db8233 USER32!ButtonWndProcW+0x2f
00f3f304 75d8e638 USER32!_InternalCallWinProc+0x2b
00f3f3dc 75d8e3a5 USER32!UserCallWinProcCheckWow+0x218
00f3f438 75da5d6f USER32!DispatchClientMessage+0xb5
00f3f468 77d74c86 USER32!__fnDWORD+0x3f
00f3f498 74894c3a ntdll!KiUserCallbackDispatcher+0x36
00f3f49c 75d9c1a7 win32u!NtUserCreateWindowEx+0xa
00f3f774 75d9ba68 USER32!VerNtUserCreateWindowEx+0x231
00f3f84c 75d9b908 USER32!CreateWindowInternal+0x157
00f3f88c 000d15b7 USER32!CreateWindowExW+0x38
The call stack is truncated above the memcpy call and below the CreateWindowExW call but tells us what we need to know. CreateWindowEx needs to draw a menu bar so it ends up syscalling into the kernel The kernel renders our menu bar, and in order to pass the information back into user space it dispatches a user mode callback. It's during this transition that the kernel lets a little bit of uninitialised stack data sneak out into user space.
As a side note, if you're curious as to why we end up rendering the menu bar in the kernel, a good article on the reasoning can be found at https://www.microsoft.com/resources/documentation/windowsnt/4/workstation/reskit/en-us/archi.mspx?mfr=true . Basically, in NT 3.5 there was a separate graphics subsystem in user space but this system had considerable overhead due to the frequency and volume of graphics calls. Windows NT 4 moved portions of window rendering into the kernel for these reasons, limiting the number of syscalls, context switches, and inter process communication and messaging that had to occur in order to do simple drawing.
Back to the bug - within the kernel, xxxSendMenuDrawItemMessage passes a pointer to a structure on its stack to MNInitDrawItemStruct for initialisation:
Within MNInitDrawItemStruct a pointer to the structure (which we now know is a DrawItemStruct structure) is stored in r12 and *some* members of the structure are initialised. If you walk through the whole function you'll see that the first 0x40 bytes of the structure are filled with some value.
xxxSendMenuDrawItemMessage then calls into xxxSendMessage with a pointer to the buffer in r9, which calls into xxxSendTransformableMessageTimeout
xxxSendTransformableMessageTimeout calls xxxSendMessageToClient with a pointer to the buffer in r9 as well.
Within xxxSendMessageToClient you might notice there's no call to SfnINLPUAHDRAWMENUITEM, the next function in the call stack. It's an indirect call! Toward the bottom of the function you might notice this bit of logic:
where there's a call to a pointer stored on the data section at 0x1c0355e70. The value stored at that address is 0x1c0152200
Which is a stub that is just 'jmp rax'!
So where does rax come from? rax comes from r10, and r10 is [r14+rdx*8+0x2e15f0], where r14 is 0x1c0000000, or the module's base address. So 0x1C02E03E0 + rdx*8. 0x1C02E03E0 is clearly supposed to be a function table, and if we check out what's there we see that that's true:
And we see that the function we're interested in, SfnINLPUAHDRAWMENUITEM, is in that table, so that's how we get to it. Conveniently, the pointer to that structure with the unitinitialized data is, again, passed to this function as the 4th argument - in r9.
Within SfnINLPUAHDRAWMENUITEM we see that a structure on the stack is memset to 0. var_f8 appears to be a structure of size 0xb8, and farther down the function we see elements from our structure from xxxSendMenuDrawItemMessage being copied into this structure.
We noted earlier that only the first 0x40 bytes of the structure in xxxSendMenuDrawItemMessage were initialised, but we can see 0x90 bytes being copied from the structure into this new structure. This new structure is then passed into KeUserModeCallback with API number 0x6A, a pointer to the new structure, and a structure size of 0xB8.
So at this point we have a structure with stale kernel stack data being passed back into user space. When Google Security tested this bug on Windows 10 x86 they found portions of an exception handler related structure in that stale stack data which included absolute pointers to kernel functions. This information could be used to infer the base address of a loaded module in kernel space and defeat KASLR.
The patch for this bug is in xxxSendMenuDrawItemMessage and it's very easy to spot. Near the beginning of the function we see a new call to memset that zeroes out a buffer of size 0x90 on the stack. This is the structure passed to MNInitDrawItemStruct that eventually makes its way through SfnINLPUAHDRAWMENUITEM and KeUserModeCallback back into user space that we've been following this whole time.
The vulnerable function is on the left, and the patched function is on the right. The vulnerability was patched by running the structure through memset.
I can't blame whoever wrote this function too much for this oversight. The structure is passed into MNInitDrawItemStruct, so it might have appeared to get properly initialised, and given that the information disclosure is done through an undocumented internal function called by CreateWindow it's no wonder that it went so long without being discovered.
Comments
Post a Comment