Большинство современных реализаций компилятора языка Си не генерируют кода, контролирующего целостность границ переменных. Вследствие этого, в Internet можно найти огромное количество «инструментов», использующих это для атак на компьютерные системы и предназначенных для «выколачивания» дополнительных привилегий из неудачно написанного коммерческого и свободного программного обеспечения. К счастью, есть несколько способов повышения иммунитета операционных систем к таким атакам.

На сегодняшний день основным языком разработки программного обеспечения по-прежнему остается Си. Между тем, отсутствие в большинстве компиляторов проверки границ переменных порождает множество лазеек. Зачастую злоумышленнику не составляет особого труда найти, например, уязвимую сетевую службу и, атаковав ее, получить доступ к информации, находящейся в удаленной системе. Методика проведения атак, осуществляемых путем переполнения стековых переменных, подробно описана в [1]. Полностью устранить проблему можно только путем изменения архитектуры существующих инструментальных средств и операционных систем. Это, в свою очередь, потребует отказа от большей части имеющегося программного обеспечения. Отсутствие безопасных аналогов находящихся в эксплуатации программ, делает такой подход нереальным. К счастью, для некоторых ОС появились специальные защитные механизмы, встраиваемые в существующее системное программное обеспечение. Данный подход малоэффективен в борьбе с атаками, вызывающими отказы в обслуживании, однако он позволяет противостоять попыткам несанкционированного доступа к информации. Рассмотрим несколько реализаций подобного подхода для информационных систем, функционирующих под управлением ОС Linux.

Проект Libsafe

Корни многих проблем, связанных с безопасностью Unix, лежат в главной системной библиотеке Libc, ряд функций которой способны вызывать переполнение стековых переменных. Злоумышленники очень часто используют программы, вызывающие эти функции, чтобы получить несанкционированный доступ к информации. Целью разработчиков проекта Libsafe [2] стало создание альтернативных реализаций таких функций и замещение ими опасных элементов Libc. Достоинство Libsafe состоит в том, что для встраивания защиты в систему, не нужно перекомпиляции ни элементов ОС, ни самих приложений. Функции Libsafe находятся в отдельной динамической библиотеке и автоматически подгружаются при запуске приложений. Библиотека реализует следующие функции:

strcpy(char *dest, const char *src)
strpcpy(char *dest, const char *src)
wcscpy(wchar_t *dest,const wchar_t *src)
wcpcpy(wchar_t *dest, const wchar_t *src)
strcat(char *dest, const char *src)
wcscpy(wchar_t *dest, const wchar_t *src)
getwd(char *buf)
gets(char *s)
[vf]scanf(const char *format, ...)
realpath(char *path, char resolved_path[])
[v]sprintf(char *str, const char *format, ...)

Библиотеку Libsafe достаточно просто встроить в любую современную Linux-систему. Для этого в каталоге с ее исходными текстами надо выполнить команды make; make install, а затем создать в каталоге /etc/ файл с именем ld.so.preload, содержащий строку с указанием полного пути к файлу библиотеки. Для проверки работоспособности Libsafe можно использовать тесты из подкаталога exploits. При нормально функционирующей защите запуск тестов должен приводить к появлению сообщений следующего вида:

Libsafe version 2.0.16
Detected an attempt to write across stack
 boundary.
Terminating /home/const/USR_SRC/libsafe-2.0.16/
exploits/exploit-non-exec-stack.
uid=1004  euid=1004  pid=1175
Call stack:
0x400189d0  /opt/libsafe/libsafe.so.2.0.16
0x40018e45  /opt/libsafe/libsafe.so.2.0.16
0x80489a0   /home/const/USR_SRC/libsafe-2.0.16/
exploits/exploit-non-exec-stack
0x40034e39  /lib/libc-2.3.2.so
Overflow caused by memcpy()
Killed

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

Проект Openwall

Встраивание средств защиты в ядро операционной системы обеспечивает контроль на уровне процесса, вне зависимости от того, какие программные модули и библиотеки участвовали в его создании. Участники проекта Openwall [3] работают над созданием собственного дистрибутива Linux, преследуя цель «снижения количества и последствий ошибок в программных компонентах, а также уменьшения возможного вреда от ошибок в стороннем программном обеспечении, которое пользователь может установить на свой компьютер». Это достигается путем тщательной проверки программного обеспечения и применения дополнительных средств защиты, работающих на уровне ядра.

