
POC para cve-2019-1458
En diciembre, Kaspersky publicó una entrada de blog sobre [un exploit de día cero usado en ataques reales][1]. Me picó el interés porque, aunque describían cómo funcionaba el exploit, no proporcionaban ningún POC en su análisis.
Por eso decidí intentar escribir un POC para esta vulnerabilidad basándome en la entrada de blog de Kaspersky y en el análisis del parche.
Este post describe mi viaje haciendo eso.
Lo primero fue recopilar toda la información posible sobre esta vulnerabilidad.
Leyendo la entrada de blog mencionada, extraje la siguiente información:
NtUserMessageCallwin32k!DrawSwitchWndHiliteAdemás, hay una buena captura de pantalla del código descompilado que muestra algunas de las cosas enumeradas anteriormente.
Para ser exacto, muestra: la creación de la ventana de conmutación, la llamada a una función llamada toggle_alt_key y múltiples llamadas a NtUserMessageCall.
[Fuente de la imagen][1]
Mucha información útil, pero aún no describe exactamente cómo funciona esta vulnerabilidad ni cómo activarla.
[El módulo afectado era win32k.sys][2]. Descargué las versiones parcheada y sin parchear de este módulo.
Para Win7 x64 fueron:
Se pueden descargar desde [Microsoft Update Catalog][3]
Aquí está el resultado de bindiff al comparar ambas versiones

Después de descartar las funciones relacionadas con la funcionalidad DebugHook, todo lo que realmente nos queda es esta función ligeramente modificada InitFunctionTables()

Definitivamente no es el parche más grande que hay.
Esto no ayudará a identificar de inmediato la causa raíz de esta vulnerabilidad. Pero vale la pena señalar que se han añadido algunos valores iniciales para variables en
*(gpsi+0x14E), *(gpsi+0x154), *(gpsi+0x180). Así que esto podría ser un error relacionado con una variable no inicializada.
En esta sección presentaré cómo fui construyendo progresivamente un POC que activa esta vulnerabilidad, mientras al mismo tiempo descubría qué era realmente la vulnerabilidad.
El diffing del parche no dio demasiada información útil al principio, así que en la primera etapa del desarrollo me apoyé principalmente en la entrada de blog de Kaspersky.
Para tener un buen entorno de pruebas preparé una VM con Win7 SP1 x64 con la última versión vulnerable de win32k en ejecución. Además, conecté Windbg a esta VM para hacer depuración del kernel y, mientras hacía eso, también configuré la ruta del servidor de símbolos.
Empecé mi investigación mirando win32k!DrawSwitchWndHilite, que se mencionaba en la entrada de blog. Se llama desde dos lugares: xxxMoveSwitchWndHilite y xxxPaintSwitchWindow; este último llamó inmediatamente mi atención por las llamadas a GetKeyState/GetAsyncKeyState que lo rodean y que se mencionaban en el informe original. Es más, esas llamadas comprueban si se está pulsando la tecla ALT.

Llamada a DrawSwitchWndHilite desde xxxPaintSwitchWindow
Siguiendo las referencias cruzadas de llamadas (xxxWrapSwitchWndProc->xxxSwitchWndProc->xxxPaintSwitchWindow->DrawSwitchWndHilite), descubrí que el primer elemento de esa cadena se referencia en InitFunctionTables, la función que se corrigió en el parche.
Después miré NtUserMessageCall de la captura de pantalla del código descompilado.
Aquí está la declaración de esta función```cpp
NtUserMessageCall(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ULONG_PTR ResultInfo, DWORD dwType, BOOLEAN bAnsi)
El exploit lo llama con `msg = 0x14` y `dwType = 0xE0`. Veamos qué hace.```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);
Aquí registré una clase de ventana simple y creé una ventana de esa clase. Luego llamé a NtUserMessageCall con los mismos parámetros que el exploit. Para ver qué ocurre internamente, establecí el punto de interrupción kd> ba e 1 win32k!NtUserMessageCall y ejecuté el código.
Se realizan bastantes llamadas a esta función, así que tuve que capturar la correcta, pero no fue tan difícil: era la que tenía una pila de llamadas realmente corta.

NtUserMessageCall
Al recorrer el código paso a paso se revela que llama a una función del array gapfnMessageCall; el índice se calcula a partir del valor de msg y es igual a 0, por lo que la llamada se hace a NtUserfnDWORD.

NtUserfnDWORD
La siguiente llamada se hace usando el valor de dwType, y ahora el offset de gpsi es igual a 0x40, y la llamada conduce a xxxWrapSwitchWndProc (esta función ya apareció cuando revisaba la cadena de llamadas de DrawSwitchWndHilite).
xxxWrapSwitchWndProc simplemente llama a xxxSwitchWndProc.

xxxSwitchWndProc
Y este es el final; el código falla aquí y no avanza hasta xxxPaintSwitchWindow, que es donde queremos llegar según el valor de msg (0x14). Veamos por qué.
El código falla en esta etapa porque, como se resalta en la imagen anterior, el fnid de nuestra ventana no es igual a 0x2A0 (FNID_SWITCH) y el mensaje que enviamos no es igual a 1, por lo que terminamos en xxxDefWindowProc. Para evitar este escenario, tenemos que llamar a xxxSwitchWndProc con fnid establecido a FNID_SWITCH, de modo que vayamos directamente a la sentencia switch y luego a xxxPaintSwitchWindow.
¿Cómo establecer el fnid correcto? En realidad, la misma función lo hace en el primer bloque if; solo tenemos que fallar todas las comprobaciones dentro de él para llegar a la instrucción que establece el fnid.
Estas son las condiciones que debemos cumplir para fallar las tres comprobaciones if: