Компания
315,10
рейтинг
26 сентября 2012 в 18:30

Разработка → Windows 8 – да будет SMEP!


С приходом нового поколения процессоров Intel на базе архитектуры Ivy Bridge было представлено новое аппаратное средство безопасности. Оно называется Intel SMEP. Как и бит NX, предотвращающий исполнение кода на странице памяти, оно добавляет головной боли при эксплуатации уязвимостей режима ядра.

В свою очередь Microsoft реализовала поддержку SMEP в Windows 8, таким образом сделав эту ОС ещё безопасней. Однако, первая реализация «в лоб» поддержки SMEP получилась с небольшим изъяном, благодаря которому у атакующего всё ещё есть возможность относительно безболезненной для него эксплуатации уязвимостей.

Что такое SMEP?


SMEP расшифровывается как “Supervisor Mode Execution Prevention” — предотвращение исполнения кода в режиме супервизора. Режим супервизора – это привилегированный режим работы процессора, в котором исполняется ядро ОС Windows 8. В понятиях ОС этот режим называется также режимом ядра. Противоположным ему является режим пользователя – в этом режиме исполняются пользовательские приложения.

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

Привилегированный режим в свою очередь не имеет никаких ограничений, если бы не SMEP. Если он включен, любая попытка выполнить код, находящийся в памяти пользовательского приложения, приведет к ошибке страницы (page fault). В частности, в обработчике ошибок страниц на Windows 8 данная ситуация вызовет bugcheck.
Проще говоря, если какой-нибудь драйвер или системный модуль ядра попробует выполнить код, расположенный в памяти пользовательского приложения, закончится всё это синим экраном смерти с грустным смайликом.

А смысл?


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

«Но в режиме ядра теперь нельзя исполнять код со страниц памяти пользователя! А где теперь-то хранить шелл-код? А в чём смысл атаки без собственного шелл-кода? А может, и не надо тогда атаковать?»

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



Первый блин комом


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

Самое очевидное решение – использовать объекты Windows. Те самые, с которыми приложения работают через описатели (handle). Это всевозможные таймеры, события, мьютексы, порты завершения ввода-вывода, графические объекты и т.д. Тело такого объекта располагается в памяти ядра, а содержимое может быть изменено из пользовательского режима, и мы можем узнать, по какому адресу в пространстве ядра они расположены!

К примеру, когда мы открываем файл функцией CreateFile(), мы получаем описатель открытого файла. Если приложению нужно считать пару байт этого файла в буфер, то мы вызываем функцию ReadFile(), которая при помощи системного вызова передает управление в ядро ОС, где по таблице описателей находит нужный объект файла. В данном случае объект имеет тип _FILE_OBJECT, и изменяемым полем в нём является имя файла. Т.е. теоретически можно создать файл с именем "\0xBAADC0DE", которое будет содержать наш шелл-код. Затем мы эксплуатируем некую уязвимость режима ядра и передаем управление на наш шелл-код.

0: kd> dt nt!_FILE_OBJECT
+0x000 Type: Int2B
+0x002 Size: Int2B

+0x050 Flags: Uint4B
+0x058 FileName: _UNICODE_STRING
+0x068 CurrentByteOffset: _LARGE_INTEGER

+0x0c0 IrpList: _LIST_ENTRY
+0x0d0 FileObjectExtension: Ptr64 Void

Успех? Пока ещё не совсем. Дело в том, что SMEP на Windows 8 дополняется другим механизмом защиты. Несмотря на то, что код расположен на странице режима ядра, эта страница имеет пометку (бит NX) о том, что она неисполняемая, т.е. на этой странице не может исполняться код! Получается, что объекты Windows защищены от исполнения, следовательно, тоже не подходят для хранения шелл-кода. Это утверждение верно для х64 версии Windows 8. Однако в x86 версии тела графических объектов располагаются в исполняемой памяти!

Самым подходящим объектом для доставки шелл-кода оказалась палитра. Создается она при помощи функции CreatePalette() и структуры LOGPALETTE, содержимое которой и наполняется шелл-кодом. Ещё бы, как валидировать цвета в палитре? Ведь в нашей палитре будут именно те цвета, которые мы захотим! А мы захотим много байт NOP (0x90), и много байт шелл-кода. Вот такая получается «злая» палитра…



Итого имеем схему обхода SMEP на Windows 8 x86:
1. Создаем «злую» структуру LOGPALETTE, наполняя её шелл-кодом.
2. Создаем палитру через CreatePalette().
3. Находим адрес объекта палитры в ядре через разделяемую таблицу GDI.
4. Передаем управление по некоторому смещению от объекта палитры.
5. ???????
6. PROFIT!!!

А что делать с х64?


Хотите обход SMEP на Windows 8 x64? Их есть у меня! Возможно способ не такой интересный, но зато рабочий. SMEP на x64 обходится при помощи возвратно-ориентированного программирования (ROP). Если кратко, то ROP использует уже присутствующие в памяти участки кода из других модулей. Таким образом, пробрасывать свой шелл-код в ядро не нужно.

Конечно, возможности атакующего при составлении полезной нагрузки в данном случае сильно ограничены. Но всё, что нужно атакующему – отключить SMEP, а для этого в модуле «ntoskrnl» есть «подарочки» в виде функций HvlEndSystemInterrupt() и KiConfigureDynamicProcessor(). Последние байты этих функций позволяют отключить SMEP на заданном процессоре.

HvlEndSystemInterrupt():
…
pop     rax
pop     rcx
retn