Если переход на дистрибутив Openwall по каким-либо причинам нежелателен, то можно воспользоваться отдельными решениями, способными повысить уровень безопасности любой современной Linux-системы. Сделать это позволит пакет, содержащий набор дополнений для ядра, который доступен в виде отдельного архива (см. www.openwall.com/linux).

Для защиты системы понадобятся еще набор инструментов, необходимых для генерации ядра системы [5], и исходные тексты самого ядра. Убедившись, что в системе имеется все необходимое, следует войти в систему с правами пользователя root, разместить в каталоге /usr/src/ содержимое архива Openwall и соответствующей версии архива исходных текстов ядра системы, а также создать в этом же каталоге символьную ссылку с именем linux, указывающую на каталог с исходными текстами ядра. После этого содержимое каталога /usr/src будет выглядеть так:

myhost:/usr/src# ls -all
total 16
drwxrwsr-x    4 root     src 
         4096 Oct 23 17:09 .
drwxr-xr-x   12 root     root
         4096 Jul  3 16:52 ..
lrwxrwxrwx    1 root     src
         21 Oct 23 17:09 linux
 -> /usr/src/linux-2.4.22
drwxr-xr-x   15 573      573
          4096 Aug 25 17:44 linux-2.4.22
drwx---    3 root     root 
        4096 Aug 28 10:25 linux-2.4.22-ow1
myhost:/usr/src#

Скопируем файл /usr/src/linux-2.4.22-ow1/linux-2.4.22-ow1.diff в каталог /usr/src/linux. Перейдем в каталог /usr/src/linux и применим diff-файл к исходным текстам ядра:

myhost:/usr/src/linux# patch -p1
 

Убедимся, что patch отработал правильно, и в дереве исходных текстов не появились файлы с расширением .rej:

myhost:/usr/src# find ./ -name *.rej
myhost:/usr/src#

Появление файлов с расширением rej, скорее всего, укажет на несоответствие версий исходных текстов ядра системы и файла-заплаты. Если все произошло без ошибок, то можно приступить к настройке параметров. Процесс построения ядра из исходных текстов подробно описан в [5]; поэтому остановимся лишь на моментах, связанных с проектом Openwall. После применения заплаты в настройках ядра появляются опции, отвечающие за активизацию различного рода проверок. Запустим процесс настройки параметров ядра системы командой make menuconfig. Выбрав с помощью клавиш управления курсором пункт меню Security options, посмотрим, какие механизмы защиты предлагаются:

Security options -->
[*] Non-executable user stack area
[*] Autodetect and emulate GCC trampolines
[*] Restricted links in /tmp
[*] Restricted FIFOs in /tmp
[*] Restricted /proc
[*] Enforce RLIMIT_NPROC on execve(2)
[ ] Destroy shared memory segments not in use

Опция Non-executable user stack area налагает запрет на выполнение кода, находящегося в стековой области пользовательского процесса. Ее активизация приводит к блокированию ядром любых попыток исполнения кода в стеке. Таким образом, осуществляется защита от атак, использующих механизм изменения адресов возврата функций на адреса запуска вредоносного кода, расположенного в стековой области.

Включение запрета на выполнение кода, расположенного в области данных может привести к неработоспособности некоторых программ, использующих механизм так называемых Trampoline-вызовов. Если в вашей системе имеются такие программы, то необходимо включить опцию Autodetect and emulate GCC trampolines. В этом случае работоспособность программ будет восстановлена, но безопасность системы несколько снизится.

Опции Restricted links in /tmp и Restricted FIFOs in /tmp включают дополнительные ограничения при работе с соответствующими элементами файловой системы. Это позволяет противостоять целому классу атак, осуществляемых посредством манипуляций с этими объектами.

Активизация режима Restricted /proc приводит к ограничению доступа к информации, находящейся в каталоге /proc. При этом рядовым пользователям становится недоступной некоторая системная информация и информация по чужим для них процессам.

В ОС Linux есть системный запрос setrlimit, позволяющий ограничивать число процессов, создаваемых пользователем. К сожалению, это ограничение работает только в том случае, если порождение очередного процесса происходит посредством запроса fork. Включение опции Enforce RLIMIT_NPROC on execve(2) позволяет распространить действие проверок и на системный вызов execve.

