DEP и ASLR: форточки в окнах вашего компьютера

С выхода Windows 2000 прошло больше двадцати лет, и все десять лет, что официально Microsoft поддерживала ее расширенную версию, разработчики только тем и занимались, что выпускали очередные заплатки. Если вы до сих пор где-то ее используете, все обновления можно просмотреть через systeminfo в командной строке. Пройти мимо очередного сервис-пака было опасно – никто знал, во что могут вылиться незакрытые гештальты проблемы с безопасностью.

Чтобы хоть немного подняться в глазах простых сисадминов, Microsoft выводит на рынок новую ОС, которая… оказывается практически полным клоном Win2000 и, конечно, повторяет ее судьбу. И вместо того, чтобы чистосердечно раскаяться, разработчики в очередной раз переводят стрелки на грозных хакеров. Хотя на самом деле хакеры не мешали, а наоборот, помогали создавать что-то более стабильное и безопасное.

Тем временем в Microsoft продолжили искать обходные механизмы. Одним из них должен быть стать DEP (Data Execution Prevention). Он появился в XP, но так и не оправдал надежд. Следующая попытка была сделана уже в Win7, где реализовали функцию ASLR (Address Space Layout Randomization). На нее возлагали много надежд, но скоро стало понятно: ASLR так же уязвима, как и предыдущие решения.

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

Как работает DEP

Защиту от исполнения данных можно реализовать двумя способами: программным и аппаратным. Первый, он же software-enforced, подходит только для 32-битных версий Windows, у второго (hardware-enforced) область применения чуть шире: все та же 32-битная ось, но уже 64-битные чипы. Подобное разделение легко объяснить: DEP прямо соотносится с 63-м битом в записях виртуальных страниц PTE (Page Table Entry), которого нет в 32-битных ОС. Поэтому для последних доступен только один вариант – с эмуляцией и подключением ядра Ntkrnlpa.exe.

Допустим, основное (Ntoskrnl.exe) и альтернативное ядра находятся в папке \windows\system32. Если в boot.ini установить флаг /РАЕ (Physical Address Extension), то ось будет грузиться с альтернативного ядра, пока основное будет простаивать. РАЕ помогает сделать так, чтобы у обычного процессора на 32 бита было в распоряжении не 4 Гб адресного пространства, а 64 Гб.

Так выглядит трансляция адреса на разных ядрах 32-битной ОС​
Так выглядит трансляция адреса на разных ядрах 32-битной ОС​

На левом скрине у нас PDT (каталог виртуальных страниц), на который указывает регистр управления CR3 текущего процесса. В каталоге находятся ровно 1024 (или 10 бит) записей Entry, каждая – по 32 бита. На каждую запись приходится одна из 1024 таблиц PT, в которой, в свою очередь, тоже 1024 записи PTE. И, наконец, PTE указывают на одну из 1024 страниц виртуальной памяти по 4 Кб. Итого суммарно мы получаем 4 ГБ памяти (результат умножения 1024 на 1024 на 4096).

На правом скрине видно, что в ядре РАЕ с 32 до 52 бит выросла разрядность записей Entry, но их количество сократилось вдвое: было 1024, стало 512, то есть теперь у нас девять бит вместо десяти. Благодаря тому, что в записях теперь есть дополнительные разряды, можно создавать в два раза больше таблиц PT и каталогов PDT. У любой страницы по-прежнему 4 Кб, но количество страниц просто гигантское. Также возможны иные режимы, когда вместо 4-килобайтных страниц мы получаем 2- и даже 4-мегабайтные.

32-битные записи PTE заполнены информацией по максимуму, и свободных битов в них нет. Но если разрядность повысить до 64 бит (а мы помним, что полезных там только 52 бита), появляется 12 свободных бит. Их можно закодировать под что-то полезное.

Так выглядят записи PTE​
Так выглядят записи PTE​

Что мы здесь видим: в РАЕ-режиме длина записи увеличилась вдвое. Это позволило нам поместить в старший (63-й) бит защиту от исполнения NX. Если записать туда единицу, то мы защитим от исполнения страницу, у которой базовый адрес хранится в записи PTE таблицы РТ (и это та самая запись, значение бита которой мы меняем). Максимум, что будет доступно для этой страницы – запись или перезапись (write/rewrite). В ntoskrnl.exe ядро non-PAE таким битом не располагает, поэтому и DEP для него не доступен, как и для всего 32-битного семейства Windows XP без РАЕ.

DEP не активен для ядра Ntoskrnl.exe​
DEP не активен для ядра Ntoskrnl.exe​

На 64-битных осях все иначе. Так как им не нужно ядро РАЕ, то оно не входит в состав операционной системы, а сама она функционирует в режиме AWE (Address Windowing Extensions). На схеме ниже показано, как в этом случае происходит трансляция виртуального адреса.

<p>Механизм трансляции адреса в 64-битном режиме</p>​

Механизм трансляции адреса в 64-битном режиме

Поддержка DEP

Ядро выставляет DEP-защиту в момент запуска программы. Сама операционная система конфигурирует DEP (всего доступно четыре варианта), и это напрямую определяет, можно или нет обойти защиту программным способом.

Первая конфигурация: AlwaysOff = 0

Аппаратный DEP деактивирован для всех элементов ОС, а сама она работает на базовом ядре Ntoskrnl.exe без РАЕ.

Вторая конфигурация: AlwaysOn = 1

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

Третья конфигурация: OptInt = 2

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

Четвертая конфигурация: OptOut = 3

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

Не рекомендую относиться к DEP как к надежному средству защиты. Судя по конфигурациям, им легко управлять на программном уровне. А если вспомнить про VirtualProtect(Ex), с которой можно «поиграть» атрибутами страниц виртуальной памяти, механизм защиты выглядит еще более хрупким. Единственное, что достойно внимания, – это вторая конфигурация. С ней попытки сломить защиту закончатся фейлом. Минус в том, что работает это только на серверных системах, а на клиентских даже при такой конфигурации DEP может легко отключить вполне безобидное приложение вроде архиватора (легального, между прочим!).

Полезное для работы с DEP

Эти функции собраны в библиотеке kernel32.dll, и их можно использовать с правами администратора.

1. GetSystemDEPPolicy() – вызывает параметры системной политики DEP. У этой функции нет дополнительных аргументов, и все, на что она способна, – это вернуть порядковый номер выбранной DEP конфигурации. Сама конфигурация выбирается на этапе загрузки операционной системы, а для ее изменения придется обратиться к boot.ini. Чтобы активировать DEP на глобальном уровне, нужен бит (11) MSR-регистра 0xC0000080, который называется IA32_EFER.NXE.

2. GetProcessDEPPolicy() – делает запрос параметра DEP для выбранного процесса. Если операция успешная, функция возвращает TRUE, в противном случае – FALSE=0.

DEP и ASLR: форточки в окнах вашего компьютера

3. SetProcessDEPPolicy() – задает параметры DEP для выбранного процесса. Доступен всего один аргумент – флаг предыдущей функции Get(). Результат работы функции зависит от общесистемной политики DEP (два варианта: OptInt или OptOut). Работать с DEP можно только тогда, когда GetProcessDEPPolicy() периодически возвращает FALSE.

Ниже привожу код утилиты, которая будет вызывать перечисленные функции. Для вывода строк рекомендую использовать «таблицы переходов», чтобы избежать ненужных проверок в цикле CASE.

DEP и ASLR: форточки в окнах вашего компьютера
​Вид окна управления программным DEP
​Вид окна управления программным DEP

А здесь показываю, что возвращает Windows XP на ядре Ntoskrnl.exe, если отключить DEP (в Windows 7 используется другое ядро – Ntkrnlpa.exe). Системный статус – AlwaysOff=0, потому что DEP отключен на аппаратном уровне.

Ntosknrl.exe не подозревает о существовании DEP​
Ntosknrl.exe не подозревает о существовании DEP​

Мой вывод: с DEP все не так однозначно. Он, конечно, в какой-то мере выполняет свою функцию, но далеко не всегда. В Microsoft так и не исправили ошибки прошлого, зачем-то оставив VirtualProtectEx() и VirtualAlloc(), которые так «играют» с атрибутами страниц, что польза от DEP начинает стремиться к нулю. Будем считать это очередным неудачным экспериментом разработчиков, который все никак не завершится хэппи-эндом.

Как работает ASLR

ASLR в операционной системе Windows 7 рандомно меняет базу образа в памяти. System files в этой ОС по умолчанию запускаются с включенной ASLR, но у других приложений, запущенных пользователем, этот механизм может быть отключен. Чтобы исправить ситуацию и принудительно включить ASLR для наших программ, понадобится файл компиляции /DYNAMIC_BASE.

Базы образов по-разному вычисляются для образов ПО разного типа. У прикладных программ это происходит случайно при каждом запуске exe-файла, у системных – только один раз, на старте ОС. Это нужно для того, чтобы код системных DLL можно было использовать совместно, не исправляя указатели на функции API для всех процессов по отдельности.

Возвращаемся к ASLR. Он случайным образом меняет базу образа, базу стека и базу кучи Heap в блоках памяти процесса. Так как у нас 8-битный механизм выбора, то нам доступна любая из 256 баз образа. В случае со стеком и кучей рандом поменьше – всего лишь 5-битный. Соответственно, разрядность будет ниже и нам доступно всего 32 базовых адреса.

Если какому-то приложению нужно находиться в памяти под чутким контролем ASLR, об этом должен заранее позаботиться разработчик: при компиляции присвоить в РЕ-заголовке определенный перечень флагов. Первое 16-битное поле (Characteristics) расположено по смещению РЕ.16h. В каждом из его 16 бит зашифрованы определенные данные, а в целом от набора флагов напрямую зависят основные параметры исполняемого файла.

Вот несколько бит, которые могут вас заинтересовать:

  • Бит 0 – IMAGE_FILE_RELOCS_STRIPPED

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

  • Бит 1 – IMAGE_FILE_EXECUTABLE_IMAGE

Устанавливается в значение «1», если это действительно исполняемый файл, который можно запустить.

  • Бит 4 – IMAGE_FILE_AGGRESIVE_WS_TRIM

Операционная система сама уменьшит количество памяти для процесса, разбив его на несколько страниц. Такой бит есть у процессов-демонов (они редко проявляют активность).

  • Бит 5 – IMAGE_FILE_LARGE_ADDRESS_AWARE

Указывает, что программа поддерживает работу с памятью объемом от 2 Гб.

  • Бит 12 – IMAGE_FILE_SYSTEM

Указывает, что файл принадлежит к системным (например, драйверам).

  • Бит 13 – IMAGE_FILE_DLL

Указывает, что файл попадает в категорию DLL-библиотек.

Наибольший интерес представляет только нулевой бит. У DLL-ок он обычно сброшен, у исполняемых файлов – возведен. Второй случай легко объяснить: когда запускается процесс, вначале в памяти оказывается именно исполняемый файл, который оказывается в предпочтительной базе. А затем он постепенно подтягивает другие файлы приложения, причем начинает действовать принцип «кто первый встал, того и тапки база». Если какая-то DLL окажется неперемещаемой, базовые адреса EXE и DLL начнут конфликтовать.

Увидеть, в каком состоянии находятся флаги, можно с помощью небольшой утилиты РЕ Explorer, которая умеет редактировать их биты.

Взведенный бит запрещает перемещение файла​
Взведенный бит запрещает перемещение файла​

Бит (0) – далеко не все, что нужно ASLR. Обратите внимание на поле DLL-characteristics – здесь находятся ключи, которые можно задать компилятору на этапе сборки исходного exe-файла. Чтобы ASLR обратил внимание на исполняемый файл, компилятору нужно прямо указать на ключ /DYNAMIC_BASE, иначе переместить образ не получится даже при активном ASLR.

Бит-мап 16-битного поля РЕ.5Еh​
Бит-мап 16-битного поля РЕ.5Еh​
DEP и ASLR: форточки в окнах вашего компьютера

Если у нас есть привилегия Debug плюс права администратора, мы можем последовательно пройтись по всем системным файлам и сбросить те биты ASLR, которые нам не нужны (точнее, мешают). Чтобы обойти открытые файлы, чьи образы нельзя перезаписать, достаточно дескриптора открытых файлов функциями OpenProcess() с последующим DuplicateHandle(). Пара простых манипуляций, и мы получаем родительские права на любой объект.

Базу образа также можно отыскать динамически, как это делает вредоносное ПО поколения Next, а жесткую привязку через PE-заголовки сейчас мало кто практикует. Но если вы хотите потренироваться, предлагаю написать небольшую программу. Она пройдется по текущей директории, найдет там exe-шники и покажет, какие из них поддерживают ASLR. Для этого достаточно проверить флаг DLL_DYNAMIC_BASE в поле РЕ.5Еh:

DEP и ASLR: форточки в окнах вашего компьютера
​Возвращенные флаги из PE-заголовка
​Возвращенные флаги из PE-заголовка

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

Тем, кто планирует глубже вникнуть в работу защитных механизмов операционной системы, рекомендую 7-е издание книги М. Руссинович «Внутреннее устройство Windows». Захотите обсудить механизмы защиты с единомышленниками – приглашаю на форум Codeby, у нас очень много полезного! А прокачать знания помогут наши обучающие курсы по анонимности и безопасности в интернете (Paranoid I/II) и по тестированию веб-приложений на проникновение с нуля.

Начать дискуссию