В лагере UNIX
Ошибки переполнения не являются "собственностью" Windows. Это общая проблема для всех Си/Си++ программ, родиной которых был и остается UNIX. Уязвимые приложения в изобилии встречаются и там. С ними активно борются путем хитроумных извращений и оригинальных технических решений, о которых мы и собираемся сейчас рассказать.
Начнем с того, что существуют процессорных архитектуры (SPARC, SPARC64, Alpha, HPPA), на которых UNIX имеет неисполняемый стек еще от рождения. Существуют архитектуры, использующие раздельные стеки для хранения адресов возврата и локальных переменных. Подменить адрес возврата на них невозможно, но легко затереть указатель на функцию, при вызове которой управление получит shell-код (правда, передача аргументов представляет собой большую проблему, особенно если они передаться через регистры, тем не менее атаковать такую систему все-таки возможно, пусть и на концептуальном уровне).
На IBM-совместимых машинах LINUX/BSD по умолчанию ведут себя точно так же как XP без SP2. То есть трактуют r-атрибут как -x-, позволяя исполнять код там, где вздумается. Первой ласточкой, ринувшийся на встречу буре, стал патч от Solar'a 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'а и не несет в себе ничего нового. А что еще можно ожидать от Red Hat?
Еще хуже дела обстоят в OpenBSD. Начиная с версии 3.3 ядро поддерживает механизм W^X (произносится как "W xor X"), который своему названию предотвращает одновременную установку атрибутов -x- и -w- на любую область памяти, что по замыслу разработчиков должно серьезно озадачить хакеров (http://marc.theaimsgroup.com/?l=openbsd-announce&m=105175475006905&w=2).
На самом деле, эта защита элементарно обходится множественным вызовом функции mmprotect. Сначала shell-код вызывает mprotect, устанавливая один лишь атрибут записи (если он не был установлен ранее), затем копирует shell-код через memcpy и вызывает mprotect еще раз, сбрасывая атрибут записи и присваивая себе права исполнения. Кстати говоря, в версии 3.3 W^X не работал на x86, поскольку у того отсутствует возможность задания x-атрибута на уровне страниц, однако, начиная с версии 3.4 этот недостаток был исправлен.
Короче говоря, в штатной конфигурации, без установки пакета PaX, все UNIX-подобные системы потенциально уязвимы и допускают выполнение shell-кода по сценарию, описанному в разделе "атаки на DEP".