
Utilizza CVE-2016-3308 per corrompere l'heap del desktop win32k
autore : @55-AA, 18 settembre 2016
##Introduzione
L'heap del desktop è un pool del kernel utilizzato da win32k, può essere sfruttato da un'applicazione in modalità utente. Qui descriverò in dettaglio come implementare uno sfruttamento affidabile per leggere/scrivere un indirizzo arbitrario nel kernel. Questo writeup e l'analisi associata sono stati effettuati su un'installazione win7_sp1_x86 (build 17842).
##Vulnerabilità
Il 9 agosto 2016, Microsoft ha rilasciato MS16-098. Il codice della vulnerabilità si trova all'interno della funzione win32k!xxxInsertMenuItem, il prototipo della funzione è:
BOOL xxxInsertMenuItem(
PMENU pMenu,
UINT wIndex,
BOOL fByPosition,
LPMENUITEMINFOW lpmii,
PUNICODE_STRING pstrItem
);
Per prima cosa, diamo un'occhiata al pseudo codice del bug in 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 {
Nel codice sopra, quando il nono (dal primo) elemento veniva aggiunto al pMenu, DesktopAlloc() veniva chiamato per riallocare un nuovo pMenu->rgItems. Successivamente veniva chiamato MNLookUpItem() per ottenere la posizione dell'elemento in pMenu->rgItems. Ma il pItem restituito da MNLookUpItem() è un rgItems di un altro pSubMenu, invece del pMenu, quindi quando veniva chiamato RtlMoveMemory(), il pItem del pSubMenu e i byte successivi venivano sovrascritti a causa della dimensione errata dello spostamento.
Quello che segue è il codice disassemblato relativo al bug, che attiverebbe una sovrascrittura dell'heap e può essere sfruttato per costruire un trunk 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)
Per tracciare il bug, utilizzo questi breakpoint in 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
Per attivare il bug, è necessario eseguire le seguenti fasi:
L'heap del desktop è un pool globale utilizzato da tutti i processi GUI. Tutti gli oggetti GUI, come finestre e menu, sono memorizzati nell'heap del desktop e sono gestiti dall'allocatore heap del kernel. L'allocatore heap del kernel utilizza funzioni familiari come RtlAllocateHeap e RtlFreeHeap. A differenza dell'heap in modalità utente, l'heap del desktop non impiega allocatori front-end, quindi niente Low Fragmentation Heap, niente Lookaside list, ecc. Inoltre non esiste Heap Encoding fino a Windows 8 e successivi. La seguente è la struttura del trunk su win7_sp1_x86:
typedef struct _HEAP_ENTRY {
USHORT Size;
UCHAR Flags;
UCHAR SegmentIndex;
USHORT PreviousSize;
UCHAR SegmentOffset;
UCHAR UnusedBytes;
} HEAP_ENTRY, *PHEAP_ENTRY;
I campi Size e PreviousSize rappresentano la dimensione dei chunk spostata a destra di HEAP_GRANULARITY_SHIFT (definito come 3 nei sistemi a 32 bit) bit, il campo Size specifica il chunk corrente, e PreviousSize specifica quello precedente. Il bit più basso di Flags è solitamente impostato a HEAP_ENTRY_BUSY (0x01), indicando che il chunk è in uso, altrimenti è 0x00.
La figura seguente dimostra la relazione tra questi campi e i blocchi di chunk. La seconda parola sottolineata in verde (0x000f) rappresenta che la dimensione del chunk corrente è 0x78 byte, la seconda parola sottolineata in nero (0x0003) rappresenta che la dimensione del chunk precedente è 0x18 byte, e le parole sottolineate in rosso (0x0001) rappresentano che i chunk correnti sono in uso. Qui, la dimensione del chunk include la dimensione dell'intestazione, l'intestazione è definita come struttura HEAP_ENTRY sopra.

Questa è la caratteristica più importante per la corruzione dell'heap, ovvero l'allocatore dell'heap ottiene sempre il chunk rilasciato più di recente. Ciò significa che possiamo effettivamente allocare un chunk di qualsiasi dimensione e in una posizione specifica che desideriamo.
Sfruttando il bug, posso sovrascrivere alcuni byte nell'heap del desktop, ottenendo così un chunk falso che sostituisce un chunk normale. Quindi rilascio il chunk sostituito, in modo che il chunk falso venga spinto in cima alla lista dei chunk liberi. Successivamente il chunk falso viene riutilizzato, e posso scrivere qualsiasi byte al suo interno; la regione scrivibile si sovrappone a diversi chunk normali, ma non può coprire l'intero kernel. Quindi ho bisogno di costruire un'altra primitiva di lettura/scrittura nella regione sovrapposta, sfruttando tagWND.strName per scrivere un indirizzo arbitrario. Il puntatore di strName.Buffer può condurci ovunque, sia nel kernel che nello spazio utente. Ovviamente, il nostro obiettivo è solo nt!HalDispatchTable.
La figura seguente mostra la procedura di modifica dell'heap:

Secondo le dimostrazioni, corrompo l'heap del desktop passo dopo passo e implemento lo sfruttamento attraverso le seguenti fasi: