Доклады, выступления, видео и электронные публикации

Linux: объекты контроля целостности

Каннер А.М.
Россия, Москва, ЗАО «ОКБ САПР»

С учетом сложившейся в последнее время тенденции к широкому распространению ОС семейства GNU Linux (программы внедрения «свободного» ПО в учебных и государственных учреждениях РФ, повсеместное встраивание Linux в большое количество портативных электронных устройств) все чаще приходится задумываться о дополнительных мерах защиты данных, которые хранятся и обрабатываются в среде ОС Linux.

Разнообразные дистрибутивы Linux «из коробки» предоставляют достаточно широкий выбор штатных средств защиты пользовательских данных (такие как средства защиты межсетевого взаимодействия, стандартные и расширенные средства идентификации и аутентификации, права доступа к объектам файловых систем, дополнительные списки контроля доступа - Access Control Lists и расширенные атрибуты файловых объектов, разнообразные фреймворки для расширенных настроек безопасности - SELinux, Tomoyo, AppArmor и т. д.). В целом можно сказать, что при правильной настройке таких штатных средств защиты ОС семейства Linux являются в достаточной степени защищенными. Однако, это является истиной только в случае, когда не учитывается возможность неправомерного физического доступа некоторого злоумышленника к самому носителю данных или СВТ (вопрос защиты от физической кражи в данной статье освещаться не будет - это необходимо обеспечивать соответствующими организационными мерами). При таком доступе ничто не может помешать, например, изменить правила работы штатных средств защиты или вмешаться в штатный режим загрузки ОС.

Вследствие этого факта, а также опираясь на следствие из теоремы об использовании резидентного компонента безопасности (РКБ) [1], для «полноценной» защиты от несанкционированного доступа (НСД) в ОС семейства Linux (вообще говоря, это же справедливо для любых ОС) должен существовать некоторый внешний (по отношению к СВТ) аппаратный компонент, который и будет обеспечивать «полноценность» работы штатных или дополнительных программных механизмов защиты. В качестве такого аппаратного компонента принято использовать аппаратные модули доверенной загрузки (АМДЗ), которые применительно к ОС Linux должны контролировать целостность как минимум следующих компонент:

  1. главная загрузочная запись (Master Boot Record, MBR)
  2. загрузочный сектор основного раздела ОС Linux (Partition Boot Record, PBR), если загрузчик ОС при установке был записан в PBR, а не в MBR
  3. сектора 1-63 относительно начала загрузочного раздела (некоторые загрузчики, например GRUB, записывают сюда свою часть - grub stage 1.5)
  4. непосредственно сам загрузчик (в случае GRUB - /boot/grub/stage2)
  5. файлы конфигурации загрузчика (/boot/grub/grub.conf, возможно ссылку /boot/grub/menu.lst)
  6. ядро ОС Linux - /boot/vmlinuz-*.*.*-xxx (в различных дистрибутивах наименование может отличаться)
  7. initramfs-образ или образ начальной загрузки - /boot/initrd-*.*.*-xxx.img (в различных дистрибутивах наименование может отличаться)
  8. какие-то файлы конфигурации и критичные данные ОС (настройки СЗИ НСД и т. д.).

Этот список устоявшийся и его было принято считать достаточным для корректной защиты ОС Linux и в дальнейшем программных средств защиты от НСД, начиная с раннего этапа загрузки.

Так или иначе, ядро Linux достаточно давно предоставляет возможность динамически загружать дополнительный функционал (выполняемый в kernel mode) с помощью загружаемых модулей ядра - Loadable Kernel Modules (LKM). При этом, если против загрузки в ОС «несанкционированных» модулей еще есть средства защиты (начиная от прав доступа в ОС), то для контроля целостности самих модулей (например, стандартных модулей для поддержки работы ЖД и т.д.) как правило никаких средств не используется. Также нужно понимать, что такие модули ядра могут загружаться на раннем этапе загрузки ОС - т.е. до начала работы многих средств защиты информации (при этом само действие по загрузке модуля ядра несколько отличается от запуска на выполнение бинарного файла, при котором обычно применяют динамический контроль целостности[1]). Рассмотрим, чем может быть опасно нарушение целостности модулей ядра ОС Linux.

Инъекция кода в модули ядра Linux

Опасность кроется в том, что существует возможность внедрить некоторый произвольный код в какой-то стандатный модуль ядра (который заведомо загружается по умолчанию), а также обеспечить повторную загрузку этого кода даже после перезагрузки ОС. Причем нужно отметить, что данная возможность существует скорее не за счет какой-то уязвимости в механизме LKM, а за счет особенностей используемого в Linux формата бинарных данных ELF (Executable and Linking Format). Особенность данного формата, которая будет нас сейчас интересовать, заключается в том, что:

  1. в процессе линковки/компоновки (linking, т.е. процесса преобразования одного или нескольких скомпилированных объектных файлов в один исполняемый) двух объектных файлов ELF-формата линковщику (linker) необходимо получить некоторую информацию об используемых обоими файлами символах (symbols) - например, об используемых функциях или глобальных переменных;
  2. каждый файл ELF-формата содержит в себе 2 секции, содержащие информацию для описания символов - .symtab и .strtab;

