
Use a CVE-2016-3308 para corromper o desktop heap do win32k
autor : @55-AA, 18 de setembro de 2016
##Introdução
O desktop heap é um pool do kernel usado pelo win32k, que pode ser explorado por aplicações em modo de usuário. Aqui descreverei em detalhes como implementar uma exploração confiável para ler/escrever endereços arbitrários no kernel. Este writeup e a análise associada foram feitos em uma instalação do win7_sp1_x86(build 17842).
##Vulnerabilidade
Em 9 de agosto de 2016, a Microsoft lançou o MS16-098. O código da vulnerabilidade existe na função win32k!xxxInsertMenuItem, cujo protótipo é:
BOOL xxxInsertMenuItem(
PMENU pMenu,
UINT wIndex,
BOOL fByPosition,
LPMENUITEMINFOW lpmii,
PUNICODE_STRING pstrItem
);
Primeiro, vejamos o pseudocódigo do bug em 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 {
No código acima, quando o 9º (a partir do 1º) item foi adicionado ao pMenu, DesktopAlloc() foi chamado para realocar um novo pMenu->rgItems. Em seguida, MNLookUpItem() foi chamado para obter a localização do item em pMenu->rgItems. Mas o pItem retornado por MNLookUpItem() é um rgItems de outro pSubMenu, em vez do pMenu; então, quando RtlMoveMemory() foi chamado, o pItem do pSubMenu e os bytes seguintes seriam sobrescritos devido ao tamanho incorreto da movimentação.
A seguir está o código de desmontagem sobre o bug, que acionaria uma sobrescrita no heap e pode ser usado para construir um 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 rastrear o bug, uso estes breakpoints no 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 acionar o bug, as seguintes etapas precisam ser executadas:
O desktop heap é um pool global usado por todos os processos de GUI. Todos os objetos de GUI, como Window, Menu, são armazenados no desktop heap e gerenciados pelo alocador de heap do kernel. O alocador de heap do kernel usa funções conhecidas, como RtlAllocateHeap e RtlFreeHeap. Diferentemente do heap em modo de usuário, o desktop heap não utiliza alocadores de front-end, portanto não há Low Fragmentation Heap, nem lista Lookaside etc. Além disso, não há Heap Encoding até o Windows 8 e versões posteriores. A seguir está a estrutura do chunk no win7_sp1_x86:
typedef struct _HEAP_ENTRY {
USHORT Size;
UCHAR Flags;
UCHAR SegmentIndex;
USHORT PreviousSize;
UCHAR SegmentOffset;
UCHAR UnusedBytes;
} HEAP_ENTRY, *PHEAP_ENTRY;
Os campos Size e PreviousSize representam o tamanho dos chunks deslocado à direita em HEAP_GRANULARITY_SHIFT (definido como 3 em sistemas de 32 bits) bits; o campo Size especifica o chunk atual, e PreviousSize especifica o anterior. O bit menos significativo de Flags geralmente é definido como HEAP_ENTRY_BUSY (0x01), representando que o chunk está em uso; caso contrário, é 0x00.
A figura a seguir demonstra a relação entre esses campos e esses blocos de chunk. O segundo WORD sublinhado em verde (0x000f) representa que o tamanho do chunk atual é 0x78 bytes; o segundo WORD sublinhado em preto (0x0003) representa que o tamanho do chunk anterior é 0x18 bytes; e os WORDs sublinhados em vermelho (0x0001) representam que os chunks atuais estão em uso. Aqui, o tamanho do chunk inclui o tamanho do cabeçalho; o cabeçalho é definido pela estrutura HEAP_ENTRY acima.

Essa é a característica mais importante para a corrupção de heap: o alocador de heap sempre obtém o chunk liberado mais recentemente. Isso significa que podemos, na prática, alocar um chunk de qualquer tamanho e em uma localização específica que desejarmos.
Usando o bug, posso sobrescrever alguns bytes no desktop heap, obtendo assim um chunk falso que substitui um chunk normal. Então eu libero o chunk substituído, fazendo com que o chunk falso seja empurrado para o topo da lista de chunks livres. Posteriormente, o chunk falso é reutilizado e posso escrever quaisquer bytes nele; a região gravável se sobrepõe a vários chunks normais, mas não cobre todo o espaço do kernel. Portanto, preciso construir outra primitiva de leitura/escrita na região sobreposta, que aproveite o tagWND.strName para escrever em endereços arbitrários. O ponteiro de strName.Buffer pode nos levar a qualquer lugar, incluindo o espaço do kernel e o espaço do usuário. Naturalmente, nosso alvo é apenas o nt!HalDispatchTable.
A figura a seguir mostra o procedimento de alteração do heap:

De acordo com as demonstrações, corrompo o desktop heap passo a passo e implemento a exploração por meio das seguintes etapas: