Атака была реализована с помощью ранее неизвестных уязвимостей 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» всё ещё существует и может быть с некоторой вероятностью проэксплуатирован выше описанным способом с двумя потоками. На этот счёт ничего более точного сейчас сказать не получится.
Комментарии (35)
megaweber
14.05.2015 00:39-2Неплохо было бы создать независимое комьюнити, которое бы создавало код под новые известные эксплоиты и запускало бы там простой, недеструктивный код, основной целью которого был бы сбор статистики. Крупные ресурсы заливали бы туда 0.00001% своего трафика и таким образом бы создавалась адекватная картина критичности известных уязвимостей, чтобы разработчики больше обращали на это внимания.
sledopit
14.05.2015 15:27Вам не кажется, что даже единичный случай такого «залива трафика» со стороны любого крупного (и не очень) ресурса грозит большим скандалом и потерей аудитории?
megaweber
14.05.2015 15:42Если все под контролем, то не кажется. Тем более можно спрашивать согласия пользователя
Temirkhan
14.05.2015 21:51Представим:
Ситуация 1) Господа, все в порядке, вы ботнеты, но исключительно в целях сбора статистики.
Ситуация 2) АНБ следит за Вашей безопасностью, поэтому в случайных промежутках времени Ваша веб-камера будет делать снимки и отсылать нам для дальнейшего анализа.
Ситуация 3) Ребята, а вот и тот квартал, который вы подписались убрать. В некоторых мусорных мешках вирус гриппа, но это полностью безопасно.
Казалось бы, вроде ничего страшного, но при этом не покидает ощущение того, что тебя использовали)megaweber
15.05.2015 03:20Это конечно хороший взгляд с точки зрения критики, но давайте согласимся, что я все-таки не предлагал заражать людей гриппом, а предлагал контролируемый и известный способ обхода защиты с гарантированной безопасностью, так как этот код явно известен людям, которые как раз заражают «гриппом», то ничего нового тут нет и все направлено исключительно на то, чтобы привлечь внимание к проблеме.
В моем примере я не предлагаю слепо использовать пользователя, а делать все с его согласия (тем более можно сообщить ему о проблеме с безопасностью в его системе), но массовость поможет привлечь крупные компании к решению проблемы. Если все будет под контролем открытой и общедоступной системы, то это принесет только пользу, на мой взгляд.
exelens
14.05.2015 07:17+4Непонятно причём тут «русские хакеры»
akirsanov
14.05.2015 07:27+4Только коственно — накапливая семплы, вытаскивается много мета информации, такой как: время работы над документами, язык системы, часовой пояс и прочее.
На основании этого делаются предположения, и собирая все больше семплов от одной группы атакующих, накапливается определенная статистика.
В конце концов из предположений делается вывод — часовые пояса мск/спб, время работы с 9:00 до 18:00, скорее всего «русские хакеры».
www.fireeye.com/content/dam/legacy/resources/pdfs/apt28.pdf
www.recordedfuture.com/russian-malware-analysis
exelens
14.05.2015 07:37-1Спасибо, понятно. ИМХО бред.
akirsanov
14.05.2015 09:02Да, метод спорный, допускает погрешности или тупо саботаж мета данных, но ресерчеры опираются на принцип бритвы Оккама.
DonkeyHot
14.05.2015 14:13+3Если это бред, то какое ваше объяснение — что китайцы 10 лет подряд ставили русское время и локаль перед тем как билдить малварь?
ИМХО вот это скорее бред.ptsecurity Автор
18.05.2015 13:34В часовом поясе «мск/спб» находятся также Ирак, Саудовская Аравия и целая куча африканских стран.
К слову сказать, в России больше всего часовых поясов, чем в любой другой стране. Поэтому с использованием привязки к местному времени можно свалить на Россию все прибалтийские и скандинавские вирусы («русские хакеры из Калининграда!»), все китайские трояны («русские хакеры из Иркутска!») и даже всех австралийских сумчатых червей («русские хакеры из Владивостока!»).
Та же ерунда про «использование русского языка» в комментариях и названиях файлов. Это все равно что приписывать Испании все испаноязычные файлы, найденные в Интернете.
В общем, нужны более вменяемые сигнатуры для подобных обвинений. Выводы, которые приводит FireEye, годятся для болотовни в форуме (как у нас с вами), но всерьез воспринимать это «исследование» не стоит. Пропаганды там гораздо больше, чем аналитики.
DonkeyHot
18.05.2015 15:56Кроме времени там еще и русская локаль выставлена в OS.
Тут два варианта, или это были реально русские хакеры, которые не подумали о таких мелочах.
Или это ставили специально, для прикрытия, но если кто-то настолько умный, кто следит за такими мелочами, то он бы менял локаль на разные страны, а не 10 лет пользовался бы одним паттерном поведения. Потому что паттерн позволяет анализировать деятельность группы.Dywar
18.05.2015 19:29Вариантов нет.
Еще Крис Касперски писал что можно/нужно качать Китайскую ОС и ставить их часовой пояс, прокси и т.д., и это в бородатых годах, а ничего не изменилось.
Правильно выше говорят, пустая трата времени и букв.DonkeyHot
19.05.2015 00:24Я во втором варианте написал почему это неправдоподобно.
И прокси итд тут не причем. Видимо они как вы подумали только про айпи, а не про сборку билдов.
websurfer
14.05.2015 12:33Будь я китайцем, тоже локалес поставил русские.
DonkeyHot
14.05.2015 14:07+1Вряд ли китайцы 7 лет постоянно ставили русскую локаль.
petropavel
16.05.2015 09:39если товарищу майору сказали «а вот твоя группа будет маскироваться под русских», то они еще и комментарии матерные оставлять будут, не только локаль ставить.
DonkeyHot
18.05.2015 08:59В таком прикрытии мало смысла. Ведь если паттерн поведения не меняется (прикрытие одно и то же), то у оппонента все равно есть возможность собирать и связывать данные на протяжении многих лет и связывать обнаруженные эпизоды с деятельностью вашей конкретной группы.
То есть если бы уделяли внимание таким мелочам как локаль и сознательно выставляли чужую, то прикрытие периодичесски бы менялось под разные страны и часовые пояса, чтобы затруднить связывание разных эпизодов и анализ вашей общей деятельности.il--ya
19.05.2015 16:07Ну а зачем непременно затруднять связывание? Наоборот, взращивают и поддерживают легенду про злобных русских хакеров из Питера, для правдоподобности.
DonkeyHot
19.05.2015 17:30Анализируя действия группы можно узнать ее методы, интересы, слабости, предугадать дальнейшие действия и в конечном счете успешно противостоять ей.
А из какой конкретно страны эта группа это не так уж и важно, хакерством все спецслубжы занимаются.
akirsanov
14.05.2015 09:09Вот кстати исходник с использованием CVE-2015-1701 для получения системного токена:
github.com/hfiref0x/CVE-2015-1701
eme
14.05.2015 14:53Хакеры и санкции — сущности взаимоисключающие.
Хакер — это религия, философия и идеология, а не хороший IT специалист.
Называйте вещи своими именами =)progchip666
14.05.2015 21:23+1После Сноудена всё уже совершенно перепуталось. Вроде как похищал информацию из-за великой идеи о свободе информации. А теперь работает на современную Россию, в которой сегодня со свободой точно не самым лучшим образом в мире дела обстоят.
prishelec
14.05.2015 18:48Как в анекдоте:
Один программист пишет вирусы
Второй антивирусы
А третий ОС, под которым это все работает
— ИМХО: я конечно склоняюсь к тому что этот баг обнаружили случайно, но череда совпадений как то высока
Dywar
Странно что в подобных статьях нет бурного обсуждения :)
И обнаружение такого бага не похоже на случайность.
dewil
обнаружение любого бага, больше похоже на лотерею, найдешь — не найдешь.
TrueMaker
Тут, похоже, была цель найти и средств не жалели.
akirsanov
Вероятнее всего купили у ресерчера
dewil
ну это и понятно.
зато сколько средств можно на результате поднять :)