Pull to refresh

Windows 8 – да будет SMEP!

Reading time 5 min
Views 52K

С приходом нового поколения процессоров 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

Авторы: Артём Шишкин и Илья Смит
Tags:
Hubs:
+103
Comments 18
Comments Comments 18

Articles

Information

Website
www.ptsecurity.com
Registered
Founded
2002
Employees
1,001–5,000 employees
Location
Россия