Об этой уязвимости нулевого дня в Windows стало известно еще 20 апреля, когда компания FireEye и агентство Bloomberg сообщили о неудачной кибератаке на правительственное ведомство зарубежного государства, обсуждавшее с США политику санкций против России. В причастности к содеянному, а также в попытках взлома спецслужб НАТО, госорганов Грузии, Польши, Венгрии в FireEye обвинили «русских хакеров» из группировки APT28.
Об этой уязвимости нулевого дня в 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
.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
.wUniq
.
user32!FindWindow
? В тот момент, когда нам это потребуется, у окна ещё не будет заполнено ни имя, ни класс.
PEB!KernelCallbackTable
.
kernel
, а название своё получили оттого, что их клиентом обычно является win32k.sys
, обращающийся к ним, когда требуется совершить операцию в пользовательском пространстве. Вызов происходит через ntdll!KiUserCallbackDispatcher
сходным с диспетчеризацией исключений образом.
nt!KeUserModeCallback
. Вызов происходит по индексу коллбека. Резолвинг адреса по индексу производится уже в ntdll!KiUserCallbackDispatcher
.
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
в режиме ядра.
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 и более ранних версий существует простой путь.
win32k!xxxCreateWindowEx
, вся интересующая нас информация вполне укладывается в несколько hex-rays строк:
hIcon
, но не была указана для маленькой иконки hIconSm
, то win32k!xxxCreateWindowEx
при первом создании окна такого класса копирует, а точнее – масштабирует, иконку для заполнения поля win32k!tagCLS->spicnSm
. Это действие выполняется функцией win32k!xxxCreateClassSmIcon
, которая перепоручает задание одному из описанных выше пользовательских так называемых kernel callbacks
:
user32!_ClientCopyImage
. Он и выполняет поставленную задачу.
win32k!xxxCreateWindowEx
сразу заполняется WndProc
окна из WndProc
класса. Затем, как видно, если флагWFSERVERSIDEPROC
возведён в классе, он возводится и для окна.
user32!_ClientCopyImage
:
SetWindowLongPtr
для только что созданного окна:
SetWindowLongPtr
, который возводит флаг bServerSideWindowProc
в соответствующей окну структуре.
win32k!xxxCreateWindowEx
перезаписывает lpfnWndProc
значением из поля класса.
win32k!xxxCreateWindowEx
установка tagWND->lpfnWndProc
и вызовwin32k!xxxCreateClassSmIcon
идут в обратной последовательности по сравнению с более ранними версиями. Таким образом, хук наuser32!_ClientCopyImage
уже не поможет.