Крис Касперски
Cудьба shell-кода на системах с неисполняемым стеком
Не ошибается то, что не работает
(c) Windows
Отчаявшись справиться со своими же собственными ошибками, компания Microsoft совместно с Intel и AMD реализовала технологию DEP, призванную покончить с удаленными атаками раз и навсегда, но этого не произошло, и защиту удалось обойти.
Несмотря на все усилия, вырытые рвы и воздвигнутые защитные сооружения, интенсивность удаленных атак не снижается, и отражать их становится все труднее. Хакеры научились маскировать вредоносные процессы под LINUX/BSD и NT, разобрались с брандмауэрами и освоились с распределенными системами – сотни тысяч зараженных машин, управляемых через IRC, – это настоящая армия, очень мощная и разрушительная. Интересы отдельных пользователей, пострадавших от атаки, отходят на задний план, уступая место вопросам безопасности всей инфраструктуры в целом. А инфраструктура – это уже серьезно.
Анализ показывает, что подавляющее большинство атак используют ошибки переполнения, фундаментальную природу которых мы уже обсуждали (см. «Ошибки переполнения буфера извне и изнутри как обобщенный опыт реальных атак»). Львиная доля таких ошибок находится в Internet Explorer, что, в частности, и явилось причиной перехода NASA на FireFox (http://www.securitylab.ru/news/242844.php?R1=RSS&R2=allnews), что можно только приветствовать. Однако FireFox (как и все остальные браузеры) не свободен от ошибок, и в нем присутствует большое количество критических уязвимостей, позволяющих злоумышленнику выполнить свой код (далее по тексту называемый shell-кодом). Вот только одна из таких дыр: http://www.securitylab.ru/vulnerability/240254.php.
Рисунок 1. Схематичное изображение стека. Стрелка показывает направление, в котором растет стек.
Локальный буфер «растет» в противоположном направлении и при определенных обстоятельствах может затирать адрес возврата
Никакое программное обеспечение небезопасно! Даже если своевременно устанавливать свежие заплатки, всегда существует риск, что хакер найдет новую дыру, о которой еще никто не знает, и с успехом воспользуется ею. Кстати говоря, разработка атакующих программ за последние несколько лет поднялась с колен чистого энтузиазма и встала на коммерческий поток с большими вложениями. Стоимость гарантированно работающего эксплоита зачастую доходит до $2 000. Можно только догадываться, кому и зачем это нужно…
В очередной раз возникло понимание, что «дальше так жить нельзя» (c) и нужно что-то решать. Попытки покончить с удаленными атаками неоднократно предпринимались еще с 80-х годов, особенно после Червя Морриса, но все безуспешно. Как говорят в этих случаях американцы: серебряной пули нет! Оборотня не убить! Тем не менее, Microsoft решилась на отчаянный шаг и возродила старые идеи, отрытые на помойке истории и реализованные без учета понимания ситуации наших дней.
Технология DEP (Data Execution Protection – защита от выполнения данных), реализованная в Windows XP SP2 и Server 2003 SP1, делает секцию данных, стек и кучу неисполняемыми, что (теоретически) предотвращает засылку shell-кода и отсекает целый класс удаленных атак, основанных на переполнении.
В чем же революционность такого решения? Ведь еще со времен Windows 95 (не говоря уже про стандарт POSIX, принятый в 1985 году) xатрибут (от eXecutable – исполняемый) имеется только у кодовой секции и отсутствует у всех остальных. Достаточно взять любой ELF/PE-файл и посмотреть! Так-то оно так, но… у x86процессоров на этот счет имеется свое собственное мнение: на уровне страниц процессор поддерживает всего лишь два атрибута защиты: -a- (от accessed – страница доступна для чтения/исполнения) и -w- (от write – запись в страницу разрешена). Никакого x-атрибута в PTE нет, и он присутствует только в таблице селекторов, а это значит, что мы не можем выборочно разрешать/запрещать исполнение кода для отдельных страниц, а только для всего сегмента целиком. Вот потому Windows с ее плоской моделью памяти вынуждена трактовать атрибут -r- как x-, а -x- как -r-. То есть, право на чтение страницы дает неявное право на ее исполнение и наоборот.
На самом деле, никакого произвола со стороны процессора здесь нет: проблему можно решить и в рамках плоской модели. Достаточно «всего лишь» перегруппировать сегменты и установить правильные лимиты (см. рис. 2). Естественно, это требует больших телодвижений со стороны разработчиков ядра (необходимо использовать раздельные ITLB/DTLB и т. д.), что их совсем не вдохновляет.
Рисунок 2. «Эмуляция» NX/XD-битов на x86-процессорах
Почему же в x86 не была предусмотрена возможность задания x-атрибута на уровне страниц? Риторический вопрос… Скорее всего, во времена проектирования 80386 это было никому не нужно, вот и решили не усложнять логику процессора без необходимости. А вот в Itanium этот атрибут присутствует изначально, и специальный бит, известный под аббревиатурой XD (от eXecute Disable – выполнение запрещено), определяет, разрешено ли выполнение кода в данной странице или нет.
Аналогичный бит имеется и в процессорах AMD-64 (Opteron и Athlon 64), только там он называется NX (от No-eXecute – не выполняемый). Кому-то в компании пришла в голову «здравая» мысль объявить x-атрибут «технологией», и вокруг NX-бита тут же развернулась рекламно-маркетинговая акция «Enhanced Virus Protection» (расширенная вирусная защита), или сокращенно EVP. На сайте компании выложено множество красочных роликов, демонстрирующих, как AMD борется с вирусами на уровне процессора (!). Неудивительно, что 64-битная редакция NT от рождения имеет неисполняемую кучу и стек! Microsoft просто подхватила брошенный ей атрибут защиты и встроила его в систему, чтобы лишний раз продемонстрировать, что она не отстает от прогресса. В этом-то и заключается сущность аппаратного (hardware-enforced) DEP.
Рисунок 3. Организация виртуальной памяти
Рисунок 4. NX-бит — новый атрибут защиты в PDE/PTE
Воспользовавшись шумихой, развернутой AMD, компания Intel внедрила поддержку XD-бита в 32-разрядные Pentium, сделав эту «технологию» доступной всем и каждому (желающих пересесть на AMD-64 ради одного лишь DEP набралось не так уж и много). Строго говоря, Nx присутствует не только в «настоящих» 64-битных процессорах от AMD, но и в собранных на 64-битном ядре 32-битных (т.е. работающих в состоянии перманентной эмуляции i386) процессорах – например, Sempron (около 99$ за штуку). Достаточно лишь воткнуть последний Pentium-4 и обновить ядро, чтобы Windows могла задействовать новые аппаратные возможности, и тогда при попытке выполнения прикладного кода в куче, секции данных или в стеке возбуждается исключение типа STATUS_ACCESS_VIOLATION (C0000005h), и если только программист не установил свой обработчик SEH, выполнение программы аварийно завершается с выдачей сообщения «to help protect your computer, Windows has closed this program» (см. рис. 5).
Рисунок 5. Реакция DEP на попытку выполнения кода на стеке
В режиме ядра при попытке выполнения кода в неисполняемой области памяти возбуждается исключение ATTEMPTED_EXECUTE_OF_NOEXECUTE_MEMORY c BugCheck-кодом FCh, обрушивающим систему в «синий экран». 64-битные версии NT защищают от исполнения стек ядра, paged- и session pool, а 32-битные – только стек.
Если процессор не поддерживает NX/XD-битов, система активирует программный (software-enforced) DEP, который даже и не пытается эмулировать неисполняемый стек/кучу (а ведь мог бы!). Технология, которую продвигает Microsoft, в действительности представляет собой примитивную защиту обработчика структурных исключений, ранее известную под именем SafeSEH. Она никак не препятствует выполнению shell-кода, но затрудняет использование им структурных исключений, если тот вдруг решит их использовать. Подробнее об этом можно прочитать в статье «SEH на службе контрреволюции», электронная копия которой лежит на сервере ftp://nezumi.org.ru.
Мы будем обсуждать только аппаратный DEP, поскольку его сложнее всего обойти. Некоторые даже считают, что это невозможно вообще: «…на сегодняшний день не существует ни одного хотя бы концептуального эксплойта, на практике доказывающего возможность поражения Windows XP с установленным пакетом обновления Service Pack 2» (www.computerra.ru/softerra/36652). Если бы DEP проектировался головой, все так бы и было. Однако Microsoft идет своим особым путем, который умом не понять, и DEP обходится без труда, а вот у легальных пользователей возникает множество проблем, о которых мы еще поговорим.
Неправильно называть DEP защитным механизмом. До «защитного механизма» ему так же далеко, как одиноко торчащему рулю до самосвала. DEP – это всего лишь поддержка атрибутов защиты страниц и ничего более!
Конфигурирование DEP
64-битные редакции NT при работе в native-режиме всегда задействуют DEP и не позволяют его отключать. Если разработчику хочется выполнить код на стеке или в куче (а хочется это достаточно часто), он должен явно назначить атрибуты доступа к данному региону памяти путем вызова API-функции VirtualAlloc или VirtualProtect. Никаких прав для этого не нужно, так что мы получаем лишь видимость безопасности – защиту от непреднамеренного доступа, но не более того, и чуть позже мы покажем, как ее обойти.
С 32-битными приложениями все намного сложнее. Существует огромное количество уже написанного ПО, трактующего атрибут -r- как -x- и отказывающегося работать, если это не так. Поэтому для сохранения обратной совместимости Microsoft предусмотрела возможность отключения DEP для 32-битных редакций NT и 32битных приложений, исполняющихся в 64битных редакциях NT.
Чтобы задействовать механизм DEP, необходимо иметь процессор, поддерживающий NX/XD-биты, причем 32-битные процессоры поддерживают NX-бит только в режиме PAE (Physical Address Extension – расширение физических адресов). 32-битные редакции NT автоматически распознают тип процессора, при необходимости добавляя ключ /PAE в файл boot.ini. 64битные редакции не имеют отдельного PAE-ядра (они работают через технологию расширения окна адресации Address Windowing Extension – AWE), поэтому добавлять ключ /PAE для них не нужно.
Если не указывать никаких дополнительных ключей, то в 32-битных редакциях NT будет запрещено выполнение кода на стеке, в ядре и в некоторых системных службах прикладного уровня. Все остальные приложения будут исполняться в обычном режиме. 64-битные версии по умолчанию разрешают выполнение кода на стеке/куче только для 32-битных приложений, а для 64-битных они запрещены (см. рис. 6).
Рисунок 6. Регионы памяти, защищенные от выполнения кода
Ключ /execute полностью отключает DEP для 32-битных редакций NT и 32-битных приложений, исполняющихся под 64-битными редакциями NT, но на «родные» 64-битные приложения его влияние не распространяется, и они по-прежнему остаются защищены.
Рисунок 7. Конфигурирование DEP через редактирование файла boot.ini в Notepad
Ключ /noexecute=уровень_политики позволяет конфигурировать DEP по своему усмотрению, выбирая требуемый уровень защищенности. Параметр «уровень_политики» может принимать одно из следующих значений: AlwaysOn, AlwaysOff, OptIn и OptOut, описанных в таблице 1.
Таблица 1. Влияние уровня политики на безопасность
Параметр
|
Значение
|
OptIn (по умолчанию)
|
DEP включена только для ограниченных системных процессов Windows и ядра
|
OptOut
|
DEP включена для всех процессоров и ядра, однако можно сформировать список приложений, на которые защита не распространяется
|
AlwaysOn
|
DEP включена для всех процессоров и ядра. Отключить защиту для выборочных приложений нельзя
|
AlwaysOff
|
DEP отключена для всех процессов и ядра
|
Если редактирование boot.ini приводит вас в ужас (что в общем-то вполне закономерно, поскольку после этого система может перестать загружаться), воспользуйтесь интерактивным конфигуратором:
- В командной строке введите «start sysdm.cpl» или нажмите +.
- На вкладке «Дополнительно» в группе «Быстродействие» нажмите кнопку «Параметры».
- Перейдите на вкладку «Предотвращение выполнения данных».
- Выберите пункт «Включить DEP только для основных программ и служб Windows», чтобы добавить в boot.ini, параметр OptIn или выберите «Включить DEP для всех программ и служб, кроме выбранных ниже», чтобы добавить параметр OptOut. Другие параметры через интерактивный конфигуратор добавить невозможно.
- Если был выбран параметр OptOut, нажмите кнопку «Добавить» и укажите программы, на которые DEP распространяться не должна.
Список программ, для которых отключен DEP, можно сформировать и через реестр (чисто хакерский путь, экономящий массу времени). Просто откройте ключ HKLMSOFTWAREMicrosoftWindows NTCurrentVersionAppCompatFlagsLayers и создайте новый ключ типа «строка». Его имя должно содержать полный путь к exe-файлу, защиту которого мы хотим отключить, поместив внутрь ключа значение «DisableNXShowUI». Только не перепутайте их местами – иначе ничего не получится.
Рисунок 8. Редактирование файла boot.ini через интерактивный конфигуратор
Рисунок 9. Интерактивное конфигурирование DEP
Проблемы совместимости
Исполняемый стек необходим очень многим приложениям: защитным механизмам, эмуляторам, just-in-time-компиляторам и т. д. Попытки сделать стек неисполняемым неоднократно предпринимались еще в 70-80-х годах, когда никакого Windows и в проекте не существовало, но… они как-то не прижились. А все из-за проблем совместимости. Кому нужна операционная система, если на ней нельзя запускать свои любимые приложения? Выхода нет. Или безопасность, или совместимость. Компромисс невозможен. Разве только что… рассказывают (врут, конечно), что давным-давно стали греки строить баню. И озадачились они вопросом: стоит ли строгать доски или нет? Ведь если не построгать, можно легко занозиться, а если построгать… попробуй тут не поскользнуться мыльной пяткой. Поразмыслив, они поступили так: построгали и прибили обстроганным на низ. Аналогичным путем пошла и Microsoft: разработала некоторую пародию на защиту и тут же ее отключила, чтобы у пользователей не возникло лишних проблем.
Тем не менее проблемы все-таки возникли. Наберите «DEP» в «Базе Знаний», чтобы посмотреть, какие конфликты обнаруживаются только с продуктами самой Microsoft, которая по всем понятиям должна была обеспечить надлежащую преемственность и совместимость. Вот, например:
Со сторонними производителями дело обстоит еще хуже. Навесные упаковщики и протекторы, использующие самомодифицирующийся код и другие антихакерские трюки, не могут работать с неисполняемым стеком, а это значит, что защита DEP для них должна быть отключена. Кое-кто может возразить, что все проблемы от «неправильного» стиля программирования и использования «недокументированных» особенностей системы (которые на самом деле очень даже документированные), однако проблемы встречаются не только у кустарей, но и весьма именитых фирм, например, Borland.
Заходим в «Базу Знаний», набираем «DEP» и тут же получаем множество ссылок, типа:
Таким образом, DEP очень конфликтная вещь, создающая множество проблем и ни от чего не защищающая. Microsoft допустила множество серьезных просчетов, позволяющих проникать сквозь DEP даже на максимальном уровне защищенности.
Все дальнейшие рассуждения применимы как к 32-, так и к 64-битным редакциям NT и не зависят от настройки системы. Будем для определенности считать, что ключ /noexecute установлен в положение AlwaysOn.
Атака на DEP
Microsoft подтвердила возможность обхода DEP еще в январе 2005, когда на maxpatrol появилась статья Александра Анисимова «Defeating Microsoft Windows XP SP2 Heap protection and DEP bypass», однако не придала этому большего значения, заявив, что реализовать удаленную атаку все равно не удастся: «An attacker cannot use this method by itself to attempt to run malicious code on a user’s system. There is no attack that utilizes this, and customers are not at risk from the situation» (Атакующий не может использовать этот метод для запуска зловредного кода на целевой системе. До сих пор не было продемонстрировано ни одной атаки, использующей этот трюк, и потребители находятся вне риска – http://www.eweek.com/article2/0,1759,1757786,00.asp).
Это наглая ложь! Механизм DEP легко пробивается с любого расстояния, хоть из Антарктики. Результатом атаки становится переданный и успешно выполненный shell-код, выполняющий команды, заранее подготовленные злоумышленником. Чтобы понять, как это делается, сначала необходимо разобраться с классическими методами переполнения, подробно разобранными в статье «Ошибки переполнения буфера извне и изнутри как обобщенный опыт реальных атак».
Напомним читателю ее основные положения: отсутствие контроля границ локальных буферов позволяет затирать адрес возврата из функции, помещая сюда указатель на shell-код, который находится здесь же, в стеке. Другой тип переполнения связан с кучей. С его помощью хакер может модифицировать любую writable ячейку в адресном пространстве уязвимого процесса (например, подменить указатель на виртуальную функцию или подделать адрес возврата). Имеются и другие возможности (в частности, атакующий может изменить обычный порядок выделения блоков памяти из кучи, разместив следующий выделяемый блок поверх ключевых структур данных), но для простоты изложения мы ограничимся модификацией адреса возврата.
Рисунок 10. Классическая удаленная атака — засылка shell-кода в стек с последующей передачей на него управления
путем модификации адреса возврата
Существует множество защитных механизмов, контролирующих целость кучи и адреса возврата, но… со своей задачей они не справляются. Это отдельная большая тема, никак не связанная ни с NX/XD-битами, ни с технологией DEP. В следующих статьях мы подробно рассмотрим ее, но… это будет потом. Сейчас же ограничимся тем, что DEP никак не препятствует модификации адреса возврата и существует множество программ, действительно позволяющих это делать (Internet Explorer, FireFox и др.).
Манипулируя адресом возврата, хакер может вызывать произвольные функции уязвимой программы (в том числе и API-функции операционной системы), передавая необходимые параметры через стек. Конечно, при этом он будет очень ограничен в своих возможностях, поскольку среди готовых функций полезных для хакерствования не так уж и много, однако если как следует подумать головой, проблему удается решить путем вызова API-функции CreateProcess или функции System из библиотеки CRT, запустив штатную программу типа tftp.exe и закачав на атакуемый компьютер двоичный файл, который можно исполнить с помощью CreateProcess/System (атака типа return-to-libc). Механизм DEP этому сценарию никак не препятствует, поскольку в этом случае shell-код передается не через стек/кучу, а через легальный исполняемый файл. На стеке содержатся лишь аргументы вызываемых функций и «поддельные» адреса возврата, указывающие на них.
Таким образом, даже при включенном DEP у хакера сохраняется возможность забрасывать на атакуемую машину свой код и передавать на него управление. Наличие tftp.exe не является необходимым условием для атаки. Даже если его удалить, хакер может вызвать cmd.exe и… нет, удалять все файлы с диска необязательно (хотя и возможно), достаточно перенаправить вывод в файл и с помощью ECHO создать крохотный com, делающий что-то «полезное». Да и в самой уязвимой программе наверняка содержатся функции, через которые можно загрузить файл из Интернета… Словом, возможностей – море! Атаки этого типа хорошо изучены хакерами и описаны в литературе. Только специалисты из Microsoft похоже об этом ничего не знают, иначе как можно объяснить тот загадочный факт, что успешность атаки данного типа никак не зависит от активности DEP и старые эксплоиты полностью сохраняют свою работоспособность. Распространение червей останавливается только потому, что вместе с DEP пакет обновлений включает в себя заплатки на все известные дыры. Однако стоит хакерам найти еще одну ошибку переполнения (а это всего лишь вопрос времени), как Интернет захлестнет новая эпидемия, и никакой DEP ее не остановит. (Вообще-то, нет, не захлестнет, ведь автоматическое обновление теперь включено по умолчанию.)
Некоторые могут сказать, что это «ненастоящая» атака, поскольку в ней отсутствует явная передача shell-кода через стек, а ведь именно на это DEP и рассчитан! Мысль, конечно, умная, но забавная. И какой это хакер бросится на амбразуру, если можно сходить в обход? Неужели в Microsoft всерьез считают, что атакующий играет по «правилам» и идет по пути наибольшего сопротивления, карабкаясь по извилистой горной тропинке, напичканной патрулями, когда столбовая дорога никем не охраняется?!
В качестве разминки для мозгов рассмотрим альтернативный сценарий атаки, передающий shell-код через стек. Засунуть shell-код в локальный буфер – не проблема, подменить адрес возврата тоже, но… при попытке передачи управления на shell-код при активном DEP будет возникать исключение, ведь x-атрибута у нас нет, хотя… это смотря, что мы переполняем. Как уже говорилось выше, некоторые приложения (в том числе и браузеры) нуждаются в исполняемом стеке, в который они складывают откомпилированный java-код. Но не будем смягчать себе условия. Давайте считать, что никаких исполняемых страниц в нашем стеке нет. Чтобы их заполучить, необходимо либо сбросить NX/XD-бит (но это можно делать только система), либо… вызывать функцию VirtualProtect, назначая атрибуты защиты по своему желанию! Но как же мы вызовем VirtualProtect без возможности выполнения shell-кода? Да очень просто – скорректируем адрес возврата так, чтобы он указывал на VirtualProtect, тогда при выполнении команды return она передаст на нее управление!
На самом деле, это только идея. До практической реализации ей еще далеко. В жизни все намного сложнее и… элегантнее. Допустим, вызвали мы VirtualProtect. А дальше что? Куда она возвратит управление? И как узнать адрес выделенного блока? Это великолепная головоломка, на решение которой автор этой статьи потратил целый день и был очень разочарован, когда узнал, что он не один такой умный, и все загадки разгадали еще до него. Взгляните на рис. 11. Специалисты поймут идею с первого взгляда, неспециалистам мы сейчас все объясним.
Рисунок 11. Подготовка стека для реализации атаки типа commit-n-copy
Итак, все по порядку. Начнем с ответа на вопрос: куда возвращает управление VirtualProtect? Ответ очевиден: по указателю, который лежит за модифицированным адресом возврата! В момент выхода из VirtualProtect, процессор сталкивает текущий адрес возврата с вершины стека и удаляет переданные ей аргументы. Так происходит потому, что VirtualProtect (как и все API-функции) придерживается соглашения о передаче параметров типа stdcall, при котором аргументы удаляются самой вызываемой функцией.
Таким образом, мы можем вызывать столько stdcall-функций, сколько захотим. Процессор будет послушно стягивать их со стека, поступательно двигаясь от вершины вглубь. Техника вызова cdecl-функций выглядит чуть сложнее. Они не очищают аргументы при выходе, и атакующему приходится это делать самостоятельно. Проще всего перенаправить адрес возврата на код типа «ADD ESP,n/RET», расположенный где-то внутри уязвимой программы, где n – количество байт, занятых аргументами. Такую комбинацию можно встретить практически в любом оптимизированном эпилоге. Ну а нужное n подобрать совсем несложно!
Теперь мы знаем, как вызывать функцию для изменения атрибутов доступа и вернуться обратно в shell-код, но это еще не все. Функция VirtualProtect требует, чтобы ей передали адрес уже выделенного региона, а shell-коду он неизвестен. К тому же, по слухам, Microsoft собирается встроить в VirtualProtect дополнительную проверку, запрещающую назначать x-атрибут, если он не был присвоен еще при выделении. Тупик? Не совсем, ведь мы можем выделить новый регион, назначить нужные права, скопировать туда свой shell-код и передать ему управление. С выделением никаких проблем нет – берем VirtualAlloc и вперед. Как прочитать возвращенный указатель – вот в чем вопрос! Функция передает его в регистре EAX, и заранее предсказать его значение невозможно. А ведь хакер должен сформировать указатель и затолкать его в стек еще на стадии проектирования shell-кода, то есть задолго до вызова VirtualAlloc. Ну теперь уж точно тупик… А вот и нет! Внимательное чтение SDK (или Рихтера) показывает, что Windows позволяет COMMIT (то есть передавать) уже переданную память по заданному адресу и хотя последствия такого выделения могут быть очень печальными (для кучи) – кого это волнует?! Главное, чтобы shell-код получил управление, и он его получит! Выбираем произвольный адрес, который с высокой степенью вероятности не занят ничем полезным (например, 191000h), и передаем его функции VirtualAlloc вместе с флагом MEM_COMMIT и атрибутами PAGE_EXECUTE_READWRITE. Все! Первая стадия атаки благополучно завершилась, и теперь можно глотнуть пива.
На втором шаге мы вызываем функцию memcpy и копируем shell-код в только что выделенный регион памяти, целевой адрес которого заранее известен. После этой операции shell-код оказывается в области памяти, где разрешено выполнение, и нам остается только засунуть в стек еще один подложный адрес возврата, который будет указывать на shell-код (внимание! функция memcpy в отличие от VirtualAlloc придерживается cdecl соглашения, поэтому после нее мы уже не можем выполнять никакие другие функции, предварительно не удалив аргументы со стека).
Последовательность вызовов, реализующих атаку, выглядит так (напоминаем: это не shell-код, это именно последовательность вызовов API-функций, осуществляемая путем подмены адресов возврата):
Листинг 1. Последовательность вызова функций, реализующих атаку типа commit-n-copy
VirtualAlloc(REMOTE_BASE, SHELLCODE_LENGTH, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(REMOTE_BASE, SHELLCODE_BASE, SHELLCODE_LENGTH);
GOTO shell_code;
Маленький нюанс – 64-битные редакции NT передают аргументы APIфункциям через регистры, и потому вызывать VirtualAlloc на них уже не удастся (разумеется, речь идет только о 64-битных приложениях). То есть вызывать-то удастся, а вот передать аргументы – нет, поэтому этот сценарий уже не сработает, однако вызов функций уязвимой программы через подмену адреса возврата будет действовать по-прежнему (то есть запустить tftp.exe мы все-таки сможем, вызывая ее через функцию System, которая по-прежнему принимает аргументы через стек).
Подведем итог: стечение ряда неблагоприятных для DEP обстоятельств делает эту технологию практически полностью бесполезной. Да, она отсекает целый класс атак, основанных на переполнении, однако дает пищу для новых, и в целом ситуация никак не меняется. Забавно, но большинство людей (в том числе и администраторов!) совершенно не понимают, что такое DEP, зачем он нужен, какие цели преследует и в чем заключается взлом. Достаточно почитать дискуссию, развернувшуюся на http://www.mastropaolo.com/?p=13, чтобы убедиться, что оба предложенных сценария обхода DEP не считаются взломом, поскольку x-атрибут присваивается «легальным» способом через вызов VirtualAlloc. На самом деле суть взлома вовсе не в том, чтобы выполнить код в области памяти без x-атрибута (это действительно невозможно), а в том, чтобы использовать уязвимое ПО в своих хакерских целях. В идеале DEP должен представлять собой целый комплекс защитных мер, предотвращающих это, но ничего подобного он не делает, ограничиваясь простой формальной поддержкой NX/XD-атрибутов. Перечислим те обстоятельства, которые ему мешают:
- параметры API-функций передаются через стек;
- адреса API-функций и положение вершины стека легко предсказуемы;
- всякий процесс может пометить любой регион памяти как «исполняемый»;
- функция VituallAlloc позволяет выделять/передавать уже переданную память.
В лагере UNIX
Ошибки переполнения не являются «собственностью» Windows. Это общая проблема для всех программ С/С++, родиной которых был и остается UNIX. Уязвимые приложения в изобилии встречаются и там. С ними активно борются путем хитроумных извращений и оригинальных технических решений, о которых мы и собираемся сейчас рассказать.
Начнем с того, что существуют процессорные архитектуры (SPARC, SPARC64, Alpha, HPPA), на которых UNIX имеет неисполняемый стек еще от рождения. Существуют архитектуры, использующие раздельные стеки для хранения адресов возврата и локальных переменных. Подменить адрес возврата на них невозможно, но легко затереть указатель на функцию, при вызове которой управление получит shell-код (правда, передача аргументов представляет собой большую проблему, особенно если они передаются через регистры, тем не менее атаковать такую систему все-таки возможно, пусть и на концептуальном уровне).
На IBM-совместимых машинах LINUX/BSD по умолчанию ведут себя точно так же, как XP без SP2. То есть трактуют r-атрибут как -x-, позволяя исполнять код там, где вздумается. Первой ласточкой, ринувшейся на встречу буре, стал патч от Solar Designer, делающий стек неисполняемым. Хакеров это ничуть не смутило (ведь осталась возможность вызова функций через адрес возврата return-to-libc), но зато помешало многим «честным» программам. В общем, большой популярности это решение не получило.
Неисполняемый стек – это всего лишь одна из защитных мер, которая оправдывает себя только в совокупности с целым комплексом остальных. Так, например, бессмысленно ставить бронированную дверь, если можно залезть через окно. Рассмотрим возможности, которые предоставляет популярный пакет PaX, распространяемый на бесплатной основе вместе с исходными текстами и подробной технической документацией, из которой можно почерпнуть массу интересного.
Первое и главное – PaX не требует специальной поддержи со стороны оборудования и не нуждается в битах NX/XD. Вместо этого он группирует сегменты, как показано на рис. 2, и устанавливает лимиты так, чтобы они не пересекались. Несомненный плюс такого решения в том, что для защиты от хакеров нам не нужно приобретать новый процессор – это раз. Shell-код не сможет выделить исполняемый регион в области кучи или стека – это два. Тоже самое, впрочем, относится и к just-in-time компиляторам, поэтому с совместимостью будут проблемы, которые PaX обходит довольно элегантным путем. При аппаратной поддержке со стороны ЦП (биты NX/XD) он может защищать не только весь процесс целиком, но и его отдельную часть. Допустим, мы имеем приложение, нуждающееся в исполняемом стеке, но не присваивающее x-атрибут явно. Под Windows мы будем вынуждены занести его в список программ, на которые механизм DEP не распространяется, со всеми вытекающими отсюда последствиями. А PaX позволяет отключить защиту лишь для части стека! Конечно, это снижает иммунитет системы, но не так радикально, как полное отключение DEP. К тому же рандомизация адресов не позволяет shell-коду добраться до этой исполняемой области и использовать ее в своих целях.
Вторым китом является технология рандомизации адресного пространства – Address Space Layout Randomization или сокращенно ASLR. Если при каждом запуске программы непредсказуемым образом менять положение всех сегментов, хакер не сможет определить ни расположение shell-кода, ни адреса API-функций (это утверждение справедливо даже для тех клонах UNIX, в которых вызов APIфункций происходит через прерывание, например в Linux/i386, поскольку прикладные процессы общаются с ядром не напрямую, а через разделяемые библиотеки, придерживающиеся соглашения stdcall, и являющиеся своеобразным аналогом KERNEL32.DLL), следовательно, подмена адреса возврата не дает ничего кроме DoS. Пакет PaX позволяет рандомизовать основные сегменты ELF-файла (code, data, bss), кучу, библиотечные функции, стек потока, разделяемую память и стек ядра, короче устраивает настоящий хаос, в котором не остается ничего постоянного, за что можно было бы уцепиться. Ну или практически ничего.
Третий кит – PaX «дорабатывает» функцию mprotect так, чтобы назначать x-атрибут могла только система. Никакой прикладной процесс не может изменить атрибуты региона памяти с -x на +x. Также никакой регион памяти не может иметь -x- и -w- атрибуты одновременно. Пакет PaX портирован на множество систем, в том числе и… под Windows. Существует по меньшей мере два приличных порта для NT – BufferShield (см. одноименную врезку) и StackDefender (http://www.ngsec.com/ngproducts/stackdefender), которые в отличие от штатного DEP действительно защищают компьютер от вторжения, и преодолеть их ну очень трудно.
Другой популярный пакет – Exec Shield (http://people.redhat.com/mingo/exec-shield), входящий в состав Red Hat Enterprise Linux v.3, update 3, также использует неисполняемую кучу/стек, замечательно работая на всем семействе x86 процессоров без NX/XD-битов, и частично рандомизует адресное пространство, произвольным образом изменяя базовый адрес стека, расположение разделяемых библиотек и начало области кучи. Остальная память остается нетронутой, однако для отражения большинства атак и этого оказывается вполне достаточно. Фактически, Exec Shield представляет урезанный вариант PaX и не несет в себе ничего нового.
Еще хуже дела обстоят в OpenBSD. Начиная с версии 3.3 ядро поддерживает механизм W^X (произносится как «W xor X»), который по своему названию предотвращает одновременную установку атрибутов -x- и -w- на любую область памяти, что по замыслу разработчиков должно серьезно озадачить хакеров (http://marc.theaimsgroup.com/?l=openbsd-announce&m=105175475006905&w=2).
На самом деле эта защита элементарно обходится множественным вызовом функции mprotect. Сначала shell-код вызывает mprotect, устанавливая один лишь атрибут записи (если он не был установлен ранее), затем копирует shell-код через memcpy и вызывает mprotect еще раз, сбрасывая атрибут записи и присваивая себе права исполнения. Кстати говоря, в версии 3.3 W^X не работал на x86, поскольку у того отсутствует возможность задания x-атрибута на уровне страниц, однако, начиная с версии 3.4, этот недостаток был исправлен.
Короче говоря, в штатной конфигурации, без установки пакета PaX, все UNIX-подобные системы потенциально уязвимы и допускают выполнение shell-кода по сценарию, описанному в разделе «атаки на DEP».
Рисунок 12. Окно конфигурации пакета StackDefender
Рисунок 13. Окно протокола пакета StackDefender
Заключение
DEP – это не защита! Это рекламный трюк! Если только Microsoft не создаст свой собственный клон PaX, реализованный по всем правилам (что очень навряд ли), черви и хакеры продолжат свое существование. Так что слухи об их кончине в очередной раз оказались преждевременными и сильно преувеличенными. По этому поводу вспоминается один анекдот «Недостаток у танка ровно один: через триплексы ни хрена не видно, куда едешь. Но когда ты едешь на танке, это не очень большой недостаток». Так вот, Microsoft едет в танке, прикрываясь толстой броней монополизма, и ее совершенно не волнует, куда она едет и что творит. Не полагайтесь на Microsoft! Для надежной обороны своего компьютера (рабочей станции, сервера) используйте BufferShield или StackDefender, «пробить» который смогут только гуру, да и то с кучей ограничений, делающих червей практически нежизнеспособными.
Приложение
Кто поддерживает XD-бит
По данным компании Intel, XD-бит поддерживают следующие операционные системы, предотвращая непреднамеренное исполнение кода в неисполняемых областях памяти (однако преднамеренных хакеров эта мера не остановит, и все системы могут быть атакованы, Windows – проще, Red Hat – сложнее):
- Microsoft Windows Server 2003 with Service Pack 1;
- Microsoft Windows XP with Service Pack 2;
- SUSE Linux 9.2;
- Red Hat Enterprise Linux 3 Update 3.
Прототип функции VirtualAlloc
Функция VirtualAlloc занимается выделением (allocate) и передачей (commit) виртуальной памяти, с одновременной установкой прав доступа на чтение/запись/выполнение:
Листинг 2. Функция VirtualAlloc и ее прототип
LPVOID VirtualAlloc
(
LPVOID lpAddress,
SIZE_T dwSize,
WORD flAllocationType,
DWORD flProtect
);
Назначение аргументов в функции следующее:
Таблица 2. Аргументы функции VirtualAlloc
Аргумент
|
Значение
|
lpAddress
|
Указатель на передаваемый регион памяти
|
dwSize
|
Размер передаваемого региона
|
flAllocationType
|
Тип запроса, MEM_COMMIT (1000h) – передача памяти
|
flProtect
|
Атрибуты защиты, PAGE_EXECUTE_READWRITE (40h) – rwx
|
BufferShield или PaX на NT
Пакет BufferShield – это достойный конкурент штатному DEP, не требующий установки SP2, работающий без аппаратной поддержки со стороны процессора, использующий рандомизацию раскладки адресного пространства и задействующий NX/XDбиты, если они есть. Он значительно превосходит DEP по защищенности и атаковать его очень сложно (тем не менее некоторые лазейки все-таки есть, в частности, из-за отсутствия GOT рандомизация выполняется не полностью, оставляя в памяти значительное количество предсказуемых адресов).
Защищать можно как отдельные приложения (например, пресловутый Internet Explorer), так и определенный диапазон памяти внутри конкретного приложения. Так же поддерживаются и многопроцессорные системы (правда, только для «однополых» процессоров, то есть если один процессор имеет поддержку NX/XD-битов, а другой нет, NX/XD-биты остаются незадействованными для всех процессоров).
Короче говоря, BufferShield реализует те же самые возможности, что и пакет PaX, фактически являясь его портом на NT. Однако в отличие от бесплатного PaX BufferShield распространяется на коммерческой основе, а для ознакомления предлагается только 30-дневная пробная версия: http://www.sys-manage.com/sites/D_BuffShld.html.
Рисунок 14. Окно настройки BufferShiled
Основные свойства продукта:
- распознает выполнение кода на стеке, куче, виртуальной памяти и сегменте данных;
- запрашивает подтверждение на завершение, если обнаружено переполнение буфера;
- ведет отчет обнаруженных переполнений в протоколе событий (Windows event log);
- позволяет включать защиту даже для отдельных частей выбранных приложений (!!!);
- поддерживает NX-бит и задействует его, если доступен (но может работает и без него);
- поддерживает симметричные многопроцессорные системы (SMP) с однополыми ЦП;
- рандомизует раскладку адресного пространства (технология ASLR).
Глоссарий
- ASLR (Address Space Layout Randomization – рандомизация раскладки адресного пространства): мощная технология защиты против shell-кода, основанная на непредсказуемом изменении адресов системных функций, расположения стека, используется в пакете PaX и Exec Shield, но только не в DEP: http://en.wikipedia.org/wiki/ASLR.
- DEP (Data Execution Prevention – предотвращение выполнения данных): совокупность программных и аппаратных технологий, призванных предотвратить выполнение shell-кода, но в действительности с этим не справляющихся. DEP может быть как аппаратным (hardware-enforced), так и программным (software-enforced). Аппаратный DEP фактически сводится к поддержке NX/XD-бита, программный представляет собой SafeSEH.
- EVP (Enhanced Virus Protection – расширенная вирусная защита): маркетинговый трюк AMD, выдающий XD-бит за «технологию защиты против вирусов».
- PAE (Physical Address Extension – режим расширения физических адресов): специальный режим 32-разрядных x86-процессоров, в котором они могут адресовать больше памяти и использовать дополнительные возможности (в частности, атрибут NX);
- PTE (Page Table Entry – элемент каталога страниц): структура данных, описывающая свойства «своей» страницы виртуальной памяти и задающая права доступа.
- NX (Not executable – не исполняемый): AMD64: специальный бит в PTE, разрешающий/запрещающий выполнение машинного кода в данной странице.
- XD (execution Disabled – исполнение запрещено): Intel: специальный бит в PTE, разрешающий/запрещающий выполнение машинного кода в данной странице.
- SafeSEH (безопасный SEH): набор защитных мер, разработанных Microsoft для предотвращения использования обработчика структурных исключений (SEH) shell-кодом, позднее был переименован в software-enforced DEP.
Ссылки:
- PaX (лучший бесплатный проектор для LINUX/BSD): http://www.ngsec.com/ngproducts/stackdefender.
- BufferShield 1.01k (коммерческий порт PaX на NT): http://www.sys-manage.com/index10.htm.
- Stackdefender (еще один порт PaX на NT): http://www.ngsec.com/ngproducts/stackdefender.
- Exec Shield (усеченный клон PaX для In Red Hat Enterprise Linux v.3, update 3): http://people.redhat.com/mingo/exec-shield.
- W^X (простой и бесполезный защитный пакет для OpenBSD): http://marc.theaimsgroup.com/?l=openbsd-announce&m=105175475006905&w=2.
- http://support.microsoft.com/kb/875352/ru; http://support.microsoft.com/kb/875352.
- AMD64 Enhanced Virus Protection: официальная презентация «EVP-технологии» от компании AMD. Куча роликов и никакой технической информации (на английском языке): http://www.amd.com/us-en/Weblets/0,,7832_11104_11105,00.html.
- Execute Disable Bit Functionality Blocks Malware Code Execution: детальная техническая информация от Intel, касающаяся XD-бита, ориентированная на системных программистов (на английском языке): http://cache-www.intel.com/cd/00/00/14/93/149307_149307.pdf.
- Processor Number Feature Table: перечень процессоров от Intel, поддерживающих XDбит (на английском языке): http://support.intel.com/products/processor_number/proc_info_table072505.pdf.
- nx: how well does it say NO to attacker’s eXecution Attempts: презентация с Black Hat, раскрывающая принципы работы механизма DEP и перечисляющая его основные уязвимости (на английском языке): http://www.blackhat.com/presentations/bh-usa-05/bh-us-05-maynor.pdf.
- Buffer overflow attacks bypassing DEP (NX/XD bits) – part 2 : Code injection: обход DEP, основанный на выделении региона памяти посредством вызова VirtualAlloc с последующим копированием shell-кода (на английском языке): http://www.mastropaolo.com/?p=13.
- Defeating Microsoft Windows XP SP2 Heap protection and DEP bypass: обход DEP, основанный на переполнении кучи с последующей подменой адреса возврата из функции main на функцию crt!system (на английском языке): http://www.maxpatrol.com/defeating-xpsp2-heap-protection.htm.
- Windows Heap Overflows: презентация с Black Hat, описывающая общие принципы переполнения кучи под NT (на английском языке): http://www.blackhat.com/presentations/win-usa-04/bh-win-04-litchfield/bh-win-04-litchfield.ppt.
- DEP evasion technique: замечательный блог, демонстрирующий технику обхода DEP и сравнивающий его с аналогичными защитными механизмами из UNIX (на английском языке): http://woct-blog.blogspot.com/2005/01/review-of-microsofts-dep.html; http://woct-blog.blogspot.com/2005/01/dep-evasion-technique.html.
- Касперски К. Ошибки переполнения буфера извне и изнутри как обобщенный опыт реальных атак. – Журнал «Системный администратор», №3, март 2004 г. Уже устаревавшая статья, подробно описывающая причины и следствия ошибок переполнения различных типов (http://www.samag.ru/cgi-bin/go.pl?q=articles;n=03.2004;a=07).
- SEH на службе контрреволюции: еще одна моя статья, демонстрирующая технику обхода программного DEP: ftp://nezumi.org.ru/pub/zq-buf-SEH.zip.
- eWEEK.com Special Report: Windows XP Service Pack 2: Microsoft подтверждает возможность обхода DEP (на английском языке): http://www.eweek.com/article2/0,1759,1757786,00.asp.