
POC para cve-2019-1458
Em dezembro, a Kaspersky publicou um post em blog sobre [exploit 0day usado ativamente in the wild][1]. Isso despertou meu interesse, pois embora tenham descrito como o exploit funcionava, eles não forneceram nenhum POC em sua análise.
Foi por isso que decidi tentar escrever um POC para esta vulnerabilidade com base no post do blog da Kaspersky e na análise do patch.
Este post descreve minha jornada nesse processo.
A primeira coisa foi coletar o máximo de informações possível sobre esta vulnerabilidade. Ao ler o post mencionado, extraí as seguintes informações:
NtUserMessageCallwin32k!DrawSwitchWndHiliteAlém disso, há uma boa captura de tela do código decompilado mostrando algumas das coisas listadas anteriormente.
Para ser exato, ela mostra: a criação da janela de alternância, a chamada a uma função chamada toggle_alt_key e múltiplas chamadas a NtUserMessageCall.
[Fonte da imagem][1]
Muita informação útil, mas ainda assim não descreve exatamente como essa vulnerabilidade funciona e como acioná-la.
[O módulo afetado era o win32k.sys][2]. Baixei as versões corrigida e não corrigida desse módulo.
Para Windows 7 x64, essas foram:
Elas podem ser baixadas do [Catálogo do Microsoft Update][3]
Aqui está o resultado do bindiff comparando as duas versões

Após descartar funções relacionadas à funcionalidade DebugHook, o que realmente nos resta é esta função ligeiramente alterada InitFunctionTables()

Definitivamente não é o maior patch que existe.
Isso não ajudará a identificar imediatamente a causa raiz dessa vulnerabilidade. Mas vale notar que alguns valores iniciais para variáveis em
*(gpsi+0x14E), *(gpsi+0x154), *(gpsi+0x180) foram adicionados. Portanto, isso pode ser um bug relacionado a variável não inicializada.
Nesta seção, apresentarei como construí progressivamente um POC que aciona essa vulnerabilidade, enquanto simultaneamente descobria o que a vulnerabilidade realmente era.
O diffing de patches não forneceu muitas informações úteis no início, então dependi principalmente do post do blog da Kaspersky na primeira etapa do desenvolvimento.
Para ter um bom ambiente de teste, preparei uma VM Win7 SP1 x64 com a última versão vulnerável do win32k em execução. Além disso, anexei o Windbg a essa VM para fazer depuração de kernel e, durante isso, configurei também o caminho do servidor de símbolos.
Comecei minha investigação olhando para win32k!DrawSwitchWndHilite, mencionado no post do blog. Ela é chamada a partir de dois lugares: xxxMoveSwitchWndHilite e xxxPaintSwitchWindow; esta última chamou minha atenção imediatamente, por causa das chamadas a GetKeyState/GetAsyncKeyState ao redor, que foram mencionadas no relatório original. Além disso, essas chamadas verificam se a tecla ALT está pressionada.

Chamada a DrawSwitchWndHilite a partir de xxxPaintSwitchWindow
Continuando a seguir as referências cruzadas de chamadas (xxxWrapSwitchWndProc->xxxSwitchWndProc->xxxPaintSwitchWindow->DrawSwitchWndHilite), descobri que o primeiro elemento dessa cadeia é referenciado em InitFunctionTables, função que foi corrigida no patch.
Em seguida, examinei NtUserMessageCall da captura de tela do código decompilado.
Aqui está a declaração dessa função```cpp
NtUserMessageCall(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ULONG_PTR ResultInfo, DWORD dwType, BOOLEAN bAnsi)
O exploit está chamando-o com `msg = 0x14` e `dwType = 0xE0`. Vamos ver o que isso faz.```cpp
HINSTANCE hInstance = GetModuleHandle(NULL);
WNDCLASSEX wcx;
ZeroMemory(&wcx, sizeof(wcx));
wcx.hInstance = hInstance;
wcx.cbSize = sizeof(wcx);
wcx.lpszClassName = L"SploitWnd";
wcx.lpfnWndProc = DefWindowProc;
printf("[*] Registering window\n");
ATOM wndAtom = RegisterClassEx(&wcx);
if (wndAtom == INVALID_ATOM) {
printf("[-] Failed registering SploitWnd window class\n");
exit(-1);
}
printf("[*] Creating instance of this window\n");
HWND sploitWnd = CreateWindowEx(0, L"SploitWnd", L"", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL);
if (sploitWnd == INVALID_HANDLE_VALUE) {
printf("[-] Failed to create SploitWnd window\n");
exit(-1);
}
NtUserMessageCall(sploitWnd, WM_ERASEBKGND, 0, 0, 0, 0xE0, 1);
Aqui registrei uma classe de janela simples e criei uma janela dessa classe. Então chamei NtUserMessageCall com os mesmos parâmetros do exploit. Para ver o que acontece internamente, configurei o breakpoint kd> ba e 1 win32k!NtUserMessageCall e executei o código.
Existem algumas chamadas sendo feitas para essa função, então tive que capturar a correta, mas não foi tão difícil, era a que tinha uma pilha de chamadas realmente curta.

NtUserMessageCall
Percorrendo o código passo a passo, descobri que ele chama uma função do array gapfnMessageCall; o índice é calculado com base no valor de msg e é igual a 0, então a chamada é feita para NtUserfnDWORD

NtUserfnDWORD
A próxima chamada é feita usando o valor de dwType, e agora o offset de gpsi é igual a 0x40, e a chamada leva a xxxWrapSwitchWndProc (essa função já apareceu quando eu estava verificando a cadeia de chamadas de DrawSwitchWndHilite).
xxxWrapSwitchWndProc simplesmente chama xxxSwitchWndProc.

xxxSwitchWndProc
E este é o fim; o código falha aqui, não indo mais adiante até xxxPaintSwitchWindow, que é onde queremos chegar com base no valor de msg (0x14). Vamos verificar o porquê.
O código falha nesse estágio porque, como destacado na imagem anterior, o fnid da nossa janela não é igual a 0x2A0 (FNID_SWITCH) e a mensagem que estamos enviando não é igual a 1; portanto, acabamos em xxxDefWindowProc. Para evitar esse cenário, temos que chamar xxxSwitchWndProc com o fnid definido como FNID_SWITCH, de modo que iremos direto para a instrução switch e, depois, para xxxPaintSwitchWindow.
Como definir o fnid correto? Na verdade, a mesma função faz isso no primeiro bloco if; só precisamos falhar em todas as verificações dentro dele para chegar à instrução que define o fnid.
Aqui estão as condições que precisamos atender para falhar nas três verificações if: