Перехват системных вызовов
Помните MS-DOS? Там стелстирование осуществлялось путем подмены прерываний int13h/int 21h. В LINUX для той же цели используется перехват системных вызовов (system call или сокращенно syscall). Для сокрытия процессов и файлов достаточно перехватить всего один низ них — getdents, на которую опирается всем известная readdir, которая, в полном согласии со своим именем, читает содержимое директорий (и директории /proc в том числе! Другого легального способа просмотра списка процессов под LINUX в общем-то и нет). Функция-перехватчик садится поверх getdents и просматривает возращенный ею результат, выкусывая из него все "лишнее", то есть работает как фильтр.
Сетевые соединения стелсируется аналогичным образом (они монтируются на /proc/net). Чтобы замаскировать сниффер, необходимо перехватить системный вызов ioctl, подавляя PROMISC-флаг. А перехват системного вызова get_kernel_symbols позволяет замаскировать LKM-модуль так, что его никто не найдет.
Звучит заманчиво. Остается только реализовать это на практике. Ядро экспортирует переменную extern void sys_call_table, содержащую массив указателей на syscall'ы, каждая ячейка которого содержит либо действительный указатель на соответствующий syscall, либо NULL, свидетельствующий о том, что данный системный вызов не реализован.
Просто объявите в своем модуле переменную *sys_call_table[] и тогда все системные вызовы окажутся в ваших руках. Имена известных syscall'ов перечислены в файле /usr/include/sys/syscall.h. В частности, sys_call_table[SYS_getdents]
возвращает указатель на getdents.
Простейший пример перехвата выглядит так (за более подробной информацией обращайтесь к статье "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;
}
Листинг 7 техника перехвата системных вызовов
По такому принципу работает подавляющее большинство rootkit'ов, правда, попав на неизвестное ядро, часть из них со страшным грохотом падает, а часть просто прекращает работу, что и не удивительно! Ведь раскладка системных вызовов меняется от ядра к ядру!
Рисунок 5 последствия неудачного перехвата системных вызовов