Со времени выхода в свет 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-бит), что в сумме даёт максимум
Теперь посмотрим на правый рисунок..
Основное отличие РАЕ-ядра в том, что увеличилась разрядность всех записей 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 выставляется ядром на этапе запуска нашего приложения. ОС может сконфигурировать защиту одним из четырёх способов, от которых зависит, сможем-ли мы обойти её на программном уровне, или нет.
Здесь настораживает только конфиг под номером 2 и если честно, то его приходится воспринимать как суровую действительность. Если в ответ на запрос мы получим значение "AlwaysOn=1", значит любые попытки обойти защиту будут заранее обречены на провал.
Но этот выхлоп скорее исключение, чем правило и может обламать нас только на серверных системах, которые мы сейчас не берём в счёт. Дело в том, что во всех клиентских версиях, DEP отключают на время и легальные программы типа распаковщики, архиваторы, всякого рода визарды и близкие им по смыслу тулзы. Поэтому мастдай включает эту опцию редко, хотя вполне может создать и список исключений. Так-что процент попаданий можно определить только подбросив монетку вверх, а дальше – на чьё ухо сядет муха.
Основные пользовательские функции для работы с DEP хранятся в библиотеке kernel32.dll и могут использоваться на прикладном уровне с админскими правами – вот их список:
1. 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.
3. SetProcessDEPPolicy() - устанавливает параметры DEP для указанного процесса.
Функция имеет всего один аргумент в виде одного из трёх флагов, предыдущей функции Get(). На положительный результат влияет в первую очередь общесистемная политика DEP, которая должна быть "OptIn" или "OptOut". Во-вторых функция проверки GetProcessDEPPolicy() должна перманентом возвратить FALSE, что будет означать возможность оперировать с DEP.
Напишем небольшую утилиту, которая будет вызывать функции из этого списка.
Значит сначала берём системный статус, потом статус для своего процесса и выводим всю информацию на экран. Строки выводит будем используя "таблицы переходов", чтобы исключить допотопные проверки значения в цикле CASE:
А вот что возвращает этот код на моей XP с отключённым DEP и на стандартном ядре Ntoskrnl.exe, в то время-как Win-7 работает на ядре Ntkrnlpa.exe. Здесь видно, что DEP отключён на аппаратном уровне и статус возвращает "AlwaysOff=0".
Таким образом, DEP не так страшен, как его малюют. На дне его кармана имеется тунель в прошлое, и Microsoft никак не может его прикрыть. Только вдумайтесь – организовать защиту от исполнения DEP на уровне виртуальных страниц, и в это-же время оставить функцию VirtualProtectEx(), которая сводит на нет эту защиту и может снимать атрибуты не только со-своих, но и с чужих страниц. Кроме того есть и VirtualAlloc(), которая выделяет страницы с любыми атрибутами.. Какого цвета у разработчиков кровь и что они там курят – хз, нужен эксперимент.
ASLR – принцип работы механизма
Рассмотрим очередную "бомбу" под названием "Advanced Space Layout Randomization" ASLR, которая начиная с Win-7 призвана рандомно менять базу образа в памяти. Отметим, что все системные файлы Win запускаются с включённым механизмом ASLR, но у сторонних приложений он может быть отключён. Чтобы наши программы влились в эту струю, нужно указать это явно, посредством флага компиляции "/DYNAMIC_BASE".
Для образов прикладных программ, новая рандомная база вычисляется при каждом запуске исполняемого файла. В отличие от юзерских программ, базы системных образов вычисляется только один раз, при начальной загрузке операционной системы и используется во всей системе вплоть до её перезагрузки. Если-бы системные DLL заново отображались по разным адресам внутри различных процессов, их код нельзя было-бы использовать совместно. Загрузчику пришлось-бы корректировать указатели на функции API для каждого процесса в отдельности, превращая общие функции в закрытые данные процесса.
Механизм ASLR рандомно меняет базы трёх блоков памяти процесса – это: база образа, база стека, и база кучи Heap. Рандом образа - 8-битный, что позволяет выбрать одну из 256 возможных баз. А вот рандом стека и кучи уже 5-битный – такая разрядность ограничивает выбор до 32-х базовых адресов.
Все-кто хочет под управлением ASLR кочевать в памяти, должны иметь в своём РЕ-заголовке определённые флаги, которые задаются программистом при компиляции проекта. Первое поле размером 16-бит находится по-смещению РЕ.16h и называется "Characteristics". Набор этих флагов определяет глобальные свойства исполняемого файла, и каждый из 16-ти его бит несёт в себе определённую информацию. Основные его биты перечислены нихе:
Чтобы продемонстрировать состояние этих флагов, запустим программу "РЕ-Explorer". Она не то-чтобы отображает флаги, но и позволяет редактировать их биты. Это одна из лучших утилит в своём роде, способная проникнуть в самые закрома исполняемых файлов:
Однако это не единственный бит, от которого плящет ASLR.. а точнее - он для него второстепенный. Прямым рычагом для рандомной релокации образа является слово по-смещение РЕ.5Eh с несовпадающим по смыслу именем "DLL-characteristics". Именно в этом поле хранятся ключи, которые мы задаём компилятору при сборке своего исходника в экзе. Как упоминалось выше, чтобы системный механизм ASLR подхватил наш файл, нужно указать компилятору ключ "/DYNAMIC_BASE" – иначе образ так и останется неперемещаемым, хоть в системе и будет активен ASLR. Бит-мап 16-битного поля РЕ.5Еh представлен ниже:
Мда.. судя по характеристикам, адепты этой секты способны на многое, и ожидать от них можно чего угодно. Заполучив админские права, и привелегию "Debug" мы можем рекурсией обойти все файлы в системной папке и сбросить непригодные нам биты ASLR. Проблема открытых файлов и невозможности перезаписать их образ на диске, решается копированием дескрипторов открытых файлов функциями OpenProcess() с последующим DuplicateHandle(). В результате мы получим родительские права на объект, со-всеми вытекающими последствиями.
Не знаю для чего это нужно, ведь базу образа можно найти и динамическим путём. Любая малварь поколения "Next" поступает именно так, а жёсткую привязку через РЕ-заголовок можно найти сейчас только на свалке истории. Однако для самообразования можно написать такую утилиту, которая будет искать все исполняемые файлы в текущем дире жёсткого диска, и выводить информацию о поддержки ими технологии ASLR. Тут просто поиск и проверка флага "DLL_DYNAMIC_BASE" в поле РЕ.5Еh:
По идее, чтобы прибить ASLR для избранных файлов, нужно обнаружив у него флаг "DynamicBase" сбросить его, тогда у механизма ASLR останутся несыгранные роли. Но чем он нам мешает? У рандомной смены базы тоже есть своя грация, над которой "художники" постарались на славу. М.Руссинович в седьмом издании своего бестцеллера "Внутреннее устройство Windows" описывает эти механизмы на более глубоком уровне, вплоть до алгоритмов вычисления дельты для рандома и прочее. Так-что всем, кого интересует данная тема, советую почитать этот его том. А за сим всё.. до скорого.
На скорую руку заштопав старые щели, Билли сбрасывает на рынок эту-же систему под новым именем, которую ждёт та-же участь и она бумерангом прилетает обратно – круг замыкается. А виной тому хакеры, огромной ступнёй годзиллы втирающие в грязь мастдай только потому, что за свои честно/заработанные требуют товар соответствующего качества – мол если предлагаешь обществу весчь, она должна быть без изъянов. По большому счёту, только хакеры и двигают прогресс компьютерной безопасности, заставляя корпорации строить более мощные обороны.
Большие надежды 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 выставляется ядром на этапе запуска нашего приложения. ОС может сконфигурировать защиту одним из четырёх способов, от которых зависит, сможем-ли мы обойти её на программном уровне, или нет.
1. 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 и могут использоваться на прикладном уровне с админскими правами – вот их список:
1. 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(), которая выделяет страницы с любыми атрибутами.. Какого цвета у разработчиков кровь и что они там курят – хз, нужен эксперимент.
ASLR – принцип работы механизма
Рассмотрим очередную "бомбу" под названием "Advanced Space Layout Randomization" ASLR, которая начиная с Win-7 призвана рандомно менять базу образа в памяти. Отметим, что все системные файлы Win запускаются с включённым механизмом ASLR, но у сторонних приложений он может быть отключён. Чтобы наши программы влились в эту струю, нужно указать это явно, посредством флага компиляции "/DYNAMIC_BASE".
Для образов прикладных программ, новая рандомная база вычисляется при каждом запуске исполняемого файла. В отличие от юзерских программ, базы системных образов вычисляется только один раз, при начальной загрузке операционной системы и используется во всей системе вплоть до её перезагрузки. Если-бы системные DLL заново отображались по разным адресам внутри различных процессов, их код нельзя было-бы использовать совместно. Загрузчику пришлось-бы корректировать указатели на функции API для каждого процесса в отдельности, превращая общие функции в закрытые данные процесса.
Механизм ASLR рандомно меняет базы трёх блоков памяти процесса – это: база образа, база стека, и база кучи Heap. Рандом образа - 8-битный, что позволяет выбрать одну из 256 возможных баз. А вот рандом стека и кучи уже 5-битный – такая разрядность ограничивает выбор до 32-х базовых адресов.
Все-кто хочет под управлением ASLR кочевать в памяти, должны иметь в своём РЕ-заголовке определённые флаги, которые задаются программистом при компиляции проекта. Первое поле размером 16-бит находится по-смещению РЕ.16h и называется "Characteristics". Набор этих флагов определяет глобальные свойства исполняемого файла, и каждый из 16-ти его бит несёт в себе определённую информацию. Основные его биты перечислены нихе:
• Бит 0 – IMAGE_FILE_RELOCS_STRIPPED – таблетка от ASLR.
Устанавливается в единицу, если в файле нет информации о перемещениях, и он должен лечь на дефолтную базе. Если база занята кем-то другим, загрузчик сообщает об ошибке.
• Бит 1 – IMAGE_FILE_EXECUTABLE_IMAGE
Устанавливается, если файл является исполняемым экзе, и действительно может быть запущен.
• Бит 4 – IMAGE_FILE_AGGRESIVE_WS_TRIM
Если взведён, то ОС принудительно урежет объём памяти для этого процесса, путём разбиения его на страницы. Этим битом могут похвастаться процессы-демоны, которые большую часть времени спят, пробуждаясь лишь несколько раз в день.
• Бит 5 – IMAGE_FILE_LARGE_ADDRESS_AWARE
Приложение способно работать с памятью, объёмом больше 2 Gb.
• Бит 12 – IMAGE_FILE_SYSTEM
Если взведён, значит это системный файл типа драйвера.
• Бит 13 – IMAGE_FILE_DLL
Взведённый бит является признаком DLL-библиотеки.
Из всей этой группы, в контексте ASLR примечательным является только бит (0), который разрешает релоки. У библиотек DLL он всегда сброшен, а у исполняемых файлов типа *.ехе – наоборот взведён. Это потому, что при запуске процесса на исполнение сначала в память загружается экзе, который сразу занимает предпочтительную базу, и только потом уже он подтягивает в своё пространство импортируемые либы – т.е. кто первым встал, того и база. Если DLL будет неперемещаемой, то может получиться конфликт базовых адресов EXE<->DLL.Чтобы продемонстрировать состояние этих флагов, запустим программу "РЕ-Explorer". Она не то-чтобы отображает флаги, но и позволяет редактировать их биты. Это одна из лучших утилит в своём роде, способная проникнуть в самые закрома исполняемых файлов:
Однако это не единственный бит, от которого плящет ASLR.. а точнее - он для него второстепенный. Прямым рычагом для рандомной релокации образа является слово по-смещение РЕ.5Eh с несовпадающим по смыслу именем "DLL-characteristics". Именно в этом поле хранятся ключи, которые мы задаём компилятору при сборке своего исходника в экзе. Как упоминалось выше, чтобы системный механизм ASLR подхватил наш файл, нужно указать компилятору ключ "/DYNAMIC_BASE" – иначе образ так и останется неперемещаемым, хоть в системе и будет активен ASLR. Бит-мап 16-битного поля РЕ.5Еh представлен ниже:
C-подобный:
0x0001 Зарезерв (должно быть ноль).
0x0002 Зарезерв.
0x0004 Зарезерв.
0x0008 Зарезерв.
0x0020 IMAGE_DLL_HIGH_ENTROPY_VA = ASLR с энтропией 24 бита (для х64).
0x0040 IMAGE_DLL_DYNAMIC_BASE = ASLR с энтропией 8 бит (для х32).
0x0080 IMAGE_DLL_FORCE_INTEGRITY = обязательная проверка целостности кода.
0x0100 IMAGE_DLL_NX_COMPAT = образ совместим с NX (механизм DEP).
0x0200 IMAGE_DLL_NO_ISOLATION = не изолировать образ.
0x0400 IMAGE_DLL_NO_SEH = запрет на использование в образе обработчиков исключений SEH.
0x0800 IMAGE_DLL_NO_BIND = не связываемый образ.
0x1000 IMAGE_DLL_APPCONTAINER = образ должен выполняться в AppContainer.
0x2000 IMAGE_DLL_WDM_DRIVER = образ является драйвером WDM.
0x4000 IMAGE_DLL_GUARD_CF = поддерживает защиту Control Flow Guard, CFG.
0x8000 IMAGE_DLL_TERMINAL_SERVER_AWARE = образ является терминальным сервером.
Мда.. судя по характеристикам, адепты этой секты способны на многое, и ожидать от них можно чего угодно. Заполучив админские права, и привелегию "Debug" мы можем рекурсией обойти все файлы в системной папке и сбросить непригодные нам биты ASLR. Проблема открытых файлов и невозможности перезаписать их образ на диске, решается копированием дескрипторов открытых файлов функциями OpenProcess() с последующим DuplicateHandle(). В результате мы получим родительские права на объект, со-всеми вытекающими последствиями.
Не знаю для чего это нужно, ведь базу образа можно найти и динамическим путём. Любая малварь поколения "Next" поступает именно так, а жёсткую привязку через РЕ-заголовок можно найти сейчас только на свалке истории. Однако для самообразования можно написать такую утилиту, которая будет искать все исполняемые файлы в текущем дире жёсткого диска, и выводить информацию о поддержки ими технологии ASLR. Тут просто поиск и проверка флага "DLL_DYNAMIC_BASE" в поле РЕ.5Еh:
C-подобный:
format pe console
include 'win32ax.inc'
entry start
;//------
.data
capt db 13,10,' ASLR info v0.1'
db 13,10,' ~~~~~~~~~~~~~~~~~~~',0
info db 13,10,10,' File: ======== %s'
db 13,10,' PE.16h = %04X'
db 13,10,' PE.5Eh = %04X',0
aslr db ' <-- ASLR dynamicBase bit found',0
fData WIN32_FIND_DATA ;// структура для FindFile()
fName = fData +44 ;// ..смещение имени-файла в ней
fMask db '*.exe',0 ;// маска для поиска = только ЕХЕ
frmt db '%s',0 ;// спецификатор для scanf()
hndl dd 0 ;// хэндл поиска
buff rb 512 ;// буфер под РЕ-заголовок
;//------
.code
start: cinvoke printf,capt ;// шапка
;//--- Поиск подходящих кандидатов.. ---------
invoke FindFirstFile,fMask,fData ;// ищем первый файл
mov [hndl],eax ;// запомнить хэндл поиска
;//--- Сбрасываем заголовок найденного файла в буфер -----
@scan: invoke _lopen,fName,0 ;// открываем файл
push eax ;// запомнить его хэндл
invoke _lread,eax,buff,512 ;// считываем 512 байт в свой буфер
pop eax ;// хендл на родину
invoke _lclose,eax ;// файл больше не нужен..
;//--- Поиск флагов в буфере -----------------
mov esi,buff ;// ESI = голова буфа
mov esi,[esi+0x3c] ;// указатель на РЕ-заголовок
add esi,buff ;// смещаемся к нему
mov ax,word[esi+0x16] ;// AX = характеристики ЕХЕ
mov bx,word[esi+0x5e] ;// ВХ = характеристики DLL
and eax,0xffff ;// обнулить не нужное
and ebx,0xffff ;// ...^^^
push ebx ;// запомнить флаги DLL
cinvoke printf,info,fName,eax,ebx ;// выводим батву на консоль
;//--- Определяем поддержку файлом ASLR ------
pop ebx ;// ЕВХ = флаги DLL
test ebx,1000000b ;// чекнуть бит "DLL_DYNAMIC_BASE"
jz @next ;// если он нуль..
cinvoke printf,aslr ;// иначе: даём знать об этом!
@next: invoke FindNextFile,[hndl],fData ;// продолжить поиск в дире..
or eax,eax ;// если EAX = 0, нет больше файлов
jnz @scan ;// повторить, если NoZero.
invoke CloseHandle,[hndl] ;// прибить хэндл поиска
@exit: cinvoke scanf,frmt,capt ;// ждём клаву..
cinvoke exit,0 ;//
;//=== Секция импорта ========================
data import
library msvcrt,'msvcrt.dll',kernel,'kernel32.dll'
import msvcrt,printf,'printf',scanf,'scanf',exit,'exit'
import kernel,FindFirstFile,'FindFirstFileA',\
FindNextFile,'FindNextFileA',CloseHandle,'CloseHandle',\
_lopen,'_lopen',_lread,'_lread',_lwrite,'_lwrite',_lclose,'_lclose'
end data
По идее, чтобы прибить ASLR для избранных файлов, нужно обнаружив у него флаг "DynamicBase" сбросить его, тогда у механизма ASLR останутся несыгранные роли. Но чем он нам мешает? У рандомной смены базы тоже есть своя грация, над которой "художники" постарались на славу. М.Руссинович в седьмом издании своего бестцеллера "Внутреннее устройство Windows" описывает эти механизмы на более глубоком уровне, вплоть до алгоритмов вычисления дельты для рандома и прочее. Так-что всем, кого интересует данная тема, советую почитать этот его том. А за сим всё.. до скорого.