
Usar CVE-2016-3308 para corromper el heap de escritorio de win32k
autor : @55-AA, 18 de septiembre de 2016
##Introducción
El desktop heap es un pool del kernel usado por win32k; puede ser explotado por una aplicación en modo usuario. A continuación describiré en detalle cómo implementar una explotación fiable para leer/escribir direcciones arbitrarias en el kernel. Este writeup y el análisis asociado se realizaron en una instalación win7_sp1_x86 (build 17842).
##Vulnerabilidad
El 9 de agosto de 2016, Microsoft publicó MS16-098. El código vulnerable se encuentra dentro de la función win32k!xxxInsertMenuItem, cuyo prototipo es :
BOOL xxxInsertMenuItem(
PMENU pMenu,
UINT wIndex,
BOOL fByPosition,
LPMENUITEMINFOW lpmii,
PUNICODE_STRING pstrItem
);
Primero veamos el pseudo-código del bug en xxxInsertMenuItem:
if (pMenu->cItems >= pMenu->cAlloced) {
if (pMenu->rgItems) {
pNewItems = (PITEM)DesktopAlloc(
pMenu->head.rpdesk,
(pMenu->cAlloced + CMENUITEMALLOC) * sizeof(ITEM),
DTAG_MENUITEM);
......
pMenu->cAlloced += CMENUITEMALLOC;
pMenu->rgItems = pNewItems;
if (wIndex != MFMWFP_NOITEM)
pItem = MNLookUpItem(pMenu, wIndex, fByPosition, &pMenuItemIsOn);
......
pMenu->cItems++;
if (pItem != NULL) {
RtlMoveMemory(pItem + 1, pItem, (pMenu->cItems - 1) *
sizeof(ITEM) - ((char *)pItem - (char *)pMenu->rgItems));
} else {
En el código anterior, cuando el 9.º elemento (a partir del 1.º) se añadía a pMenu, se llamaba a DesktopAlloc() para reasignar un nuevo pMenu->rgItems. Después se llamaba a MNLookUpItem() para obtener la ubicación del elemento en pMenu->rgItems. Pero el pItem devuelto por MNLookUpItem() es un rgItems de otro pSubMenu, no del pMenu, por lo que al llamarse a RtlMoveMemory(), el pItem del pSubMenu y los bytes posteriores se sobrescribían debido al tamaño incorrecto del movimiento.
El siguiente es el código de desensamblado del bug, que desencadenaría una sobrescritura del heap; puede aprovecharse para construir un chunk falso:
0: kd> u win32k!xxxInsertMenuItem+0x1f5 l8
win32k!xxxInsertMenuItem+0x1f5:
95d295af 6bc06c imul eax,eax,6Ch
95d295b2 2bc3 sub eax,ebx
95d295b4 034634 add eax,dword ptr [esi+34h]
95d295b7 50 push eax
95d295b8 8d436c lea eax,[ebx+6Ch]
95d295bb 53 push ebx
95d295bc 50 push eax
95d295bd e85ea40100 call win32k!memmove (95d43a20)
Para seguir el bug, uso estos breakpoints en WinDbg:
ba e1 win32k!xxxInsertMenuItem
ba e1 win32k!xxxInsertMenuItem+0xf3
95d294e3 e843e70200 call win32k!DesktopAlloc (836d7bf5)
ba e1 win32k!xxxInsertMenuItem+0x129
95d294e3 e80de70200 call win32k!DesktopAlloc (836d7bf5)
ba e1 win32k!xxxInsertMenuItem+0x1f5
95d295af 6bc06c imul eax,eax,6Ch
Para desencadenar el bug, deben llevarse a cabo los siguientes pasos:
El desktop heap es un pool global utilizado por todos los procesos GUI. Todos los objetos GUI, como Window o Menu, se almacenan en el desktop heap y son gestionados por el asignador de heap del kernel. El asignador de heap del kernel usa funciones conocidas como RtlAllocateHeap y RtlFreeHeap. A diferencia del heap en modo usuario, el desktop heap no emplea asignadores de front-end, por lo que no hay Low Fragmentation Heap, ni listas Lookaside, etc. Tampoco hay Heap Encoding hasta Windows 8 y posteriores. La siguiente es la estructura de chunk en win7_sp1_x86:
typedef struct _HEAP_ENTRY {
USHORT Size;
UCHAR Flags;
UCHAR SegmentIndex;
USHORT PreviousSize;
UCHAR SegmentOffset;
UCHAR UnusedBytes;
} HEAP_ENTRY, *PHEAP_ENTRY;
Los campos Size y PreviousSize representan el tamaño de los chunks desplazado a la derecha HEAP_GRANULARITY_SHIFT bits (definido como 3 en sistemas de 32 bits). El campo Size especifica el chunk actual, y PreviousSize especifica el anterior. El bit más bajo de Flags normalmente se establece a HEAP_ENTRY_BUSY(0x01), lo que indica que el chunk está en uso; si no, es 0x00.
La siguiente figura muestra la relación entre estos campos y los bloques de chunks. La segunda WORD subrayada en verde (0x000f) representa que el tamaño del chunk actual es 0x78 bytes; la segunda WORD subrayada en negro (0x0003) representa que el tamaño del chunk anterior es 0x18 bytes; y las WORDs subrayadas en rojo (0x0001) representan que los chunks actuales están en uso. Aquí, el tamaño del chunk incluye el tamaño de la cabecera, definida como la estructura HEAP_ENTRY anterior.

Es la característica más importante para la corrupción del heap: el asignador de heap siempre obtiene el chunk liberado más recientemente. Esto significa que podemos asignar un chunk con cualquier tamaño y en la ubicación que queramos.
Aprovechando el bug, puedo sobrescribir algunos bytes en el desktop heap, obteniendo así un chunk falso que reemplaza a un chunk normal. Luego libero el chunk reemplazado, de modo que el chunk falso se coloca en la cima de la lista de chunks libres. Posteriormente, el chunk falso se reutiliza, y puedo escribir cualquier byte en él; la región escribible se superpone con varios chunks normales, pero no puede cubrir todo el espacio del kernel. Por lo tanto, necesito construir otra primitiva R/W en la región superpuesta, aprovechando tagWND.strName para escribir en direcciones arbitrarias. El puntero de strName.Buffer puede llevarnos a cualquier lugar, tanto del kernel como del espacio de usuario. Por supuesto, nuestro objetivo es solo nt!HalDispatchTable.
La siguiente figura muestra el procedimiento de cambio del heap:

Según lo demostrado, corrompo el desktop heap paso a paso e implemento la explotación mediante las siguientes etapas: