Об этой уязвимости нулевого дня в Windows стало известно еще 20 апреля, когда компания FireEye и агентство Bloomberg сообщили о неудачной кибератаке на правительственное ведомство зарубежного государства, обсуждавшее с США политику санкций против России. В причастности к содеянному, а также в попытках взлома спецслужб НАТО, госорганов Грузии, Польши, Венгрии в FireEye обвинили «русских хакеров» из группировки APT28.
Атака была реализована с помощью ранее неизвестных уязвимостей CVE-2015-3043 в Adobe Flash и CVE-2015-1701 в Windows. Пользователя отправляли по ссылке на зараженный сайт, где скрипт JavaScript с помощью Flash-уязвимости подгружал в компьютер исполняемый файл, который посредством дыры CVE-2015-1701 в Windows повышал привилегии и похищал ключи шифрования.
Компания Adobe в течение считанных часов устранила уязвимость во Flash, но в Microsoft не торопились и выпустили патч только накануне. В этом материале мы расскажем об основных особенностях данного бага.
Сначала следует описать некоторые структуры и механизмы, используемые для эксплуатации CVE-2015-1701 уязвимости. Без печально известной
Наша
Нас здесь интересуют два поля:
Так вот, младшие 16 бит дескриптора окна
![](https://habrastorage.org/r/w1560/files/a9d/9f0/ff3/a9d9f0ff33f34b489b87b5bba1cd9d94.png)
и то же самое в ядре:
Поле
Поля
Нас же здесь интересует только одно возможное значение
означающее, что объект является окном, а поле
Здесь можно обратить внимание, что и пользовательское
Последнее не рассмотренное ранее поле
Пользуясь всей этой информацией, мы можем искать окна, принадлежащие потоку нашего процесса, и восстанавливать их дескрипторы. Для этого нужно найти такой элемент
Индекс такой структуры будет равен младшим 16 битам дескриптора, а старшие 16 бит будут содержаться в поле
Почему просто не воспользоваться
Другой концепт, который следует объяснить, тесно связан с полем
Как видно, здесь содержатся различные коллбеки, но они, конечно же, не
В ядре механизм вызова этих коллбеков реализуется в функции
Далее на очереди
Затем происходит очень важная для нас оптимизация.
На переданный параметр
![](https://habrastorage.org/r/w1560/files/88b/134/2b6/88b1342b64d84d3fb3cc4ab4136d6597.png)
![](https://habrastorage.org/r/w1560/files/d0f/684/a7f/d0f684a7f8ec40bdb67e9efc7ecd487f.png)
Если такое отображение для переданного
Как видно из скриншота, если отображение удачно, то у окна возводится флаг
Таким образом, если через
Теперь рассмотрим создание окна. За это, грубо говоря, целиком и полностью ответственна процедура
![](https://habrastorage.org/r/w1560/files/659/81f/b12/65981fb124904d77ace04a676204281f.png)
Затем происходит заполнение атрибутов окна. Вся процедура даже в
Возникает вопрос: что случится, если вызвать
И действительно, существует вероятность вызовом
Впрочем, вероятность установки флага из соседнего потока вызовом
Несмотря на громадные размеры функции
Если во время регистрации класса окна у него была указана картинка для обычной иконки
Под 0x36-м номером в таблице идёт
После копирования иконки в
В результате получаем следующее. Для начала требуется зарегистрировать класс с указанием обычной иконки, но без указания маленькой:
![](https://habrastorage.org/r/w1560/files/db2/736/7e9/db27367e96df4c7394c124d36f4802a0.png)
Также нужен хук на
Он будет вызывать
После чего, в момент создания окна вызывается установленный ранее хук.
Окно в этот момент уже занесено в таблицу, но ещё не инициализировано.
![](https://habrastorage.org/r/w1560/files/9d9/5f8/99e/9d95f899e97b481094c1b296e243265a.png)
Хук вызывает
А по возвращении из коллбека,
Таким образом, оконная функция, указанная при регистрации класса, будет выполняться в ядре:
Очевидно, самое простое, для чего это можно использовать — это воровство системного токена с последующим запуском системного шелла.
P.S. Беглый осмотр Windows 8.1 показал, что в
Есть вероятность, что «race condition» всё ещё существует и может быть с некоторой вероятностью проэксплуатирован выше описанным способом с двумя потоками. На этот счёт ничего более точного сейчас сказать не получится.
Атака была реализована с помощью ранее неизвестных уязвимостей 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
, который с ней очень тесно связан.![](https://habrastorage.org/files/2f9/e01/d11/2f9e01d11ae14fb1968afc035b733cd5.png)
Наша
gSharedInfo
хранит указатели на различные связанные с окнами структуры и, что самое замечательное, многие из этих структур отображены в пользовательское пространство (смаплены в юзер-мод, по-нашему), причём соответствующий символ user32!gSharedInfo
с некоторых пор (либо с висты, либо с семёрки) стал экспортируемым.![](https://habrastorage.org/files/079/786/b9c/079786b9c96a4f16b9fb8552e9854648.png)
Нас здесь интересуют два поля:
aheList
— указывает на массив элементов типаwin32k!_HANDLEENTRY
;HeEntrySize
— содержит размер элементаwin32k!_HANDLEENTRY
.
Так вот, младшие 16 бит дескриптора окна
HWND
на самом деле являются индексом в массиве gSharedInfo->aheList
. Например, если у нас переменная window
содержит HWND
дескриптор:![](https://habrastorage.org/files/a9d/9f0/ff3/a9d9f0ff33f34b489b87b5bba1cd9d94.png)
и то же самое в ядре:
![](https://habrastorage.org/files/c3f/75f/6b9/c3f75f6b907d4cefb3f5553bda8689b5.png)
Поле
wUniq
структуры win32k!_HANDLEENTRY
содержит верхние 16 бит дескриптора HWND
и, судя по всему, служит простой цели разделения объектов, занимающих в разные промежутки времени один и тот же адрес в данном массиве. Таким образом, если объект будет освобождён и позже его место займёт, к примеру, новое окно с wUniq = 0x12
, то по старому дескриптору 0x0011024c
к нему обратиться уже будет нельзя.Поля
bFlags
и bType
содержат различные флаги и тип объекта, адресуемого полем phead, соответственно. Подробнее возможные принимаемые ими значения можно глянуть в ReactOS.Нас же здесь интересует только одно возможное значение
bType
:TYPE_WINDOW = 1
означающее, что объект является окном, а поле
phead
адресует структуру win32k!tagWND
.![](https://habrastorage.org/files/1fe/a51/c56/1fea51c567034ff0a995b859bb63f420.png)
Здесь можно обратить внимание, что и пользовательское
user32!gSharedInfo->aheList[…].phead
хранит адрес, принадлежащий ядру. Впрочем, при желании можно получить адрес его пользовательского отображения, но это уже другая история, поэтому за подробностями отсылаю вас к принимающей на вход дескриптор окна HWND
и возвращающей tagWND*
процедуре user32!ValidateHWND
, а точнее к вызываемой ею user32!HMValidateHandle
.Последнее не рассмотренное ранее поле
pOwner
структуры win32k!_HANDLEENTRY
содержит указатель на win32k!_W32THREAD
потока, которому принадлежит объект. Каждый поток хранит этот указатель в win32k!_KTHREAD->Win32Thread
(почему не в _ETHREAD
), а также, что в нашем случае намного важнее, в TEB!Win32ThreadInfo
.![](https://habrastorage.org/files/d78/0bc/129/d780bc129c6a423ca3f44cc05540b3e7.png)
Пользуясь всей этой информацией, мы можем искать окна, принадлежащие потоку нашего процесса, и восстанавливать их дескрипторы. Для этого нужно найти такой элемент
user32!gSharedInfo->aheList[…]
, у которого:bType == TYPE_WINDOW
;pOwner == TEB!Win32ThreadInfo
.
Индекс такой структуры будет равен младшим 16 битам дескриптора, а старшие 16 бит будут содержаться в поле
wUniq
.Почему просто не воспользоваться
user32!FindWindow
? В тот момент, когда нам это потребуется, у окна ещё не будет заполнено ни имя, ни класс.KernelCallbackTable
Другой концепт, который следует объяснить, тесно связан с полем
PEB!KernelCallbackTable
.![](https://habrastorage.org/files/f06/bc6/33f/f06bc633f5584524a9291a9fcd80f209.png)
Как видно, здесь содержатся различные коллбеки, но они, конечно же, не
kernel
, а название своё получили оттого, что их клиентом обычно является win32k.sys
, обращающийся к ним, когда требуется совершить операцию в пользовательском пространстве. Вызов происходит через ntdll!KiUserCallbackDispatcher
сходным с диспетчеризацией исключений образом. В ядре механизм вызова этих коллбеков реализуется в функции
nt!KeUserModeCallback
. Вызов происходит по индексу коллбека. Резолвинг адреса по индексу производится уже в ntdll!KiUserCallbackDispatcher
.SetWindowLongPtr
Далее на очереди
user32!SetWindowLongPtr
, а на самом деле — его исполнение в виде win32k!xxxSetWindowData
. Ограничимся только одним интересующим нас случаем — с параметром GWLP_WNDPROC
.win32k!xxxSetWindowData
сначала выполняет различные проверки. Например, принадлежит ли окно процессу, поток которого пытается установить WndProc
, а также, не является ли это окно уже уничтоженным (FNID_DELETED_BIT
бит).Затем происходит очень важная для нас оптимизация.
![](https://habrastorage.org/files/277/2e9/377/2772e93778534d4aabf3829d885189f7.png)
На переданный параметр
WndProc
(value_
в скриншоте) вызывается MapClientToServerPfn
. Эта простенькая и в то же время чрезвычайно полезная функция отображает функции из win32k!gpsi->apfnClientW
и win32k!gpsi->apfnClientA
на соответствующие им функции из win32k!gpsi-> aStoCidPfn
:![](https://habrastorage.org/files/88b/134/2b6/88b1342b64d84d3fb3cc4ab4136d6597.png)
![](https://habrastorage.org/files/d0f/684/a7f/d0f684a7f8ec40bdb67e9efc7ecd487f.png)
![](https://habrastorage.org/files/1d9/1e2/5fb/1d91e25fbe80413daf5de5be467d3bd2.png)
Если такое отображение для переданного
WndProc
возможно, то вызов процедуры можно оптимизировать, обращаясь напрямую к имплементации функции в ядре, например, win32k!xxxDefWindowProc
, не тратя время на переключение в пользовательский режим для вызова обёртки, например, ntdll!NtdllDefWindowProc_A
, на которую user32!DefWindowProcA
является сквозным экспортом.Как видно из скриншота, если отображение удачно, то у окна возводится флаг
WFSERVERSIDEPROC
, после чего отображённое значение заносится в его поле win32k!tagWND->lpfnWndProc
.Таким образом, если через
user32!SetWindowLongPtr
установить одну из стандартных процедур, то на самом деле выполняться будет соответствующая ей процедура из win32k.sys
в режиме ядра.xxxCreateWindowEx
Теперь рассмотрим создание окна. За это, грубо говоря, целиком и полностью ответственна процедура
win32k!xxxCreateWindowEx
. Сначала вызовом win32k!HMAllocObject
аллоцируется объект tagWND
и информация о нём заносится в таблицу gSharedInfo->aheList
:![](https://habrastorage.org/files/659/81f/b12/65981fb124904d77ace04a676204281f.png)
Затем происходит заполнение атрибутов окна. Вся процедура даже в
hex-rays
занимает пару тысяч строк, поэтому подробно останавливаться на всех совершаемых действиях нет ни смысла, ни возможности.Вариант эксплуатации
Возникает вопрос: что случится, если вызвать
SetWindowLongPtr(hwnd, GWLP_WNDPROC, DefWindowProc)
на окно в тот момент, когда оно уже создано, но у него ещё на заполнено поле lpfnWndProc
. Ведь это поле заполняется из поля класса, в котором оно, вероятно, уже хранится отображённым по MapClientToServerPfn
, если такое отображение возможно.И действительно, существует вероятность вызовом
SetWindowLongPtr
возвести флаг WFSERVERSIDEPROC
до того, как адрес WndProc
будет заполнен значением из поля класса. При этом, данный флаг не скидывается при установке поля WndProc
, так как разработчики не предполагали возможности, что он может быть установлен. Присутствует только логика установки флага для окна, если соответствующий флаг класса возведён.![](https://habrastorage.org/files/6a1/193/e96/6a1193e96a6e4acfb6828489cc096ecd.png)
Впрочем, вероятность установки флага из соседнего потока вызовом
SetWindowLongPtr
во время выполнения CreateWindowEx
ничтожна, ведь нужно сначала отыскать HWND
окна в массиве user32!gSharedInfo->aheList
, после чего цепочка вызовов user32!SetWindowLongPtr -> … -> win32k!xxxSetWindowData
должна отработать быстрее, чем произойдёт инициализация полей tagWND
в win32k!xxxCreateWindowEx
. Можно, конечно, поиграть с processor affinity
и приоритетами потоков. Однако, для Windows 7 и более ранних версий существует простой путь.Вариант для Windows 7
Несмотря на громадные размеры функции
win32k!xxxCreateWindowEx
, вся интересующая нас информация вполне укладывается в несколько hex-rays строк:![](https://habrastorage.org/files/f8a/eda/f13/f8aedaf133504a57b1b2a66f2d667e98.png)
Если во время регистрации класса окна у него была указана картинка для обычной иконки
hIcon
, но не была указана для маленькой иконки hIconSm
, то win32k!xxxCreateWindowEx
при первом создании окна такого класса копирует, а точнее – масштабирует, иконку для заполнения поля win32k!tagCLS->spicnSm
. Это действие выполняется функцией win32k!xxxCreateClassSmIcon
, которая перепоручает задание одному из описанных выше пользовательских так называемых kernel callbacks
:![](https://habrastorage.org/files/40c/50d/61c/40c50d61c8d949b1b81b8ee15a5b19e0.png)
Под 0x36-м номером в таблице идёт
user32!_ClientCopyImage
. Он и выполняет поставленную задачу.![](https://habrastorage.org/files/d6a/f84/702/d6af8470292b4e268aac42e94883329a.png)
После копирования иконки в
win32k!xxxCreateWindowEx
сразу заполняется WndProc
окна из WndProc
класса. Затем, как видно, если флаг WFSERVERSIDEPROC
возведён в классе, он возводится и для окна.Результаты
В результате получаем следующее. Для начала требуется зарегистрировать класс с указанием обычной иконки, но без указания маленькой:
![](https://habrastorage.org/files/db2/736/7e9/db27367e96df4c7394c124d36f4802a0.png)
![](https://habrastorage.org/files/907/d42/684/907d426841a340ae8e4d3e9af6d4ae8e.png)
Также нужен хук на
user32!_ClientCopyImage
:![](https://habrastorage.org/files/abb/4a1/dc1/abb4a1dc13b04b548d820010ab2ece28.png)
Он будет вызывать
SetWindowLongPtr
для только что созданного окна:![](https://habrastorage.org/files/87d/ccd/be5/87dccdbe5b164914859fc2efaf6adaa0.png)
После чего, в момент создания окна вызывается установленный ранее хук.
![](https://habrastorage.org/files/a6d/174/ffa/a6d174ffa47749e4baa02074a1f0b9ad.png)
Окно в этот момент уже занесено в таблицу, но ещё не инициализировано.
![](https://habrastorage.org/files/9d9/5f8/99e/9d95f899e97b481094c1b296e243265a.png)
![](https://habrastorage.org/files/89c/6fa/d02/89c6fad029214bd3a2f9a6b429d8b8e2.png)
Хук вызывает
SetWindowLongPtr
, который возводит флаг bServerSideWindowProc
в соответствующей окну структуре.![](https://habrastorage.org/files/226/b6a/958/226b6a958b9540589e09c7213f844916.png)
А по возвращении из коллбека,
win32k!xxxCreateWindowEx
перезаписывает lpfnWndProc
значением из поля класса.![](https://habrastorage.org/files/4af/0db/78e/4af0db78e66747e99eb543121cdfe8b3.png)
Таким образом, оконная функция, указанная при регистрации класса, будет выполняться в ядре:
![](https://habrastorage.org/files/f04/5b2/348/f045b2348ab242f8a753cc6ab7a716a6.png)
Очевидно, самое простое, для чего это можно использовать — это воровство системного токена с последующим запуском системного шелла.
![](https://habrastorage.org/files/e5c/e21/21f/e5ce2121f9b148e09994ac491d860fed.png)
![](https://habrastorage.org/files/574/b7c/e57/574b7ce572824941bce62eb51ed3e5e7.png)
P.S. Беглый осмотр Windows 8.1 показал, что в
win32k!xxxCreateWindowEx
установка tagWND->lpfnWndProc
и вызов win32k!xxxCreateClassSmIcon
идут в обратной последовательности по сравнению с более ранними версиями. Таким образом, хук на user32!_ClientCopyImage
уже не поможет.Есть вероятность, что «race condition» всё ещё существует и может быть с некоторой вероятностью проэксплуатирован выше описанным способом с двумя потоками. На этот счёт ничего более точного сейчас сказать не получится.