
POC для cve-2019-1458
В декабре Лаборатория Касперского опубликовала пост в блоге об [используемом в дикой природе 0day-эксплойте][1]. Это заинтересовало меня, потому что, хотя они описали, как работает эксплойт, они не предоставили никакого POC в своём анализе.
Поэтому я решил попробовать написать POC для этой уязвимости на основе поста Лаборатории Касперского и анализа патча.
Этот пост описывает мой путь в этом деле.
Первым делом нужно было собрать как можно больше информации об этой уязвимости. Читая упомянутый пост в блоге, я извлёк следующую информацию:
NtUserMessageCallwin32k!DrawSwitchWndHiliteКроме того, есть отличный скриншот декомпилированного кода, показывающий некоторые из перечисленных выше вещей.
Если быть точным, он показывает: создание окна переключения, вызов функции с именем toggle_alt_key и множественные вызовы NtUserMessageCall.
[Источник изображения][1]
Много полезной информации, но она всё ещё не описывает, как именно работает эта уязвимость и как её вызвать.
[Затронутым модулем был win32k.sys][2]. Я скачал пропатченную и непропатченную версии этого модуля.
Для win7 x64 это были:
Их можно скачать из [Microsoft Update Catalog][3]
Вот результат bindiff при сравнении обеих версий

После исключения функций, связанных с функциональностью DebugHook, у нас фактически остаётся эта слегка изменённая функция InitFunctionTables()

Определённо не самый большой патч.
Это не поможет сразу выявить корневую причину этой уязвимости. Но стоит отметить, что были добавлены некоторые начальные значения для переменных в
*(gpsi+0x14E), *(gpsi+0x154), *(gpsi+0x180). Так что это может быть ошибка, связанная с неинициализированной переменной.
В этом разделе я покажу, как я постепенно создавал POC, который вызывает эту уязвимость, одновременно выясняя, чем на самом деле является эта уязвимость.
На начальном этапе диффинг патча не дал слишком много полезной информации, поэтому на первой стадии разработки я в основном полагался на пост в блоге Лаборатории Касперского.
Чтобы иметь хорошую тестовую среду, я подготовил VM с Win7 SP1 x64 и последней уязвимой версией win32k. Кроме того, я подключил Windbg к этой VM для отладки ядра и заодно настроил путь к серверу символов.
Я начал исследование с изучения win32k!DrawSwitchWndHilite, которая упоминалась в посте в блоге. Она вызывается из двух мест: xxxMoveSwitchWndHilite и xxxPaintSwitchWindow; последнее сразу привлекло моё внимание из-за окружающих вызовов GetKeyState/GetAsyncKeyState, упомянутых в исходном отчёте. Более того, эти вызовы проверяют, нажата ли клавиша ALT.

Вызов DrawSwitchWndHilite из xxxPaintSwitchWindow
Продолжая прослеживать перекрёстные ссылки вызовов (xxxWrapSwitchWndProc->xxxSwitchWndProc->xxxPaintSwitchWindow->DrawSwitchWndHilite), я обнаружил, что на первый элемент этой цепочки есть ссылка в InitFunctionTables, функции, которая была исправлена в патче.
Затем я обратился к NtUserMessageCall со скриншота декомпилированного кода.
Вот объявление этой функции```cpp
NtUserMessageCall(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ULONG_PTR ResultInfo, DWORD dwType, BOOLEAN bAnsi)
Эксплойт вызывает его с `msg = 0x14` и `dwType = 0xE0`. Посмотрим, что он делает.```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);
Здесь я зарегистрировал простой оконный класс и создал окно этого класса. Затем вызвал NtUserMessageCall с теми же параметрами, что и в эксплойте. Чтобы увидеть, что происходит под капотом, я установил точку останова kd> ba e 1 win32k!NtUserMessageCall и запустил код.
К этой функции выполняется довольно много вызовов, поэтому мне пришлось поймать нужный, но это было не так сложно — это был вызов с очень коротким стеком вызовов.

NtUserMessageCall
Проход по коду показал, что она вызывает функцию из массива gapfnMessageCall; индекс вычисляется на основе значения msg и равен 0, поэтому вызов направляется к NtUserfnDWORD.

NtUserfnDWORD
Следующий вызов выполняется с использованием значения dwType, и теперь смещение gpsi равно 0x40, а вызов ведёт к xxxWrapSwitchWndProc (эта функция уже появлялась, когда я проверял цепочку вызовов DrawSwitchWndHilite).
xxxWrapSwitchWndProc просто вызывает xxxSwitchWndProc.

xxxSwitchWndProc
И это конец: код падает здесь и не идёт дальше к xxxPaintSwitchWindow, а именно туда мы хотим попасть исходя из значения msg (0x14). Давайте разберёмся, почему.
На этом этапе код падает, потому что, как показано на предыдущем изображении, fnid нашего окна не равен 0x2A0 (FNID_SWITCH), а отправляемое сообщение не равно 1, поэтому мы попадаем в xxxDefWindowProc. Чтобы избежать этого сценария, нам нужно вызвать xxxSwitchWndProc с fnid, установленным в FNID_SWITCH, чтобы перейти сразу к конструкции switch, а затем к xxxPaintSwitchWindow.
Как установить правильный fnid? Вообще-то та же функция делает это в первом блоке if, нам просто нужно провалить все проверки внутри него, чтобы добраться до инструкции, устанавливающей fnid.
Вот условия, которые нужно выполнить, чтобы провалить все три проверки if: