
POC pour cve-2019-1458
En décembre, Kaspersky a publié un article de blog sur [une vulnérabilité 0-day exploitée dans la nature][1]. Cela a piqué mon intérêt car, bien qu'ils aient décrit le fonctionnement de l'exploit, ils n'ont fourni aucun POC dans leur analyse.
C'est pourquoi j'ai décidé d'essayer d'écrire un POC pour cette vulnérabilité sur la base de l'article de Kaspersky et de l'analyse du correctif.
Ce post décrit mon parcours pour y parvenir.
La première chose a été de recueillir autant d'informations que possible sur cette vulnérabilité. En lisant l'article mentionné, j'ai extrait les informations suivantes :
NtUserMessageCallwin32k!DrawSwitchWndHiliteEn outre, il y a une belle capture d'écran du code décompilé montrant certaines des choses listées précédemment.
Plus précisément, cela montre : la création de la fenêtre de changement, un appel à une fonction nommée toggle_alt_key et plusieurs appels à NtUserMessageCall.
[Source de l'image][1]
Beaucoup d'informations utiles, mais cela ne décrit toujours pas exactement comment cette vulnérabilité fonctionne et comment la déclencher.
[Le module affecté était win32k.sys][2]. J'ai téléchargé les versions corrigée et non corrigée de ce module.
Pour Win7 x64, celles-ci étaient :
Elles peuvent être téléchargées à partir du [Catalogue Microsoft Update][3]
Voici le résultat du bindiff comparant les deux versions

Après avoir éliminé les fonctions liées à la fonctionnalité DebugHook, il ne nous reste vraiment que cette fonction légèrement modifiée InitFunctionTables()

Ce n'est définitivement pas le plus gros correctif qui soit.
Cela n'aidera pas à identifier immédiatement la cause racine de cette vulnérabilité. Mais il est intéressant de noter que certaines valeurs initiales pour les variables à
*(gpsi+0x14E), *(gpsi+0x154), *(gpsi+0x180) ont été ajoutées. Il pourrait donc s'agir d'un bogue lié à une variable non initialisée.
Dans cette section, je présenterai comment j'ai progressivement construit un POC qui déclenche cette vulnérabilité, tout en déterminant simultanément en quoi consistait réellement la vulnérabilité.
Le diffing des correctifs n'a pas donné beaucoup d'informations utiles au début, donc je me suis principalement appuyé sur l'article de Kaspersky lors de la première phase de développement.
Pour avoir un bon environnement de test, j'ai préparé une VM Win7 SP1 x64 avec la dernière version vulnérable de win32k en cours d'exécution. En plus de cela, j'ai attaché Windbg à cette VM pour effectuer du débogage noyau et, ce faisant, j'ai également configuré le chemin du serveur de symboles.
J'ai commencé mon investigation en regardant win32k!DrawSwitchWndHilite qui était mentionné dans l'article. Il est appelé depuis deux endroits : xxxMoveSwitchWndHilite et xxxPaintSwitchWindow ; ce dernier a immédiatement attiré mon attention à cause des appels environnants GetKeyState/GetAsyncKeyState qui étaient mentionnés dans le rapport original. De plus, ces appels vérifient si la touche ALT est enfoncée.

Appel à DrawSwitchWndHilite depuis xxxPaintSwitchWindow
En suivant ensuite les références croisées d'appels (xxxWrapSwitchWndProc->xxxSwitchWndProc->xxxPaintSwitchWindow->DrawSwitchWndHilite), j'ai découvert que le premier élément de cette chaîne est référencé dans InitFunctionTables, la fonction qui a été corrigée dans le correctif.
Ensuite, je me suis penché sur NtUserMessageCall à partir de la capture d'écran du code décompilé.
Voici la déclaration de cette fonction```cpp
NtUserMessageCall(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ULONG_PTR ResultInfo, DWORD dwType, BOOLEAN bAnsi)
Exploit l'appelle avec `msg = 0x14` et `dwType = 0xE0`. Voyons ce que ça fait.```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);
Ici, j'ai enregistré une classe de fenêtre simple et créé une fenêtre de cette classe. Ensuite, j'ai appelé NtUserMessageCall avec les mêmes paramètres que l'exploit. Pour voir ce qui se passe sous le capot, j'ai positionné un point d'arrêt kd> ba e 1 win32k!NtUserMessageCall et exécuté le code.
Il y a pas mal d'appels à cette fonction, j'ai donc dû attraper le bon, mais ce n'était pas si difficile, c'était celui avec une pile d'appels très courte.

NtUserMessageCall
En parcourant le code, on voit qu'il appelle une fonction du tableau gapfnMessageCall, l'index est calculé en fonction de la valeur msg et vaut 0, donc l'appel est fait à NtUserfnDWORD

NtUserfnDWORD
L'appel suivant est effectué en utilisant la valeur dwType, et maintenant le décalage gpsi vaut 0x40, et l'appel mène à xxxWrapSwitchWndProc (cette fonction est déjà apparue lorsque je vérifiais la chaîne d'appels de DrawSwitchWndHilite).
xxxWrapSwitchWndProc appelle simplement xxxSwitchWndProc.

xxxSwitchWndProc
Et c'est la fin, le code échoue ici, n'allant pas plus loin vers xxxPaintSwitchWindow, qui est là où nous voulons arriver en fonction de la valeur msg (0x14). Vérifions pourquoi.