
CVE-2016-3308 nutzen, um den win32k-Desktop-Heap zu korrumpieren.
Autor : @55-AA, 18. September 2016
##Einleitung
Desktop-Heap ist ein Kernel-Pool, der von win32k verwendet wird und durch eine Anwendung im Benutzermodus ausgenutzt werden kann. Hier werde ich im Detail beschreiben, wie eine zuverlässige Ausnutzung implementiert werden kann, um beliebige Adressen im Kernel zu lesen/schreiben. Dieser Bericht und die zugehörige Analyse wurden auf einer win7_sp1_x86-Installation (Build 17842) durchgeführt.
##Schwachstelle
Am 9. August 2016 veröffentlichte Microsoft MS16-098. Der verwundbare Code befindet sich in der Funktion win32k!xxxInsertMenuItem. Der Funktionsprototyp ist:
BOOL xxxInsertMenuItem(
PMENU pMenu,
UINT wIndex,
BOOL fByPosition,
LPMENUITEMINFOW lpmii,
PUNICODE_STRING pstrItem
);
Zuerst betrachten wir den Pseudocode des Fehlers 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 {
Im obigen Code: Als das 9. (ab dem 1.) Element in pMenu eingefügt wurde, wurde DesktopAlloc() aufgerufen, um ein neues pMenu->rgItems neu zu allozieren. Dann wurde MNLookUpItem() aufgerufen, um die Position des Elements in pMenu->rgItems zu ermitteln. Aber das von MNLookUpItem() zurückgegebene pItem ist ein rgItems eines anderen pSubMenu statt des pMenu. Wenn RtlMoveMemory() aufgerufen wird, würden das pItem des pSubMenu und die folgenden Bytes aufgrund der fehlerhaften Verschiebegröße überschrieben.
Im Folgenden ist der Disassemblierungscode zu dem Fehler, der einen Heap-Overwrite auslöst; er kann genutzt werden, um einen gefälschten Chunk aufzubauen:
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)
Um den Fehler zu verfolgen, verwende ich diese Haltepunkte 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
Um den Fehler auszulösen, müssen die folgenden Schritte durchgeführt werden:
Desktop-Heap ist ein globaler Pool, der von allen GUI-Prozessen verwendet wird. Alle GUI-Objekte wie Fenster und Menüs werden im Desktop-Heap gespeichert und vom Kernel-Heap-Allokator verwaltet. Der Kernel-Heap-Allokator verwendet vertraute Funktionen wie RtlAllocateHeap und RtlFreeHeap. Anders als der User-Mode-Heap verwendet der Desktop-Heap keine Front-End-Allokatoren, es gibt also keinen Low-Fragmentation-Heap, keine Lookaside-Liste usw. Außerdem gibt es erst ab Windows 8 eine Heap-Verschlüsselung. Im Folgenden ist die Struktur eines Chunks auf win7_sp1_x86:
typedef struct _HEAP_ENTRY {
USHORT Size;
UCHAR Flags;
UCHAR SegmentIndex;
USHORT PreviousSize;
UCHAR SegmentOffset;
UCHAR UnusedBytes;
} HEAP_ENTRY, *PHEAP_ENTRY;
Die Felder Size und PreviousSize repräsentieren die Chunk-Größe, die um HEAP_GRANULARITY_SHIFT (auf 32-Bit-Systemen als 3 definiert) Bits nach rechts verschoben ist. Das Feld Size gibt den aktuellen Chunk an, und PreviousSize den vorherigen. Das niederwertigste Bit von Flags ist normalerweise auf HEAP_ENTRY_BUSY (0x01) gesetzt, was bedeutet, dass der Chunk in Benutzung ist; andernfalls ist es 0x00.
Die folgende Abbildung zeigt die Beziehung zwischen diesen Feldern und den Chunk-Blöcken. Das zweite grün unterstrichene WORD (0x000f) repräsentiert, dass die aktuelle Chunk-Größe 0x78 Bytes beträgt. Das zweite schwarz unterstrichene WORD (0x0003) repräsentiert, dass die vorherige Chunk-Größe 0x18 Bytes beträgt, und die rot unterstrichenen WORDs (0x0001) repräsentieren, dass die aktuellen Chunks in Benutzung sind. Hier enthält die Chunk-Größe die Header-Größe; der Header ist als die obige HEAP_ENTRY-Struktur definiert.

Das wichtigste Merkmal für die Heap-Korruption ist, dass der Heap-Allokator immer den zuletzt freigegebenen Chunk zurückgibt. Das bedeutet, dass wir tatsächlich einen Chunk beliebiger Größe an einer von uns gewünschten Position allozieren können.
Durch Ausnutzung des Fehlers kann ich einige Bytes im Desktop-Heap überschreiben. Dadurch erhalte ich einen gefälschten Chunk, der einen normalen Chunk ersetzt. Dann gebe ich den ersetzten Chunk frei, sodass der gefälschte Chunk an die Spitze der Freiliste gelangt. Anschließend wird der gefälschte Chunk wiederverwendet; ich kann beliebige Bytes in ihn schreiben. Der schreibbare Bereich überlappt mehrere normale Chunks, kann aber nicht den gesamten Kernel-Bereich abdecken. Daher muss ich in dem überlappten Bereich eine weitere Lese-/Schreib-Primitive aufbauen, die tagWND.strName ausnutzt, um an beliebige Adressen zu schreiben. Der Zeiger strName.Buffer kann uns überallhin führen, einschließlich Kernel- und Benutzerbereich. Unser Ziel ist natürlich nur die nt!HalDispatchTable.
Die folgende Abbildung zeigt den Ablauf der Heap-Veränderung:

Entsprechend der Darstellung korrumpiere ich den Desktop-Heap Schritt für Schritt und implementiere die Ausnutzung in den folgenden Phasen: