Страницы

четверг, 14 мая 2015 г.

Как «русские хакеры» пытались узнать о новых санкциях против России: изучаем CVE-2015-1701

Об этой уязвимости нулевого дня в Windows стало известно еще 20 апреля, когда компания FireEye и агентство Bloomberg сообщили о неудачной кибератаке на правительственное ведомство зарубежного государства, обсуждавшее с США политику санкций против России. В причастности к содеянному, а также в попытках взлома спецслужб НАТО, госорганов Грузии, Польши, Венгрии в FireEye обвинили «русских хакеров» из группировки APT28.

Атака была реализована с помощью ранее неизвестных уязвимостей CVE-2015-3043 в Adobe Flash и CVE-2015-1701 в Windows. Пользователя отправляли по ссылке на зараженный сайт, где скрипт JavaScript с помощью Flash-уязвимости подгружал в компьютер исполняемый файл, который посредством дыры CVE-2015-1701 в Windows повышал привилегии и похищал ключи шифрования.

Компания Adobe в течение считанных часов устранила уязвимость во Flash, но в Microsoft не торопились и выпустили патч только накануне. В этом материале мы расскажем об основных особенностях данного бага.

Ценная gSharedInfo

Сначала следует описать некоторые структуры и механизмы, используемые для эксплуатации CVE-2015-1701 уязвимости. Без печально известной win32k.sys не обошлось и на этот раз, поэтому первым делом остановимся на структуре win32k!tagSHAREDINFO, которой отвечает символ win32k!gSharedInfo, а также на типе данных HWND, который с ней очень тесно связан.


Наша gSharedInfo хранит указатели на различные связанные с окнами структуры и, что самое замечательное, многие из этих структур отображены в пользовательское пространство (смаплены в юзер-мод, по-нашему), причём соответствующий символ user32!gSharedInfo с некоторых пор (либо с висты, либо с семёрки) стал экспортируемым.


Нас здесь интересуют два поля:

  • aheList — указывает на массив элементов типа win32k!_HANDLEENTRY;
  • HeEntrySize — содержит размер элемента win32k!_HANDLEENTRY.

Так вот, младшие 16 бит дескриптора окна HWND на самом деле являются индексом в массиве gSharedInfo->aheList. Например, если у нас переменная window содержит HWND дескриптор:


и то же самое в ядре:


Поле wUniq структуры win32k!_HANDLEENTRY содержит верхние 16 бит дескриптора HWND и, судя по всему, служит простой цели разделения объектов, занимающих в разные промежутки времени один и тот же адрес в данном массиве. Таким образом, если объект будет освобождён и позже его место займёт, к примеру, новое окно с wUniq = 0x12, то по старому дескриптору 0x0011024c к нему обратиться уже будет нельзя.

Поля bFlags и bType содержат различные флаги и тип объекта, адресуемого полем phead, соответственно. Подробнее возможные принимаемые ими значения можно глянуть в ReactOS.

Нас же здесь интересует только одно возможное значение bType:

TYPE_WINDOW = 1

означающее, что объект является окном, а поле phead адресует структуру win32k!tagWND.


Здесь можно обратить внимание, что и пользовательское user32!gSharedInfo->aheList[…].phead хранит адрес, принадлежащий ядру. Впрочем, при желании можно получить адрес его пользовательского отображения, но это уже другая история, поэтому за подробностями отсылаю вас к принимающей на вход дескриптор окна HWND и возвращающей tagWND* процедуре user32!ValidateHWND, а точнее к вызываемой ею user32!HMValidateHandle.

Последнее не рассмотренное ранее поле pOwner структуры win32k!_HANDLEENTRY содержит указатель на win32k!_W32THREAD потока, которому принадлежит объект. Каждый поток хранит этот указатель в win32k!_KTHREAD->Win32Thread (почему не в _ETHREAD), а также, что в нашем случае намного важнее, в TEB!Win32ThreadInfo.


Пользуясь всей этой информацией, мы можем искать окна, принадлежащие потоку нашего процесса, и восстанавливать их дескрипторы. Для этого нужно найти такой элемент user32!gSharedInfo->aheList[…], у которого:

  • bType == TYPE_WINDOW;
  • pOwner == TEB!Win32ThreadInfo.

Индекс такой структуры будет равен младшим 16 битам дескриптора, а старшие 16 бит будут содержаться в поле wUniq.

Почему просто не воспользоваться user32!FindWindow? В тот момент, когда нам это потребуется, у окна ещё не будет заполнено ни имя, ни класс.

KernelCallbackTable

Другой концепт, который следует объяснить, тесно связан с полем PEB!KernelCallbackTable.


Как видно, здесь содержатся различные коллбеки, но они, конечно же, не kernel, а название своё получили оттого, что их клиентом обычно является win32k.sys, обращающийся к ним, когда требуется совершить операцию в пользовательском пространстве. Вызов происходит через ntdll!KiUserCallbackDispatcher сходным с диспетчеризацией исключений образом.

В ядре механизм вызова этих коллбеков реализуется в функции nt!KeUserModeCallback. Вызов происходит по индексу коллбека. Резолвинг адреса по индексу производится уже в ntdll!KiUserCallbackDispatcher.

SetWindowLongPtr

Далее на очереди user32!SetWindowLongPtr, а на самом деле — его исполнение в виде win32k!xxxSetWindowData. Ограничимся только одним интересующим нас случаем — с параметром GWLP_WNDPROC.

win32k!xxxSetWindowData сначала выполняет различные проверки. Например, принадлежит ли окно процессу, поток которого пытается установить WndProc, а также, не является ли это окно уже уничтоженным (FNID_DELETED_BIT бит).

Затем происходит очень важная для нас оптимизация.


На переданный параметр WndProc (value_ в скриншоте) вызывается MapClientToServerPfn. Эта простенькая и в то же время чрезвычайно полезная функция отображает функции из win32k!gpsi->apfnClientW и win32k!gpsi->apfnClientA на соответствующие им функции из win32k!gpsi-> aStoCidPfn:




 Если такое отображение для переданного WndProc возможно, то вызов процедуры можно оптимизировать, обращаясь напрямую к имплементации функции в ядре, например, win32k!xxxDefWindowProc, не тратя время на переключение в пользовательский режим для вызова обёртки, например, ntdll!NtdllDefWindowProc_A, на которую user32!DefWindowProcA является сквозным экспортом.

Как видно из скриншота, если отображение удачно, то у окна возводится флаг WFSERVERSIDEPROC, после чего отображённое значение заносится в его поле win32k!tagWND->lpfnWndProc.

Таким образом, если через user32!SetWindowLongPtr установить одну из стандартных процедур, то на самом деле выполняться будет соответствующая ей процедура из win32k.sys в режиме ядра.

xxxCreateWindowEx

Теперь рассмотрим создание окна. За это, грубо говоря, целиком и полностью ответственна процедура win32k!xxxCreateWindowEx. Сначала вызовом win32k!HMAllocObject аллоцируется объект tagWND и информация о нём заносится в таблицу gSharedInfo->aheList:


Затем происходит заполнение атрибутов окна. Вся процедура даже в hex-rays занимает пару тысяч строк, поэтому подробно останавливаться на всех совершаемых действиях нет ни смысла, ни возможности.

Вариант эксплуатации

Возникает вопрос: что случится, если вызвать SetWindowLongPtr(hwnd, GWLP_WNDPROC, DefWindowProc) на окно в тот момент, когда оно уже создано, но у него ещё на заполнено поле lpfnWndProc. Ведь это поле заполняется из поля класса, в котором оно, вероятно, уже хранится отображённым по MapClientToServerPfn, если такое отображение возможно.

И действительно, существует вероятность вызовом SetWindowLongPtr возвести флаг WFSERVERSIDEPROC до того, как адрес WndProc будет заполнен значением из поля класса. При этом, данный флаг не скидывается при установке поля WndProc, так как разработчики не предполагали возможности, что он может быть установлен. Присутствует только логика установки флага для окна, если соответствующий флаг класса возведён.


Впрочем, вероятность установки флага из соседнего потока вызовом SetWindowLongPtr во время выполнения CreateWindowEx ничтожна, ведь нужно сначала отыскать HWND окна в массиве user32!gSharedInfo->aheList, после чего цепочка вызовов user32!SetWindowLongPtr -> … -> win32k!xxxSetWindowData должна отработать быстрее, чем произойдёт инициализация полей tagWND в win32k!xxxCreateWindowEx. Можно, конечно, поиграть с processor affinity и приоритетами потоков. Однако, для Windows 7 и более ранних версий существует простой путь.

Вариант для Windows 7

Несмотря на громадные размеры функции win32k!xxxCreateWindowEx, вся интересующая нас информация вполне укладывается в несколько hex-rays строк:


Если во время регистрации класса окна у него была указана картинка для обычной иконки hIcon, но не была указана для маленькой иконки hIconSm, то win32k!xxxCreateWindowEx при первом создании окна такого класса копирует, а точнее – масштабирует, иконку для заполнения поля win32k!tagCLS->spicnSm. Это действие выполняется функцией win32k!xxxCreateClassSmIcon, которая перепоручает задание одному из описанных выше пользовательских так называемых kernel callbacks:


Под 0x36-м номером в таблице идёт user32!_ClientCopyImage. Он и выполняет поставленную задачу.


После копирования иконки в win32k!xxxCreateWindowEx сразу заполняется WndProc окна из WndProc класса. Затем, как видно, если флаг WFSERVERSIDEPROC возведён в классе, он возводится и для окна.

Результаты

В результате получаем следующее. Для начала требуется зарегистрировать класс с указанием обычной иконки, но без указания маленькой:



Также нужен хук на user32!_ClientCopyImage:


Он будет вызывать SetWindowLongPtr для только что созданного окна:


После чего, в момент создания окна вызывается установленный ранее хук.


Окно в этот момент уже занесено в таблицу, но ещё не инициализировано.



Хук вызывает SetWindowLongPtr, который возводит флаг bServerSideWindowProc в соответствующей окну структуре.


А по возвращении из коллбека, win32k!xxxCreateWindowEx перезаписывает lpfnWndProc значением из поля класса.


Таким образом, оконная функция, указанная при регистрации класса, будет выполняться в ядре:


Очевидно, самое простое, для чего это можно использовать — это воровство системного токена с последующим запуском системного шелла.




P.S. Беглый осмотр Windows 8.1 показал, что в win32k!xxxCreateWindowEx установка tagWND->lpfnWndProc и вызов win32k!xxxCreateClassSmIcon идут в обратной последовательности по сравнению с более ранними версиями. Таким образом, хук на user32!_ClientCopyImage уже не поможет.

Есть вероятность, что «race condition» всё ещё существует и может быть с некоторой вероятностью проэксплуатирован выше описанным способом с двумя потоками. На этот счёт ничего более точного сейчас сказать не получится

Комментариев нет:

Отправить комментарий