Далее необходимо привести краткое описание секций .symtab и .strtab, которое незаинтересованный читатель может пропустить и продолжить читать следующий абзац с сутью использования нами этих секций. В секции .symtab содержатся структуры с данными для линковщика по всем используемым символам ELF-файла (в частном случае - функциям). В каждой структуре есть поле st_name, которое представляет из себя индекс символа в секции .strtab. По указанному индексу в .strtab содержится null-terminated строка - фактически имя символа, при этом смещение секции .strtab определяется механизмом Section name resolution mechanism [2] - т. е. фактически смещение известно (можно определить).

Справочные материалы выше предназначены лишь для того, чтобы понимать - в ELF-формате любой символ можно найти по имени (и, если нужно, каким-либо образом изменить, однако, например, измененное имя по-хорошему должно содержать то же количество символов). Что вообще это может дать в нашем случае применительно к модулям ядра Linux? По сути нам становится доступным изменение точки входа модуля ядра и/или выполнение некоторой внешней по отношению к модулю функции до его основного функционала (или если нужно - после).

Не будем вдаваться в глубокое описание того, как именно происходит загрузка модулей ядра в ОС Linux (и в то, что в ходе этой загрузки происходит обращение к символам из формата ELF), для более глубокого изучения читатель может воспользоваться соответствующими материалами [3]. Продемонстрируем непосредственно пример возможности инъекции кода. Для этого нужно создать два модуля ядра:

Тестовый модуль ядра

Инъектируемый модуль

origmod.c:

evilmod.c:

#include <linux/module.h>

int init(void) {

printk(KERN_ALERT "original init\n");

return 0;

}

void clean(void) {

printk(KERN_ALERT "original exit\n");

return;

}

MODULE_LICENSE("GPL");

module_init(init);

module_exit(clean);

#include <linux/module.h>

/** init() wrapper */

extern int init();

int evil(void) {

printk(KERN_ALERT "evil inject!");

/**

* here should be injected code

*/

/** here we can call init() function of the original module */

init();

return 0;

}

MODULE_LICENSE("GPL");

Makefile:

Makefile:

SRC := origmod.c

KERNEL_SOURCE_DIR := /lib/modules/$(shell uname -r)/build

obj-m := origmod.o

all: ${SRC} ${HEADERS} Makefile

${MAKE} modules -C ${KERNEL_SOURCE_DIR} M=${shell pwd}

clean:

-rm -f *.ko *.o

-rm -f *.mod.* .*.cmd

-rm -rf .tmp_versions

-rm -f Module.symvers Module.markers modules.order

.PHONY: all clean cscope tags

.DELETE_ON_ERROR:

SRC := evilmod.c

KERNEL_SOURCE_DIR := /lib/modules/$(shell uname -r)/build

obj-m := evilmod.o

all: ${SRC} ${HEADERS} Makefile

${MAKE} modules -C ${KERNEL_SOURCE_DIR} M=${shell pwd}

clean:

-rm -f *.ko *.o

-rm -f *.mod.* .*.cmd

-rm -rf .tmp_versions

-rm -f Module.symvers Module.markers modules.order

.PHONY: all clean cscope tags

.DELETE_ON_ERROR:

В результате сборки у нас получится модуль origmod.ko (будем считать его исходным), который при загрузке будет выводить в dmesg строку «original init» (module_init определяет «точку входа» в init()), а при выгрузке - «original exit» (module_exit -> clean()). Модуль evilmod.ko (инъектируемый модуль) содержит функцию evil(), которая выводит сообщение «evil injected», а затем вызывает init() (сам модуль содержит символ init(), который не определен, поэтому отдельно загрузиться не сможет).

Рассмотрим полученные модули origmod.ko и evilmod.ko с точки зрения формата ELF, выполнив $ objdump -t origmod.ko и $ objdump -t evilmod.ko:

Тестовый модуль ядра

Инъектируемый модуль

origmod.ko: file format elf32-i386

SYMBOL TABLE:

..................

00000011 g F .text 00000010 cleanup_module

00000000 g F .text 00000011 init_module

00000011 g F .text 00000010 clean

00000000 *UND* 00000000 printk

00000000 g F .text 00000011 init

evilmod.ko: file format elf32-i386

SYMBOL TABLE:

..................

00000000 g F .text 00000016 evil

00000000 *UND* 00000000 printk

00000000 *UND* 00000000 init

Теперь воспользуемся возможностью ld и слинкуем оба модуля в модуль evilorigmod.ko ($ ld -r origmod.ko evilmod.ko -o evilorigmod.ko), при этом таблицы символов будут дополнены недостающими символами из evilmod.ko и origmod.ko (важно, чтобы в модулях не было повторяющихся символов). Опция -r позволяет создать модуль того же формата, что и линкующиеся модули, поэтому модуль evilorigmod.ko будет содержать в себе функции двух модулей, из которых слинкован, и при этом останется загружаемым модулем ядра - его можно загрузить в ядро ОС. Полученный модуль evilorigmod.ko будет иметь следующие символы:

evilorigmod.ko: file format elf32-i386

SYMBOL TABLE:

..................

00000024 g F .text 00000016 evil

00000000 g O .gnu.linkonce.this_module 00000154 __this_module

00000011 g F .text 00000010 cleanup_module

00000000 g F .text 00000011 init_module

00000011 g F .text 00000010 clean

00000000 *UND* 00000000 printk

00000000 g F .text 00000011 init

Теперь, можно изменить «точку входа» в модуль init_module (инструментарий доступен в [3]), при этом таблица символов должна принять следующий вид:

evilorigmod.ko: file format elf32-i386

SYMBOL TABLE:

..................

00000024 g F .text 00000016 evil

00000000 g O .gnu.linkonce.this_module 00000154 __this_module

00000011 g F .text 00000010 cleanup_module

00000024 g F .text 00000011 init_module

00000011 g F .text 00000010 clean

00000000 *UND* 00000000 printk

00000000 g F .text 00000011 init

При загрузке полученного модуля evilorigmod.ko, будет выведено следующее:

[20436.323297] evil inject!

[20436.323298] original init

Таким образом, мы добились полной работоспособности модуля при добавлении внешнего функционала. Необходимо также отметить, что аналогичным образом можно инъектировать код в процедуру выгрузки модуля clean() - т.е. фактически мы можем полностью инъектировать один модуль ядра в другой.

Аналогичным образом можно инъектировать некоторый модуль в какой-либо стандартный модуль ядра, загружаемый ОС по умолчанию (usbhid.ko, pcspkr.ko и пр., исходные коды модулей иметь не обязательно), при этом контрольные суммы такого модуля естественно будут изменены. Однако, таким образом можно обеспечить повторное выполнение инъектируемого кода после перезагрузки ОС, при котором в системе не наблюдается каких-либо подозрительных модулей (что сразу бы насторожило администратора). При этом различные системы обнаружения вторжений (HIDS, Host Intrusion Detection System) со стандартными настройками не обнаружат подмены, т.к. модули ядра не являются ни исполняемыми модулями, ни suid-файлами.

Описанная выше ситуация с инъектированием кода в модули ядра также характерна для ОС Solaris, OpenBSD, FreeBSD, NetBSD (с несущественными изменениями).

Выводы

Описанная в статье техника внедрения постороннего кода в модули ядра достаточно опасна в т.ч. и для современных систем Linux с ядром ветки 3.* (а также для других Unix-подобных ОС). Такое внедрение позволяет, во-первых, выполнять свой код на уровне ядра ОС, а во-вторых, внедрять этот код не слишком заметным способом. Так или иначе указанные возможности уже предполагают, что в системе есть права суперпользователя (иначе невозможно осуществить выгрузку/загрузку модуля ядра, подменить его в /lib/modules/... или в образе начальной загрузки ОС initrd), однако в любом случае лучше предпринять какие-то защитные меры.

В первую очередь желательно контролировать целостность тех модулей ядра, которые автоматически загружаются при загрузке ОС. К сожалению, размер всех модулей на современных ОС Linux (каталог /lib/modules) уже «из коробки» может быть порядка 90-100Мб и более, поэтому простой статический контроль целостности в данном случае будет выполняться непозволительно долго. Есть вариант дополнить динамический контроль целостности - проводить его не только для бинарных файлов при выполнении execve(), а для любых файлов вне зависимости от операции. Данный вариант дополнительно предоставляет возможность провести контроль целостности не только самих бинарных файлов, но и всех используемых ими разделяемых библиотек (/usr/lib, /lib).

Во-вторых, желательно использовать подсистему разграничения доступа, которая бы могла перехватывать и анализировать легитимность загрузки модулей ядра (т.е. перехватывать системный вызов sys_init_module и т. д.).

Оба этих подхода планируется развивать в рамках продукта компании ОКБ САПР - ПАК СЗИ НСД «Аккорд-Х».

Литература

1. Конявский В. А. Управление защитой информации на базе СЗИ НСД «Аккорд». М.: Радио и связь, 1999. С. 50.

2. Tool interface specification on ELF // TIS Committee, 1995

3. Infecting loadable kernel modules: kernel versions 2.6.x\3.0.x // Phrack (Volume 0x0e, Issue 0x44, Phile #0x0b of 0x13), http://www.phrack.org/issues.html?issue=68&amp;id=11#article


[1] этот
механизм в «классической» реализации
применяется непосредственно перед запуском на выполнение бинарных файлов

Автор: Каннер А. М.

Дата публикации: 01.01.2013

Библиографическая ссылка: Каннер А. М. Linux: объекты контроля целостности // Комплексная защита информации. Электроника инфо. Материалы XVIII Международной конференции 21–24 мая 2013 года, Брест (Республика Беларусь). 2013. С. 123–126.

Scientia potestas est
Кнопка связи