KiConfigureDynamicProcessor():
…
mov     cr4, rax
add     rsp, 28h
retn


// ROP chain to refresh cr4 value

//                  vTrash			vROPChain
DWORD_PTR dwRopStack[7      +       10] = {0};

// HvlEndSystemInterrupt gadget
dwRopStack[7 + 0] = dwKernelBase + HvlGadgetOffset;
// New CR4 value
dwRopStack[7 + 1] = 0x00000000000506F8;
// KiConfigureDynamicProcessor
dwRopStack[7 + 3] = dwKernelBase + Cr4GadgetOffset;
// Out address (shellcode)
dwRopStack[7 + 9] = (DWORD_PTR)pTestBuf;




Долой ограничения


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

Заключение


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

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

P.S. SMEP отключается 13-ю байтами кода. За подробностями прошу по ссылкам ниже.
Как победить SMEP на Windows 8 x86
Как победить SMEP на Windows 8 x64

Авторы: Артём Шишкин и Илья Смит
Автор: @HonoraryBoT
Positive Technologies
рейтинг 315,10

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

  • 0
    с небольшим изъяном
    — скромности не занимать, Тёма?

    А что, неужели роп для x64 не универсален (если знать как достать dwKernelBase)?
    Как искал гаджеты: «mov cr4, *» по всему ядру?
    • +4
      dwKernelBase узнать можно через ZwQuerySystemInformation().
      Гаджеты именно так, ага. Есть ещё две занимательные функции — KiSaveProcessorControlState() и KiRestoreProcessorControlState(). «Чувак, это ропчик!»
      • 0
        Да, ROP не всегда юзабелен. Да и в любой уязвимости есть свои тонкости эксплуатации, я думаю…
  • +3
    Я вот что не понимаю: такая вещь, как сегменты, забыта окончательно? Достаточно было задать сегмент кода ядра начинающимся не с нуля, а с правильной точки, и выполнить код из пространства пользователя будет невозможно. А вместо этого придумана какая-то новая технология, которую к тому же можно отключить.

    Да, это разрушит единство адресного пространства кода и данных. Но неужели в ядре Windows до сих пор применяется самомодифицирующийся код?
    • +3
      Забыта, давно и надолго. Неэффективно, неудобно. В х64 вообще осталась пара значимых бит в дескрипторах сегментов. Защита на уровне страниц хорошо работает, а обход — недочет реализации.
  • +2
    Мельком взглянул на заголовок — показалось, что написано «да будет смерть» ))
  • +1
    Ох, необычные у вас иллюстрации…
    • +2
      Зато вы зашли и прокрутили статью до конца. Текст-то обычный.
  • 0
    Ах какой умный аффтор =). У меня только один вопрос ). Я таких суперсхем по 10 штук на дню могу рисовать с оговорками типа: это обязательно заработает… если только солнце светит не очень сильно; или… в майкрософт работают не полные дауны. Вами лично это супергениальная идея проверялась опытным путем?
    • +5
      Думаете люди в пэйперах байки травят? Конечно проверялась. А в мс даунов в кернел тим не берут — есть инсайдерская информация.
  • 0
    > закончится всё это синим экраном смерти с грустным смайликом.

    что характерно.

    Кстати, в Linux поддержка SMEP есть с весны прошлого года.
    • +15
      Да, хорошо, что наконец вышел процессор, который его поддерживает!
      • +2
        да, у нас линуксоидов и с поддержкой USB 3.0 также было. появилась за полгода до выхода технологии на рынок, за год до появления в новом Windows и за полтора до патча на XP.
        • +2
          ага… а заработало оно номально на линухе еще через год после патча под ХР — если вообще кто проверял…
          Понмиться мышкино колесико в линуксе лет через 10 после винды нормально стало подерживаться.
          Нормально — это чтобы руками не лазить в конфиги иксов.
          • 0
            Ядро Linux разрабатывают Intel, AMD и прочие монстры, а также те, кто спецификации на USB 3.0 собственно пишут. Есть Linux с платной поддержкой, в т.ч. на матплатах с USB 3.0. Так по какой причине плохо работать? Не путайте Linux для энтузиастов и Linux для предприятий.

            > Нормально — это чтобы руками не лазить в конфиги иксов.
            Спасибо, за это последнее уточнение, а то я не сразу понял, что не так с колесом.
      • 0
        И что-то не видно поддержки SMAP для Windows 8? SMAP — это фича безопасности, которая появится в процессорах Intel Haswell в следующем году. (Появится на рынке, а не в лабораториях разработчиков ядра Linux.)

        А в Linux вот как раз добавили поддержку:

        www.phoronix.com/scan.php?page=news_item&px=MTE5NzI
  • 0
    Хоть бы ссылку дали на нормального автора, первооткрыватели нашлись видетели ). j00ru.vexillium.org/?p=783 к примеру и прочие его посты. Ну и какбы немного сторонней критики kaskadsecurity.blogspot.com/2012/09/intel-windows-8.html. Такое отношение типа на хабре всёравно дураки сидят, типа схавают чо, конечно «достойно» уважения, угу…
    • 0
      Ну, во-первых, ссылки есть. Не знаю, как вы их не заметили. Видать, тоже не читали пэйперы — там все подробно описано.
      Во-вторых, метод j00ru нерабочий, поскольку reservation objects поNXены на 8ке. На хабре один стиль изложения, в пейперах — другой, очевидно же. А новости как бы не я писал, и за них не в ответе…

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.

Самое читаемое Разработка