В Linux также имеются ограничения на объем памяти, доступный выполняемому процессу. К сожалению, сегменты разделяемой памяти могут существовать независимо от процессов, их использующих, что позволяет обойти соответствующие проверки. Включение опции Destroy shared memory segments not in use вызывает уничтожение этих сегментов, после того как они становятся невостребованными. Активизация данного режима может привести к неработоспособности некоторых приложений. По этой причине авторы проекта не рекомендуют его использовать без особой нужды.

Активизируем необходимые опции, сохраним конфигурацию и выполним генерацию ядра системы. Произведем его установку. При этом не следует удалять старое ядро: в случае неудачи это позволит вернуться к работоспособной конфигурации.

Если все сделано правильно, то загрузка нового ядра системы должна пройти без особых проблем. Возможно, что при загрузке операционной системы перестанут запускаться некоторые программы, но эту проблему мы разрешим чуть позже. Пока же убедимся, что ядро работает. Для этого воспользуемся утилитой stacktest, входящей в состав пакета openwall. При нормальной работе ядра вызов утилиты с ключом -e и -b должен приводить к снятию программы с выполнения и появлению соответствующего сообщения в системном журнале.

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

Проект PaX

Целью проекта PaX [4] является также создание механизма, позволяющего противостоять атакам, основанным на различных способах перехвата управления у нормально исполняющегося процесса. Перечень защит, предоставляемых PaX-ядром, значительно отличается от аналогичного перечня Openwall-ядра. PaX предоставляет более широкий выбор механизмов, реализующих контроль попыток вживления вредоносного кода в исполняющийся процесс, исполнения существующего программного кода в порядке, не предусмотренном изначальным алгоритмом программы и обработку процессом некорректных данных, поступающих извне. Вместе с тем, в нем нет механизмов, ограничивающих доступ к символьным ссылкам, и не осуществляется контроль ситуаций, связанных с перерасходом системных ресурсов, выделенных процессу.

Процесс изготовления PaX-ядра аналогичен процессу изготовления ядра с элементами защиты Openwall. Для экспериментов с PaX понадобятся заплата к ядру системы, комплект тестов paxtests и утилита chpax. Все перечисленные компоненты системы PaX можно найти по адресу http://pageexec.virtualave.net. Применим заплату к исходным текстам стандартного Linux-ядра соответствующей версии и приступим к процессу конфигурации. На этот раз наше внимание будет сосредоточено на группе опций из раздела PaX options. Проанализируем возможности, предоставляемые подсистемой PaX, и активизируем защиту:

PaX options -->
[*] Enforce non-executable pages
[*] Paging based non-executable pages (NEW)
[*] Segmentation based non-executable pages (NEW)
[ ]   Emulate trampolines (NEW)
[*]   Restrict mprotect() (NEW)
[*]     Disallow ELF text relocations (NEW)
[*] Address Space Layout Randomization
[ ]   Randomize kernel stack base
[*]   Randomize user stack base
[*]   Randomize mmap() base

Блок опций Enforce non-executable pages отвечает за ограничения, вводимые ядром системы при выполнении программного кода. Включение опции Paging based non-executable page запрещает выполнение программного кода, расположенного в страницах памяти, содержащих данные. Авторы проекта предупреждают, что активизация этой опции на системах архитектуры i386 может привести к значительному снижению быстродействия; на платформах Alpha, SPARC и SPARC64 не произойдет снижения производительности. Для того чтобы потеря производительности была незначительной, можно выбрать опцию Segmentation based non-executable pages. В этом случае, правда, объем адресуемой памяти уменьшится вдвое, до 1,5 Гбайт.

Активизация опции Emulate trampolines снимает проблемы, возникающие при исполнении программ, использующих соответствующие вызовы.

Включение опции Restrict mprotect() запрещает изменение статуса страниц памяти на executable («выполняемый»), если он не был таковым изначально. Активизация этого режима приводит к неработоспособности многих приложений (в том числе, и приложений X Window).

Опция Disallow ELF text relocations (NEW) обеспечивает дополнительную защиту от несанкционированных действий, выполняемых посредством вызова mmap. Активизировать эту опцию можно только в тех системах, где все программы собраны с PIC-версией библиотеки crt1; в противном случае система может стать незагружаемой.

Режимы из блока Address Space Layout Randomization вносят элемент случайности при распределении системой адресного пространства. Дело в том, что большинство методик осуществления вторжений требуют знания определенных адресов внутри атакуемого процесса. В системе, управляемой классическим ядром, эти адреса распределяются стандартным образом и могут быть легко предсказаны. Введение же в процесс распределения адресного пространства элемента случайности значительно усложняет процедуру передачи управления вредоносному коду.

При включении опции Randomize kernel stack base ядро при каждом системном запросе будет производить перераспределение области стековой памяти ядра. Разработчики PaX предупреждают, что включение этой опции может повлечь за собой нестабильность в работе ОС, поэтому пользоваться ей нужно крайне осторожно.

Активизация режима Randomize user stack base включает механизм случайного распределения памяти под стековую область пользовательского процесса.

Включение опции Randomize mmap() base приведет к тому, что ядро будет назначать случайным образом базовый адрес запроса mmap, в результате чего все динамические библиотеки будут загружаться в память по произвольному адресу. Это значительно затруднит успешное проведение атак, в которых злоумышленник пытается выполнить существующий библиотечный код для своих целей, например, инициировать вызов shell из атакованной программы.

Произведем настройку основных параметров ядра ОС, сохраним конфигурацию и вновь выполним процесс генерации. Проинсталлируем новое ядро, выполним перезагрузку и убедимся в его работоспособности. На этот раз воспользуемся комплектом тестов paxtest, входящим в состав дистрибутива PaX. Выполним в подкаталоге с тестами операцию make и запустим файл с именем paxtest. Проанализируем отчет, сформированный тестами:

myhost:/usr/src/paxtest-0.9.1# ./paxtest
It may take a while for the tests to complete
Test results:
Executable anonymous mapping   : Killed
Executable bss                        : Killed
Executable data                       : Killed
Executable heap                       : Killed
Executable stack                      : Killed
Executable anonymous mapping (mprotect): Killed
Executable bss (mprotect)             : Killed
Executable data (mprotect)            : Killed
Executable heap (mprotect)            : Killed
Executable shared library bss (mprotect): Killed
Executable shared library data (mprotect): Killed
Executable stack (mprotect)           : Killed
Anonymous mapping randomisation test  : 16 bits
 (guessed)
Heap randomisation test (ET_EXEC)     : 13 bits
 (guessed)
Heap randomisation test (ET_DYN)      : 25 bits
 (guessed)
Main executable randomisation (ET_EXEC): 16 bits
 (guessed)
Main executable randomisation (ET_DYN): 17 bits
 (guessed)
Shared library randomisation test     : 16 bits
 (guessed)
Stack randomisation test (SEGMEXEC)   : 23 bits
 (guessed)
Stack randomisation test (PAGEEXEC)   : 24 bits
 (guessed)
Return to function (strcpy)        : Vulnerable
Return to function (memcpy)        : Vulnerable
Executable shared library bss          : Killed
Executable shared library data         : Killed
Writable text segments                 : Killed
myhost:/usr/src/paxtest-0.9.1#

Строки отчета, содержащие сообщение Killed, свидетельствуют о работоспособности соответствующих механизмов защиты. Сообщение Vulnerable сигнализирует о наличии уязвимости. В нашем примере таких сообщения два. Их порождают тесты, реализующие так называемую атаку типа «возврат в стиле Libc». Технология проведения таких атак не требует внедрения в систему инородного кода. Авторы проекта признают отсутствие в текущей реализации PaX соответствующего механизма защиты, объясняя это рядом технических проблем.

Управление защитой на уровне отдельных программ осуществляется при помощи утилиты chpax.

Резюме

Наиболее эффективную защиту обеспечивают средства, функционирующие на уровне ядра системы. Однако они, как правило, плохо интегрируются между собой. Это затрудняет использование в рамках одной системы защитных механизмов, взятых из разных проектов. Кроме того, даже применение таких средств не сможет обеспечить 100-процентной защиты от взлома. Тем не менее, их применение оправдано по целому ряду причин. Прежде всего, серьезно затрудняется нелегальное проникновение в систему и ее инфицирование. Кроме того, появление в системном журнале записей от соответствующих средств защиты облегчает администратору обнаружение как «неблагонадежного» программного обеспечения, так и следов попыток вторжений злоумышленников.

Литература
  1. Smashing The Stack For Fun And Profit, www.opennet.ru/base/sec/p49-14.txt.html
  2. Libsafe, www.research.avayalabs.com/project/libsafe/
  3. Openwall, www.openwall.com
  4. PaX, pageexec.virtualave.net/docs/pax.txt
  5. Linux Kernel HOWTO, www.opennet.ru/docs/HOWTO-RU/Kernel-HOWTO.html

Константин Тайтуров (2239@mail.ur.ru) — независимый эксперт (Екатеринбург).