Exploit-DB #14668
Description: Microsoft Windows - Win32k!xxxRealDrawMenuItem() Missing HBITMAP Bounds Checks
Type: dos
Platform: windows
Date: 2010-08-17
Author: Tavis Ormandy

Exploit:
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 00[PII_REDACTED]008c win32k!xxxRealDrawMenuItem+0x130 (FPO: [6,47,4])
981916a4 92adb609 f10105c8 0110007c 981916ec win32k!xxxDrawState+0x1cd (FPO: [8,33,4])
98191710 92adbdaf f10105c8 fe[PII_REDACTED]0000 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 92b3b[PII_REDACTED]c [PII_REDACTED] win32k!xxxDWP_DoNCActivate+0xc7 (FPO: [3,1,4])
98191878 92b02b92 fe607920 [PII_REDACTED] win32k!xxxRealDefWindowProc+0x7fa (FPO: [SEH])
9819189c 92b54859 fe607920 [PII_REDACTED] win32k!xxxDefWindowProc+0x10f (FPO: [4,0,4])
981918dc 92b546a4 fe607920 [PII_REDACTED] win32k!xxxSendMessageTimeout+0x1ac (FPO: [8,7,4])
98191904 92b1728b fe607920 [PII_REDACTED] win32k!xxxSendMessage+0x28 (FPO: [4,0,0])
9819197c 92b095c1 00[PII_REDACTED]0c88 00000001 win32k!xxxActivateThisWindow+0x473 (FPO: [3,21,0])
981919e4 92b091aa fe607920 fe5ad[PII_REDACTED]0 win32k!xxxSetForegroundWindow2+0x3d7 (FPO: [3,18,4])
98191a24 92b17770 fe[PII_REDACTED]0001 fe5ad928 win32k!xxxSetForegroundWindow+0x1e4 (FPO: [2,8,4])
98191a50 92b17537 [PII_REDACTED] fe600618 win32k!xxxActivateWindow+0x1b3 (FPO: [2,4,4])
98191a64 92afd9dd fe[PII_REDACTED]0000 fe607920 win32k!xxxSwpActivate+0x44 (FPO: [1,0,0])
98191abc 92b02c98 00000000 fe[PII_REDACTED]0000 win32k!xxxEndDeferWindowPosEx+0x278 (FPO: [2,16,4])
98191adc 92b2c50e fe607920 [PII_REDACTED] win32k!xxxSetWindowPos+0xf6 (FPO: [7,1,4])
98191b18 92b23b0d [PII_REDACTED] 0ad06a7a win32k!xxxShowWindow+0x25a (FPO: [2,3,4])
98191c30 92b210b6 00080000 ff9d1d28 fe607250 win32k!xxxCreateWindowEx+0x12ed (FPO: [SEH])
98191cf0 8285342a 80[PII_REDACTED]1cc0 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[PII_REDACTED]
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 addition