
POC per cve-2019-1458
A dicembre Kaspersky ha pubblicato un post sul blog riguardante [l'exploit 0day usato in the wild][1]. Ha suscitato il mio interesse perché, sebbene abbiano descritto come funzionava l'exploit, non hanno fornito alcuna POC nella loro analisi.
Questo è il motivo per cui ho deciso di provare a scrivere una POC per questa vulnerabilità basandomi sul post di Kaspersky e sull'analisi della patch.
Questo post descrive il mio percorso in questa attività.
La prima cosa è stata raccogliere quante più informazioni possibili su questa vulnerabilità. Leggendo il post citato ho estratto le seguenti informazioni:
NtUserMessageCallwin32k!DrawSwitchWndHiliteOltre a questo c'è una bella schermata di codice decompilato che mostra alcune delle cose elencate in precedenza.
Per la precisione mostra: la creazione della finestra di commutazione, la chiamata a una funzione chiamata toggle_alt_key e numerose chiamate a NtUserMessageCall.
[Fonte dell'immagine][1]
Molte informazioni utili, ma non descrive ancora esattamente come funziona questa vulnerabilità e come attivarla.
[Il modulo interessato era win32k.sys][2]. Ho scaricato sia la versione patchata che quella non patchata di questo modulo.
Per Win7 x64 queste erano:
Possono essere scaricate dal [Microsoft Update Catalog][3]
Ecco il risultato del bindiff del confronto tra le due versioni

Dopo aver escluso le funzioni relative alla funzionalità DebugHook, ciò che ci resta davvero è questa funzione leggermente modificata InitFunctionTables()

Sicuramente non è la patch più grande in circolazione.
Questo non aiuta a identificare immediatamente la causa principale di questa vulnerabilità. Ma vale la pena notare che sono stati aggiunti alcuni valori iniziali per le variabili in
*(gpsi+0x14E), *(gpsi+0x154), *(gpsi+0x180). Quindi potrebbe essere un bug legato a una variabile non inizializzata.
In questa sezione presenterò come ho costruito progressivamente una POC che attiva questa vulnerabilità, capendo allo stesso tempo cosa fosse realmente la vulnerabilità.
Il diffing della patch non ha fornito molte informazioni utili all'inizio, quindi nella prima fase di sviluppo mi sono affidato soprattutto al post di Kaspersky.
Per avere un buon ambiente di test ho preparato una VM Win7 SP1 x64 con l'ultima versione vulnerabile di win32k in esecuzione. Inoltre ho collegato Windbg a questa VM per eseguire il debug del kernel e, nel farlo, ho anche configurato il percorso del symbol server.
Ho iniziato la mia indagine esaminando win32k!DrawSwitchWndHilite, menzionata nel post. Viene chiamata da due punti: xxxMoveSwitchWndHilite e xxxPaintSwitchWindow; quest'ultimo ha subito attirato la mia attenzione, per le chiamate circostanti a GetKeyState/GetAsyncKeyState menzionate nel report originale. Inoltre, queste chiamate verificano che il tasto ALT sia premuto.

Chiamata a DrawSwitchWndHilite da xxxPaintSwitchWindow
Seguendo ulteriormente i riferimenti incrociati delle chiamate (xxxWrapSwitchWndProc->xxxSwitchWndProc->xxxPaintSwitchWindow->DrawSwitchWndHilite) ho scoperto che il primo elemento di questa catena è referenziato in InitFunctionTables, la funzione che è stata corretta dalla patch.
Poi ho esaminato NtUserMessageCall dalla schermata del codice decompilato.
Ecco la dichiarazione di questa funzione```cpp
NtUserMessageCall(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ULONG_PTR ResultInfo, DWORD dwType, BOOLEAN bAnsi)
L'exploit lo sta chiamando con `msg = 0x14` e `dwType = 0xE0`. Vediamo cosa fa.```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);
Qui ho registrato una semplice classe di finestra e ho creato una finestra di quella classe. Poi ho chiamato NtUserMessageCall con gli stessi parametri dell'exploit. Per vedere cosa succede sotto il cofano ho impostato un breakpoint kd> ba e 1 win32k!NtUserMessageCall ed eseguito il codice.
Ci sono parecchie chiamate a questa funzione, quindi ho dovuto individuare quella giusta, ma non è stato difficile: era quella con uno stack di chiamate davvero corto.

NtUserMessageCall
Procedendo passo passo nel codice si scopre che viene chiamata una funzione dall'array gapfnMessageCall; l'indice viene calcolato in base al valore di msg ed è uguale a 0, quindi la chiamata va a NtUserfnDWORD.

NtUserfnDWORD
La chiamata successiva viene effettuata usando il valore dwType; ora l'offset di gpsi è uguale a 0x40 e la chiamata porta a xxxWrapSwitchWndProc (questa funzione era già comparsa quando controllavo la catena di chiamate di DrawSwitchWndHilite).
xxxWrapSwitchWndProc chiama semplicemente xxxSwitchWndProc.

xxxSwitchWndProc
E qui finisce tutto: il codice fallisce, senza andare avanti fino a xxxPaintSwitchWindow, che è proprio il punto in cui vogliamo arrivare in base al valore di msg (0x14). Vediamo perché.
Il codice fallisce in questa fase perché, come evidenziato nell'immagine precedente, il fnid della nostra finestra non è uguale a 0x2A0 (FNID_SWITCH) e il messaggio che stiamo inviando non è uguale a 1, quindi finiamo in xxxDefWindowProc. Per evitare questo scenario dobbiamo chiamare xxxSwitchWndProc con fnid impostato a FNID_SWITCH, così da andare direttamente allo switch e successivamente a xxxPaintSwitchWindow.
Come impostare il fnid corretto? In realtà la stessa funzione lo fa nel primo blocco if: dobbiamo solo fallire tutti i controlli al suo interno per arrivare all'istruzione che imposta il fnid.
Ecco le condizioni che dobbiamo soddisfare per far fallire tutti e tre i controlli if: