Root поселение на Unix тачке

Xakep, номер #049, стр. 049-086-1


Разработка своего lkm-руткита под FreeBSD и Linux

maravan (maravan@real.xakep.ru)

В последнее время довольно популярными стали lkm-руткиты. Аббревиатура lkm расшифровывается как loadable kernel module. Как следует из названия, lkm - это программа, предназначенная для подгрузки в ядро операционной системы. Почему использование модулей ядра настолько популярно при написании руткитов? Ответ на этот вопрос не вызывает сомнений: получив возможность работать напрямую с ядром, можно делать с системой фактически все, что заблагорассудится. Это и полный контроль надо всем, что происходит в системе, и новые возможности, недоступные для "обычного" софта, работающего вне ядра, и многое другое. Следовательно, грамотно написанный lkm-руткит после своего подгружения в ядро может поставить администратора системы в тупик. Единственным способом избавления от руткита будет полная переустановка системы.

Как это делается

По своей структуре и по способу загрузки lkm очень напоминает обычную библиотеку. Когда юзерский процесс инициирует процесс загрузки модуля в ядро, т.е. "сообщает" ядру посредством некоторого системного вызова желание подгрузить код этого лкм (lkm), ядро мапит код и данные модуля в свое адресное пространство, ищет в таблице символов модуля точку входа и передает на нее управление. В принципе, все. Вот что представляет собой простейший модуль для FreeBSD, который не выполняет никаких операций, а просто подгружается в ядро и сидит там, пока мы его не выгрузим:

/* headers skipped */

static moduledata_t dummy_module = {"dummy_module", module_ops, NULL};

static struct sysinit sys_init =

{

SI_SUB_DRIVERS,

SI_ORDER_MIDDLE,

module_register_init,

&dummy_module

};

static int module_ops (struct module *module, int cmd, void *args)

{ switch (cmd)

{

case MOD_LOAD:

break;

case MOD_UNLOAD:

break;

default:

return (EINVAL);

}

return (0);

}

static void const * const

__set_sysinit_set_sym_syscall_sys_init = &sys_init;

__asm(".section .set.""sysinit_set"",\"aw\"");

__asm(".long " "sys_init");

__asm(".previous");

Небольшое пояснение:

module_ops() - это своего рода функция main() нашего модуля. На нее передается управление при загрузке модуля в ядро, а также при его выгрузке. При загрузке ей передается параметр cmd, имеющий значение MOD_LOAD, а при выгрузке, соответственно, MOD_UNLOAD.

sys_init - структура, при помощи которой ядро и ведет свой "диалог" с модулем. На данный момент единственным интересным для нас элементом этой структуры является поле udata, в котором содержится указатель на структуру dummy_module.

dummy_module - структура, описывающая наш модуль. В ней содержится непосредственно название модуля ("dummy_module"), указатель на module_ops и указатель на дополнительные данные о нашем модуле, имеющий значение NULL.

А вот то же самое, но только для линукса:

/* headers skipped */

int init_module (void)

{

return (0);

}

void cleanup_module (void)

{

return;

}

Структура модуля в линуксе гораздо проще, чем во Free BSD - здесь есть только init_module(), который запускается при загрузке модуля, и cleanup_module, запускающийся при его удалении из ядра.

Содержание  Вперед на стр. 049-086-2