eBPF как следующая эра в Linux Rootkit-разработке: механизмы, детектирование и активная нейтрализация
Дата публикации: 19 октября 2025 г.
1. Введение
С момента появления eBPF (Extended Berkeley Packet Filter) в ядре Linux версии 3.18 технология позиционировалась как инструмент для высокопроизводительного мониторинга, сетевой фильтрации и профилирования системных вызовов.
Однако с точки зрения безопасности eBPF представляет собой интерпретатор байткода в пространстве ядра, что превращает его в уникальный механизм внедрения и перехвата на уровне kernel-space без загрузки модулей.
В последние годы (2022–2025) были зафиксированы кейсы, где eBPF использовался в целях обхода средств защиты, скрытия сетевой активности и модификации поведения системных вызовов.
Эта статья описывает реальный подход к:
- детектированию вредоносных eBPF-программ
- анализу их поведения
- активной блокировке загрузки до момента исполнения
2. eBPF как руткит-платформа
2.1. Архитектура eBPF
eBPF-программа состоит из набора инструкций (байткод), исполняемого виртуальной машиной в ядре. Код загружается пользователем через системный вызов:
int bpf(int cmd, union bpf_attr *attr, unsigned int size);
Тип cmd = 5 (BPF_PROG_LOAD) отвечает за загрузку исполняемого кода в ядро.
До выполнения программа проходит проверку верификатором ядра, предотвращающим прямое обращение к критическим областям памяти, однако эта проверка не гарантирует отсутствие логических манипуляций или перехватов системных вызовов.
2.2. Потенциал для скрытности
| Традиционный руткит | eBPF-бэкдор |
|---|---|
| Требует загрузки модуля ядра (LKM) | Работает без LKM, через syscall |
Вызывает подозрение в dmesg/lsmod | Незаметен для lsmod, modinfo |
| Требует root-доступ | Может быть внедрён через процесс с CAP_BPF |
| Обнаруживается через Integrity Check | Не оставляет следов в файловой системе |
Таким образом, eBPF становится идеальным механизмом для in-memory руткита, полностью обходящего классические методы детектирования LKM.
3. Вредоносные возможности eBPF
3.1. Перехват системных вызовов
Используя kprobes, злоумышленник может внедрить eBPF-программу, перехватывающую критические вызовы ядра, например:
SEC("kprobe/sys_execve")
int kprobe__sys_execve(struct pt_regs *ctx)
{
char comm[16];
bpf_get_current_comm(&comm, sizeof(comm));
if (comm[0] == 's' && comm[1] == 'h') {
bpf_override_return(ctx, -EPERM);
}
return 0;
}
Эта программа предотвращает запуск sh или bash, создавая иллюзию “зависания” процесса.
Такое вмешательство невозможно детектировать стандартными антивирусными средствами — процесс просто не исполняется.
3.2. Сокрытие сетевой активности
С помощью socket filter eBPF может фильтровать пакеты на уровне ядра, исключая C2-трафик из захвата tcpdump, Wireshark и netstat:
SEC("socket")
int socket_filter(struct __sk_buff *skb)
{
if (skb->remote_port == 4444) return 0; // скрытый C2
return 1;
}
3.3. Подмена системных данных
eBPF может перехватывать чтение из /proc или /sys и подменять значения (например, маскировать процессы):
SEC("kprobe/vfs_read")
int hide_proc(struct pt_regs *ctx) {
char buf[128];
bpf_probe_read(&buf, sizeof(buf), (void *)PT_REGS_PARM2(ctx));
if (bpf_strncmp(buf, "malware", 7) == 0)
bpf_probe_write_user((void *)PT_REGS_PARM2(ctx), "init", 4);
return 0; }
4. Обнаружение eBPF-загрузок (userspace detection)
Наиболее надёжным индикатором является сам факт вызова bpf() с параметром cmd=5.
4.1. AuditD
auditctl -a always,exit -F arch=b64 -S bpf -F a0=5 -F key=bpf_prog_load
4.2. Falco Rule
rule: eBPF Program Loaddesc: Detects eBPF program loading into kernelcondition: evt.type=bpf and evt.arg.cmd=5output: "eBPF load detected: proc=%proc.name pid=%proc.pid user=%user.name"priority: WARNING
Эти методы фиксируют факт загрузки, но не дают информации о содержимом программы.
5. Обнаружение в пространстве ядра (kernel-level detection)
Для глубинного анализа требуется перехват самого вызова bpf() на уровне ядра.
5.1. eBPF-программа для аудита eBPF
SEC("kprobe/sys_bpf")
int kprobe__sys_bpf(struct pt_regs *ctx, int cmd, union bpf_attr *attr)
{
if (cmd == BPF_PROG_LOAD) {
struct event_t evt = {};
evt.pid = bpf_get_current_pid_tgid();
bpf_get_current_comm(&evt.comm, sizeof(evt.comm));
evt.type = attr->prog_type;
evt.cnt = attr->insn_cnt;
bpf_probe_read_str(&evt.name, sizeof(evt.name), attr->prog_name);
events.perf_submit(ctx, &evt, sizeof(evt));
}
return 0;S
}
Этот код извлекает имя программы, её тип (kprobe, tracepoint, xdp, и т.д.), количество инструкций и отправляет эти данные в пространство пользователя через perf-канал.
6. Поведенческий анализ eBPF-инструкций
Каждая инструкция представлена структурой:
struct bpf_insn {
__u8 code;
__u8 dst_reg:4;
__u8 src_reg:4;
__s16 off;
__s32 imm;
};
Ключевой индикатор вредоносности — наличие вызовов helper-функций с повышенными привилегиями:
| Helper | Описание | Уровень риска |
|---|---|---|
| bpf_override_returnS | Изменение результата системного вызова | 🔴 |
| bpf_probe_write_user | Запись в память пользователя | 🔴 |
| bpf_copy_from_user | Незаметное чтение из памяти пользователя | 🟠 |
| bpf_map_update_elem | Постоянное хранение данных в ядре | 🟡 |
7. Активная нейтрализация (kernel prevention)
Можно не только детектировать, но и разрушать загрузку вредоносного eBPF.
7.1. Метод подмены первой инструкции
SEC("kprobe/sys_bpf")
int prevent_bpf(struct pt_regs *ctx, int cmd, union bpf_attr *attr)
{
if (cmd == BPF_PROG_LOAD) {
struct bpf_insn bad = { .code = 0x95 }; // BPF_EXIT_INSN
bpf_probe_write_user((void *)attr->insns, &bad, sizeof(bad));
}
return 0;
}
Такой подход заменяет первую инструкцию на BPF_EXIT, и верификатор считает программу пустой.
Результат — отказ загрузки без падения ядра.
8. Tail Calls и обход ограничений верификатора
eBPF накладывает лимит в 1 млн инструкций и отсутствие циклов. Злоумышленники обходят это, связывая несколько программ через tail calls:
bpf_tail_call(ctx, &prog_array, 1);
Это позволяет строить цепочку исполняемых программ, что требует детектирования не только по факту загрузки, но и по цепочке вызовов bpf_tail_call.
9. Пример реального кейса: ExecHijack
Вредоносная программа, обнаруженная в ходе эксперимента Solar 4RAYS, использовала tracepoint sys_enter_execve и helper bpf_probe_write_user() для перехвата имени запускаемого бинаря.
Таким образом, вызов /bin/whoami приводил к исполнению /usr/bin/backdoor.
eBPF-Prevention перехватывал момент загрузки, анализировал prog_name и helper_id, после чего вставлял BPF_EXIT_INSN() в тело программы, предотвращая активацию.
10. Итог: архитектура защиты уровня eBPF
| Уровень | Метод | Эффект |
|---|---|---|
| Userspace | AuditD / Falco | Фиксирует факт загрузки |
| Kernel-level | eBPF-детектор | Анализирует структуру и helpers |
| Kernel prevention | BPF_EXIT подмена | Прерывает выполнение вредоносной программы |
| Post-analysis | perf → SIEM (Splunk / Graylog) | Передаёт телеметрию в SOC |
| Policy layer | whitelisting по prog_name / helper_id | Устанавливает доверенные профили eBPF |
11. Заключение
eBPF стал фундаментом новой парадигмы в области руткитов.
Он безопасен, пока остаётся под контролем — но в руках злоумышленников способен полностью обойти антивирусные решения, SELinux и AppArmor.
Единственная эффективная стратегия — создание уровня “Security for eBPF”, включающего:
- аудит всех eBPF-загрузок;
- перехват и анализ структуры
bpf_attr; - сигнатурный и поведенческий анализ helpers;
- активную блокировку на kernel-уровне;
- и экспорт телеметрии в SIEM для корреляции с другими событиями.