Об этой уязвимости нулевого дня в 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» всё ещё существует и может быть с некоторой вероятностью проэксплуатирован выше описанным способом с двумя потоками. На этот счёт ничего более точного сейчас сказать не получится.

Комментарии (35)


  1. Dywar
    13.05.2015 22:55
    +3

    Странно что в подобных статьях нет бурного обсуждения :)
    И обнаружение такого бага не похоже на случайность.


    1. dewil
      13.05.2015 23:03
      -1

      обнаружение любого бага, больше похоже на лотерею, найдешь — не найдешь.


      1. TrueMaker
        14.05.2015 06:22
        +2

        Тут, похоже, была цель найти и средств не жалели.


        1. akirsanov
          14.05.2015 07:19
          +1

          Вероятнее всего купили у ресерчера


        1. dewil
          14.05.2015 10:01
          +1

          ну это и понятно.
          зато сколько средств можно на результате поднять :)


  1. dewil
    13.05.2015 22:55
    +1

    много кода :)
    но интересно


  1. megaweber
    14.05.2015 00:39
    -2

    Неплохо было бы создать независимое комьюнити, которое бы создавало код под новые известные эксплоиты и запускало бы там простой, недеструктивный код, основной целью которого был бы сбор статистики. Крупные ресурсы заливали бы туда 0.00001% своего трафика и таким образом бы создавалась адекватная картина критичности известных уязвимостей, чтобы разработчики больше обращали на это внимания.


    1. sledopit
      14.05.2015 15:27

      Вам не кажется, что даже единичный случай такого «залива трафика» со стороны любого крупного (и не очень) ресурса грозит большим скандалом и потерей аудитории?


      1. megaweber
        14.05.2015 15:42

        Если все под контролем, то не кажется. Тем более можно спрашивать согласия пользователя


        1. Temirkhan
          14.05.2015 21:51

          Представим:
          Ситуация 1) Господа, все в порядке, вы ботнеты, но исключительно в целях сбора статистики.
          Ситуация 2) АНБ следит за Вашей безопасностью, поэтому в случайных промежутках времени Ваша веб-камера будет делать снимки и отсылать нам для дальнейшего анализа.
          Ситуация 3) Ребята, а вот и тот квартал, который вы подписались убрать. В некоторых мусорных мешках вирус гриппа, но это полностью безопасно.

          Казалось бы, вроде ничего страшного, но при этом не покидает ощущение того, что тебя использовали)


          1. megaweber
            15.05.2015 03:20

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

            В моем примере я не предлагаю слепо использовать пользователя, а делать все с его согласия (тем более можно сообщить ему о проблеме с безопасностью в его системе), но массовость поможет привлечь крупные компании к решению проблемы. Если все будет под контролем открытой и общедоступной системы, то это принесет только пользу, на мой взгляд.


  1. exelens
    14.05.2015 07:17
    +4

    Непонятно причём тут «русские хакеры»


    1. 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


      1. exelens
        14.05.2015 07:37
        -1

        Спасибо, понятно. ИМХО бред.


        1. akirsanov
          14.05.2015 09:02

          Да, метод спорный, допускает погрешности или тупо саботаж мета данных, но ресерчеры опираются на принцип бритвы Оккама.


        1. DonkeyHot
          14.05.2015 14:13
          +3

          Если это бред, то какое ваше объяснение — что китайцы 10 лет подряд ставили русское время и локаль перед тем как билдить малварь?
          ИМХО вот это скорее бред.


          1. ptsecurity Автор
            18.05.2015 13:34

            В часовом поясе «мск/спб» находятся также Ирак, Саудовская Аравия и целая куча африканских стран.

            К слову сказать, в России больше всего часовых поясов, чем в любой другой стране. Поэтому с использованием привязки к местному времени можно свалить на Россию все прибалтийские и скандинавские вирусы («русские хакеры из Калининграда!»), все китайские трояны («русские хакеры из Иркутска!») и даже всех австралийских сумчатых червей («русские хакеры из Владивостока!»).

            Та же ерунда про «использование русского языка» в комментариях и названиях файлов. Это все равно что приписывать Испании все испаноязычные файлы, найденные в Интернете.

            В общем, нужны более вменяемые сигнатуры для подобных обвинений. Выводы, которые приводит FireEye, годятся для болотовни в форуме (как у нас с вами), но всерьез воспринимать это «исследование» не стоит. Пропаганды там гораздо больше, чем аналитики.


            1. DonkeyHot
              18.05.2015 15:56

              Кроме времени там еще и русская локаль выставлена в OS.
              Тут два варианта, или это были реально русские хакеры, которые не подумали о таких мелочах.

              Или это ставили специально, для прикрытия, но если кто-то настолько умный, кто следит за такими мелочами, то он бы менял локаль на разные страны, а не 10 лет пользовался бы одним паттерном поведения. Потому что паттерн позволяет анализировать деятельность группы.


              1. Dywar
                18.05.2015 19:29

                Вариантов нет.
                Еще Крис Касперски писал что можно/нужно качать Китайскую ОС и ставить их часовой пояс, прокси и т.д., и это в бородатых годах, а ничего не изменилось.

                Правильно выше говорят, пустая трата времени и букв.


                1. DonkeyHot
                  19.05.2015 00:24

                  Я во втором варианте написал почему это неправдоподобно.
                  И прокси итд тут не причем. Видимо они как вы подумали только про айпи, а не про сборку билдов.


      1. shambho
        14.05.2015 08:11

        По таблице на стр. 27 в pdf получается, что «русские хакеры» © работают с 7:00 до 15:00 по MSK.


        1. tym32167
          14.05.2015 08:25
          +3

          Это как раз с 9 до 17 по Челябинску :)


        1. DonkeyHot
          14.05.2015 14:00
          +1

          С 8:00 до 17:00-20:00, раньше MSK было UTC+4


      1. websurfer
        14.05.2015 12:33

        Будь я китайцем, тоже локалес поставил русские.


        1. DonkeyHot
          14.05.2015 14:07
          +1

          Вряд ли китайцы 7 лет постоянно ставили русскую локаль.


          1. petropavel
            16.05.2015 09:39

            если товарищу майору сказали «а вот твоя группа будет маскироваться под русских», то они еще и комментарии матерные оставлять будут, не только локаль ставить.


            1. DonkeyHot
              18.05.2015 08:59

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


              1. il--ya
                19.05.2015 16:07

                Ну а зачем непременно затруднять связывание? Наоборот, взращивают и поддерживают легенду про злобных русских хакеров из Питера, для правдоподобности.


                1. DonkeyHot
                  19.05.2015 17:30

                  Анализируя действия группы можно узнать ее методы, интересы, слабости, предугадать дальнейшие действия и в конечном счете успешно противостоять ей.
                  А из какой конкретно страны эта группа это не так уж и важно, хакерством все спецслубжы занимаются.


  1. akirsanov
    14.05.2015 09:09

    Вот кстати исходник с использованием CVE-2015-1701 для получения системного токена:
    github.com/hfiref0x/CVE-2015-1701


  1. eme
    14.05.2015 14:53

    Хакеры и санкции — сущности взаимоисключающие.
    Хакер — это религия, философия и идеология, а не хороший IT специалист.
    Называйте вещи своими именами =)


    1. progchip666
      14.05.2015 21:23
      +1

      После Сноудена всё уже совершенно перепуталось. Вроде как похищал информацию из-за великой идеи о свободе информации. А теперь работает на современную Россию, в которой сегодня со свободой точно не самым лучшим образом в мире дела обстоят.


  1. BjornValor
    14.05.2015 16:28

    Снова сладкая парочка: Flash + Windows…


  1. prishelec
    14.05.2015 18:48

    Как в анекдоте:
    Один программист пишет вирусы
    Второй антивирусы
    А третий ОС, под которым это все работает
    — ИМХО: я конечно склоняюсь к тому что этот баг обнаружили случайно, но череда совпадений как то высока


  1. sluge
    15.05.2015 12:08

    Интересно почему микрософт у себя в корпоратовном блоге не пишет такие статьи