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!xxxInsertMenuItem
calls 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!MNLookUpItem
in 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[1], pItem, &pMenu->rgItems[0] + (sizeof(ITEM) * pMenu->cItems - (QWORD)pItem)
. The pointer arithmetic assumes that the PITEM returned is in the array pointed to by pMenu->rgItems
.
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.