DEP и ASLR – игра без правил. Часть 1
Life-Hack [Жизнь-Взлом]/ХакингСо времени выхода в свет Win2000 прошло уже 20 лет. Её разработчики, с чувством исполненного долга предвкушали себе счастливую старость, но грянул гром – система оказалась дырявой как сито, и на данный момент счёт её обновлениям потеряла наверное уже и сама Microsoft. Ознакомиться с установленными на вашем узле заплатками можно в командной строке, запросив systeminfo. Каждый сервис-пак SP несёт в себе кучу таких обновлений, игнорировать которым (с точки зрения безопасности) глупо.
На скорую руку заштопав старые щели, Билли сбрасывает на рынок эту-же систему под новым именем, которую ждёт та-же участь и она бумерангом прилетает обратно – круг замыкается. А виной тому хакеры, огромной ступнёй годзиллы втирающие в грязь мастдай только потому, что за свои честно/заработанные требуют товар соответствующего качества – мол если предлагаешь обществу весчь, она должна быть без изъянов. По большому счёту, только хакеры и двигают прогресс компьютерной безопасности, заставляя корпорации строить более мощные обороны.
Большие надежды Microsoft возлагала на появившейся в XP механизм DEP – Data Execute Prevension (защита от исполнения данных), который как и следовало ожидать, с треском провалился. Тогда на Win7 была предпринята попытка рандомной смены "базы загрузки образа в память" ASLR – Advanced Space Layout Randomization и вроде дела поправились. Но позже выяснилось, что и ASLR пробивается с любого расстояния.
В данной статье предлагаю пощупать технологии DEP и ASLR руками – что они из себя представляют, какие имеют слабые и сильные стороны, можно-ли их обойти на программном уровне и т.п. Попытка пройти сквозь эту стену не зная основной идеи ни к чему хорошему не приведёт, поэтому как обычно – начнём с теории.
"Data Execute Protect" и виртуальная память
Защита от исполнения данных DEP, может быть как аппаратной, так и программной. Программная модель (software-enforced) используется только на 32-битных системах Win, в то время как аппаратно защищать данные от исполнения (hardware-enforced) могут исключительно 64-битные процессоры, с операционной системой такой-же разрядности. Связано это с тем, что фактически DEP привязан к биту 63 в записях виртуальных страниц PTE – Page Table Entry, а в 32-битных системах этого бита попросту нет. Поэтому на х32 его приходится эмулировать программным способом, подключая для этих дел отдельное ядро Ntkrnlpa.exe.
Когда в файле boot.ini установлен флаг /РАЕ, система загружается с указанного альтернативного ядра, а дефолтное Ntoskrnl.exe отправляется на скамейку запасных (оба ядра лежат в папке \windows\system32). РАЕ означает "Physical Address Extension", или расширение физического адреса. В этом режиме, 32-битному процессору становится доступным уже не 4 Gb адресного пространства, а все 64. На рисунке показаны фундаментальные признаки трансляции адреса на различных ядрах 32-битной оси (фрагменты позаимствованы из мануалов AMD том.2):

Значит имеем "каталог виртуальных страниц" PDT, на который указывает регистр управления CR3 текущего процесса (см.левый скрин PDE). В этом каталоге хранятся 10-бит=1024 записей Entry размером dword (32-бита). Соответственно каждая такая запись PDE указывает на одну из 1024 таблиц PT, в которой так-же 1024 записи PTE (10-бит). И наконец записи PTE – PageTableEntry – указывают на одну из 1024 страниц виртуальной памяти размером 4Кб (12-бит), что в сумме даёт максимум 1024*1024*4096=4Gb памяти. Младшие 12-бит адреса выбирает уже смещение внутри данной страницы, двигая окно вверх или вниз.
Теперь посмотрим на правый рисунок..
Основное отличие РАЕ-ядра в том, что увеличилась разрядность всех записей Entry с 32 до 52-бит, зато уменьшилось общее их кол-во с 1024 до 512 (было 10-бит, стало 9). Дополнительные разряды в записях позволяют создавать вдвое больше каталогов PDT и таблиц PT, в результате чего на выходе получаем гигантское число страниц виртуальной памяти, и каждая страница по 4Кб. Есть ещё и другие режимы работы транслятора, в которых страницы имеют размер не 4Кб, а 2 или 4М-байт. Однако сути это не меняет, поэтому рассматривать их не будем – кому интересно, курите маны разработчиков от Intel (том.3) или AMD (том.2).
Когда записи PTE 32-битные, в них нет лишнего места и они забиты инфой под-завязку. Но при увеличении разрядности до 64-бит (52 из которых полезные), остаётся много свободных, и в них можно закодировать что-то ещё. Формат записей PTE обоих ядер и зарытый в них глубокий смысл, представлен на рисунке ниже:

Из приведённого рисунка видно, что в режиме РАЕ запись стала в два раза длиннее, что позволило в самом старшем бите (63) спрятать бит защиты от исполнения NX. Другими словами, запись PTE в таблице РТ хранит базовый адрес одной из страниц виртуальной памяти, и если в этой записи бит (63) установлен в единицу, то описываемая данной записью страница будет защищена от исполнения – в лучшем случае она будет доступна только на r/w. Здесь так-же видно, что non-PAE ядро ntoskrnl.exe не имеет этого бита, поэтому DEP на этом ядре не может существовать в принципе. Это относится к 32-битной линейке Win-XP без РАЕ.

В отличии от систем х32, на 64-битных системах всё проще и РАЕ-ядро им ни к чему – его просто нет в составе ОС, и система работает в т.н. режиме AWE – Address Windowing Extensions (расширение адресного окна). Регистры и все структуры этих процессоров изначально 64-битные и трансляция виртуального адреса (которым мы оперируем в своих программах) происходит по следующей схеме:

Как видим, хоть адрес и 64-битный, но используются только 48-бит из них. Такая разрядность позволяет адресовать память размером 2^48=256 терабайт, чего с избытком хватит для любой программы. Кстати по аналогии с виртуальной памятью х64, адресация накопителей HDD/SSD тоже 48-битная, но это уже из другой кухни, т.к. шины и контроллёры у них разные. Это так.. чисто к сведению..
Системная поддержка DEP
Теперь посмотрим, что нам предлагает система..
Если коротко, то защита DEP выставляется ядром на этапе запуска нашего приложения. ОС может сконфигурировать защиту одним из четырёх способов, от которых зависит, сможем-ли мы обойти её на программном уровне, или нет.
- AlwaysOff = 0
Аппаратный DEP отключён для всех частей системы, и мы можем отправить свой шелл хоть в стек, хоть в кучу, хоть к чёрту на кулички. То-есть ОС работает на стандартном ядре Ntoskrnl.exe без РАЕ.
2. AlwaysOn = 1
Аппаратный DEP включён для всех, и его (!)нельзя отключить для избранных приложений. Все процессы выполняются только с включенным DEP. Любые исправления игнорируются, что предвещает нам полные кранты.
3. OptInt = 2
Аппаратный DEP автоматически включается только для компонентов ОС. Няшно.. и это дефолт для клиентских версий Win. Мы можем явно включить/отключить его программным способом для выбранных приложений, или текущего своего процесса.
4. OptOut = 3
DEP автоматически включается для всех процессов, включая пользовательские – это дефолт для серверных версий Win. Как и в предыдущей конфигурации, DEP можно явно отключить для любых приложений. Исправлены ошибки совместимости систем.
Судя по этому списку, воспринимать DEP как крутую защиту нельзя. Думаю молчаливая договорённость уже созрела в наших головах, а если нет, то вот некоторые нюансы.. Мало того, что DEP'ом можно программно оперировать, так ещё в запазухе у нас имеется функция всех времён и народов VirtualProtect(Ex), которая с лёгкостью переназначает любые атрибуты страниц виртуальной памяти, внося изменения в их записи PTE.
Здесь настораживает только конфиг под номером 2 и если честно, то его приходится воспринимать как суровую действительность. Если в ответ на запрос мы получим значение "AlwaysOn=1", значит любые попытки обойти защиту будут заранее обречены на провал.
Но этот выхлоп скорее исключение, чем правило и может обламать нас только на серверных системах, которые мы сейчас не берём в счёт. Дело в том, что во всех клиентских версиях, DEP отключают на время и легальные программы типа распаковщики, архиваторы, всякого рода визарды и близкие им по смыслу тулзы. Поэтому мастдай включает эту опцию редко, хотя вполне может создать и список исключений. Так-что процент попаданий можно определить только подбросив монетку вверх, а дальше – на чьё ухо сядет муха.
Основные пользовательские функции для работы с DEP хранятся в библиотеке kernel32.dll и могут использоваться на прикладном уровне с админскими правами – вот их список:
- GetSystemDEPPolicy() - запрашивает параметр системной политики DEP.
Эта функция не имеет аргументов и возвращает в регистр EAX одно из четырёх указанных выше значений 0,1,2,3. Общесистемная политика настраивается во время загрузки ОС - изменить её можно только пропатчив
boot.ini (смотри хелп в конфигураторе BCD по маске "NX"). На глобальном уровне, DEP активируется битом (11) MSR-регистра 0xC0000080 под названием IA32_EFER.NXE (no_execute_enable, см.маны Интела том.4).
2. GetProcessDEPPolicy() - запрашивает параметр DEP для указанного процесса. Эта функция BOOL и возвращает TRUE при успешной операции, иначе FALSE=0.
C-подобный:
GetProcessDEPPolicy()
hProcess ;// дескриптор запрашиваемого процесса (нуль для своего),
lpFlags ;// указатель на переменную для флагов,
lpPermanent ;// указатель на переменную для возможных операций.
;// если Permanent = TRUE, то политику изменить нельзя!
;// значения флагов
;//------------------------
;// 0 = DEP отключён для указанного процесса,
;// 1 = DEP включён для указанного процесса,
;// 2 = Эмуляция DEP-ALT отключёна для указанного процесса.
3. SetProcessDEPPolicy() - устанавливает параметры DEP для указанного процесса. Функция имеет всего один аргумент в виде одного из трёх флагов, предыдущей функции Get(). На положительный результат влияет в первую очередь общесистемная политика DEP, которая должна быть "OptIn" или "OptOut". Во-вторых функция проверки GetProcessDEPPolicy() должна перманентом возвратить FALSE, что будет означать возможность оперировать с DEP.
Напишем небольшую утилиту, которая будет вызывать функции из этого списка.
Значит сначала берём системный статус, потом статус для своего процесса и выводим всю информацию на экран. Строки выводит будем используя "таблицы переходов", чтобы исключить допотопные проверки значения в цикле CASE:
C-подобный:
format pe console
include 'win32ax.inc'
entry start
;//------
.data
capt db 13,10,' DEP info v0.1'
db 13,10,' ~~~~~~~~~~~~~~~~',0
sysDep db 13,10,' System status: %x = %s ',0
softDep db 13,10,' MyProc DEP '
db 13,10,' Status...: %x = %s ',0
perm db 13,10,' Permanent: %x = %s ',0
table1 dd dOff,dOn,optIn,optOut ;// таблица указателей на строки
dOff db 'AlwaysOff, Hard disable',0
dOn db 'AlwaysON, Hard enable',0
optIn db 'OptIn, Soft enable system only',0
optOut db 'OptOut, Soft enable for all',0
table2 dd sOff,sOn,sEmu
sOff db 'Disable',0
sOn db 'Enable',0
sEmu db 'EMU Disable',0
table3 dd pOff,pOn
pOff db 'FALSE, changeable',0
pOn db 'TRUE, not changeable',0
flags dd 0
lpPerm dd 0
frmt db '%s',0
;//------
.code
start: cinvoke printf,capt
;// Запрашиваем системную политику
;// -------------------------------
invoke GetSystemDEPPolicy ;// возвращает в EAX 0,1,2,3
mov esi,table1 ;// ESI = укащатель на таблицу
push sysDep ;// спецификатор для printf как аргумент для процедуры ниже
call PrintResult ;// зовём самопальную процедуру вывода на экран!
;// Запрашиваем политику своего процесса
;// -------------------------------------
invoke GetProcessDEPPolicy,0,flags,lpPerm ;// если EAX =0, значит ошибка.
mov esi,table2 ;// адрес таблицы переходов
mov eax,[flags] ;// EAX = флаги текущего процесса
push softDep ;// аргумент для fn.ниже
call PrintResult ;//
;// Показываем, можем или нет мы изменять политику
;// -----------------------------------------------
mov esi,table3 ;//
mov eax,[lpPerm] ;// EAX = перманент от fn. выше
push perm ;//
call PrintResult ;//
@exit: cinvoke scanf,frmt,capt ;// ждём нажатие клавиши..
cinvoke exit,0 ;// на выход!
;// Пользовательская процедура вывода на экран
;// -------------------------------------------
proc PrintResult messageId ;// имеет 2-аргумента (push до call, и eax)
mov ebx,eax ;// EAX/EBX = значение
shl ebx,2 ;// умножить число на 4 (смещение текста от начала)
add esi,ebx ;// ESI = указатель на строку из таблицы переходов
cinvoke printf,[messageId],eax,dword[esi] ;// выводим значение и соответствующую строку!
ret ;// выход из процедуры по адресу-возврата CALL в стеке.
Endp
;//=== Секция импорта =======================
data import
library msvcrt,'msvcrt.dll', kernel,'kernel32.dll'
import msvcrt, printf,'printf',scanf,'scanf',exit,'exit'
import kernel, GetSystemDEPPolicy,'GetSystemDEPPolicy',\
GetProcessDEPPolicy,'GetProcessDEPPolicy'
end data

А вот что возвращает этот код на моей XP с отключённым DEP и на стандартном ядре Ntoskrnl.exe, в то время-как Win-7 работает на ядре Ntkrnlpa.exe. Здесь видно, что DEP отключён на аппаратном уровне и статус возвращает "AlwaysOff=0".

Таким образом, DEP не так страшен, как его малюют. На дне его кармана имеется тунель в прошлое, и Microsoft никак не может его прикрыть. Только вдумайтесь – организовать защиту от исполнения DEP на уровне виртуальных страниц, и в это-же время оставить функцию VirtualProtectEx(), которая сводит на нет эту защиту и может снимать атрибуты не только со-своих, но и с чужих страниц. Кроме того есть и VirtualAlloc(), которая выделяет страницы с любыми атрибутами.. Какого цвета у разработчиков кровь и что они там курят – хз, нужен эксперимент.
Продолжение следует...