This past patch Tuesday, Microsoft released MS16-098, a patch for multiple vulnerabilities in “Kernel-Mode Drivers”. Within this patch, the vulnerability identified as CVE-2016-3308 and ZDI-16-453 was addressed. This post is an analysis of this vulnerability and how it could potentially be leveraged by an attacker in the form of a Local Privilege Escalation (LPE) exploit.
The vulnerability exists within the function
win32k!xxxInsertMenuItem and how it handles reallocations of memory. This function is responsible for adding items to menus that are displayed to users. Menu items that are being added to a menu can be inserted either based on a position or an existing menu items identifier. This difference is explained in the documentation provided by Microsoft for the InsertMenuItem function. Additionally when a new menu is created, it has no storage for it’s menu items. It is not until an initial item is added to the menu that item storage is allocated. On Windows 7 x64, this allocation is done 8 items at a time resulting in the 2nd through the 8th item added to the menu not requiring a reallocation of space.
When a 9th, 17th, etc. item is added to the menu, a reallocation is triggered in order to be able to accommodate the additional item. After this occurs, and the memory allocation is successful,
win32k!MNLookUpItem a second to get the item’s location in the newly allocated space. The vulnerability in question can be triggered when the item returned from the second invocation of
win32k!MNLookUpItem does not exist in the same menu as the first. The
win32k!MNLookUpItem function recursively searches the menu structure, it’s items and the submenus optionally associated with those items. In order to force the second call to return an item from a different menu than the first call a parameter needs to be changed between the two function call. The prototype for this function looks roughly like:
PITEM MNLookUpItem(PMENU pMenu, UINT uItem, BOOL fByPosition, PMENU *pMenuOut);. The first three parameters are required, while the 4th parameter is an optional pointer to a location to store the menu object for which the item returned is associated with. Under non-exploit conditions the value of pMenu is written to pMenuOut, the change introduced in this patch checks that this is the case and exits before harm can be done to the system.
In order to trigger the desirable code path, an attacker needs to create a menu along with a submenu. The submenu requires a single item with
wID set to 1 for reasons outlined later. This submenu is referenced in an item that is added to the top level menu with a specific arbitrary
wID. The top level menu is then populated with 7 additional items with additional unique
wID values to fill it’s allocated space. To trigger the vulnerability the attacker calls the
NtUserThunkedMenuItemInfo function to add a 9th item with the
wID specified as the submenu. The first call to
win32k!xxxInsertMenuItem identifies the submenu item and returns the toplevel menu in which it resides. Before the reallocation takes place, a check on the submenu item’s
pItem->hbmpItem field takes place. If this field is set to
HBMMENU_SYSTEM, then the value compared with
wID is updated to 1. This change is leveraged by the attacker to cause the second call to
win32k!MNLookUpItem to identify the submenu’s item (who’s
wID was explicitly set to 1) and return the submenu instead of the original top level menu.
Under exploitation conditions, the item returned by the second call to
win32k!MNLookUpItem is in a different menu than what was originally assumed. This causes an out-of-bounds read to occur when a subsequent call to
memmove uses pointer arithmetic to copy and overwrite a potentially excessively large block of memory. The call to memmove is roughly equivalent to
memmove(&pItem, pItem, &pMenu->rgItems + (sizeof(ITEM) * pMenu->cItems - (QWORD)pItem). The pointer arithmetic assumes that the PITEM returned is in the array pointed to by
Reliable exploitation of this vulnerability would require the user to be able to limit the memory passed to memmove to avoid triggering a violation by corrupting memory stored after pItem. Assuming the size of memmove were limited, and pItem were the last item in the array pointed to by
pMenu->rgItems, it is feasible that an attacker could leverage the memmove operation to write up to
sizeof(win32k!tagITEM) bytes (0x90 on Windows 7 SP1 x64) past the region allocated for the array. An attacker can leverage the
HANDLEENTRY structs within the shared region to identify the location in Kernel memory of the
win32k!tagMENU struct, however to accurately predict the parameters of the memmove call, the attacker would need to know the value of the
rgItems field within this struct.
A proof of concept for this vulnerability which triggers a BSOD on Windows 7 SP1 x64 can be found here.