Техническое описание: как работает SecureExec
SecureExec — self-hosted платформа обнаружения и реагирования (EDR) для Linux. Эта статья — техническое описание: полный путь данных от хука в ядре до алерта, архитектура агента, серверный движок детекции и механизмы реагирования.
Архитектура
┌─────────────────────────────────────────┐
│ Linux-хост │
│ │
│ eBPF-программы (ядро) │
│ ├── tracepoints (хуки syscall'ов) │
│ └── kprobes (хуки функций ядра) │
│ │ │
│ Ring buffers (4 канала) │
│ │ │
│ Агент (userspace, один бинарник Rust) │
│ ├── eBPF consumer (парсинг + обогащ.)│
│ ├── Таблица процессов (pid→guid) │
│ ├── Цепочка фильтров (дедупл. + конф)│
│ ├── SQLite spool (устойчивость) │
│ └── gRPC transport │
└────────────┬────────────────────────────┘
│ TLS / gRPC stream
▼
┌─────────────────────────────────────────┐
│ Сервер SecureExec │
│ ├── Приём событий (gRPC) │
│ ├── Alert engine (21 встроенное правило)│
│ ├── Кастомные Starlark-правила │
│ ├── Таблица процессов (lineage/агент) │
│ ├── Индексация в Elasticsearch │
│ └── Диспетчер реагирования │
│ │ │
│ Веб-консоль (Next.js) │
│ ├── Дашборд алертов + поиск │
│ ├── Визуализация дерева процессов │
│ ├── Таймлайн инцидентов │
│ └── UI действий реагирования │
└─────────────────────────────────────────┘
Сбор событий через eBPF
Агент использует eBPF-программы, подключённые к tracepoints и kprobes ядра, для перехвата событий, значимых для безопасности, с минимальными накладными расходами. Kernel module не требуется — eBPF-программы загружаются при старте агента и корректно удаляются при остановке.
События проходят через четыре выделенных ring buffer'а, каждый с размером, подобранным под ожидаемую нагрузку:
| Ring Buffer | Размер | Типы событий |
|---|---|---|
PROCESS_EVENTS | 512 КБ | process_exec, process_fork, process_exit, exec_argv |
FILE_EVENTS | 256 КБ | file_create, file_modify, file_delete, file_rename, file_link, file_perm_change |
NETWORK_EVENTS | 256 КБ | network_connect, network_listen, dns_query |
SECURITY_EVENTS | 1 МБ | privilege_change, process_vm_access, memfd_create, kernel_module_load, namespace_change, capability_change, bpf_program, process_signal, keyctl |
Каждая запись в ring buffer'е начинается с байта event_tag: u8, определяющего тип события. Userspace-consumer читает тег, приводит оставшиеся байты к соответствующей #[repr(C)] структуре и конвертирует в типизированный вариант enum'а BpfEvent.
Что мы перехватываем
Примеры syscall'ов и функций ядра, на которые установлены хуки:
sched_process_exec— перехватывает каждый exec, включая полный argv через связанное событиеEXEC_ARGVдля длинных командных строкsched_process_fork/sched_process_exit— отслеживание создания и завершения процессов для in-memory таблицы процессовsys_enter_openat/security_file_open— файловые операции, фильтруются в ядре для исключения высокочастотного шума из/procи/syssys_enter_connect/sys_enter_bind— сетевые соединения и прослушивающие сокеты с полным парсингом sockaddrudp_sendmsg— извлечение DNS-запросов (UDP порт 53)sys_enter_setuid/sys_enter_setreuid— изменения привилегийsys_enter_process_vm_writev— кросс-процессная запись в память (process injection)sys_enter_memfd_create— fileless-выполнение через in-memory файловые дескрипторыsys_enter_init_module/sys_enter_finit_module— загрузка модулей ядраsys_enter_unshare/sys_enter_setns— изменения namespace'ов (сигналы побега из контейнера)
Все eBPF-программы написаны на Rust с использованием фреймворка Aya.
Архитектура агента
Агент — это один статически скомпонованный бинарник на Rust с нулевыми зависимостями. Работает как systemd-сервис (secureexec-agent.service) и обычно потребляет менее 1% CPU и < 50 МБ RAM на production-сервере, генерирующем 50 000+ событий в минуту.
Пайплайн событий
eBPF ring buffers
→ parse_*_event() (тег → BpfEvent вариант)
→ convert_bpf_events() (BpfEvent → EventKind)
→ ProcessTable.resolve() (pid → process_guid, username, container_id)
→ FilterChain (дедупликация по content_hash, настраиваемые фильтры по типам)
→ SQLite spool (устойчивая очередь на диске, переживает рестарт агента)
→ gRPC transport (streaming или batch режим)
→ Сервер
Таблица процессов
Агент ведёт in-memory таблицу процессов, индексированную по PID. При старте выполняется перечисление /proc для построения базового снимка. По мере поступления событий process_exec, process_fork и process_exit таблица обновляется в реальном времени.
Каждая запись хранит:
- PID и PPID (parent PID из procfs, не из tracepoint — tracepoint ненадёжен для PPID)
- start_time (из
/proc/{pid}/stat) - process_guid — стабильный SHA-256 хеш от
(agent_id, pid, start_time), уникально идентифицирующий процесс даже при повторном использовании PID - username — разрешённый из карты UID→name, спарсенной из
/etc/passwdпри старте - container_id — извлечённый из
/proc/{pid}/cgroup, если процесс работает внутри контейнера
process_guid — ключ, связывающий события с визуализацией дерева процессов в консоли. Благодаря включению start_time он различает два разных процесса, повторно использующих один PID.
Spool и повторная отправка
При потере gRPC-соединения с сервером события сохраняются в локальную базу SQLite (spool). При восстановлении соединения накопленные события переотправляются в порядке sequence number'ов. Heartbeat агента (AgentHeartbeat) сообщает значение spool_pending, чтобы операторы могли отслеживать backlog через веб-консоль.
Дедупликация по содержимому
Тело каждого события хешируется SHA-1 (трейт ContentHash). Агент поддерживает LRU-фильтр дедупликации на 65 536 записей. Если одинаковое содержимое события встречается дважды в пределах окна фильтра, дубликат отбрасывается до попадания в spool. Это устраняет избыточный шум от inotify-штормов и частых перезапусков процессов.
Схема событий (Protobuf)
Все события используют общий конверт:
message AgentEvent {
string id = 1; // UUIDv4
uint64 seqno = 2; // монотонный, per-agent
string timestamp = 3; // ISO-8601
string agent_id = 4;
string hostname = 5;
string os = 6;
string content_hash = 7; // SHA-1 тела события
string process_guid = 8; // стабильный ID процесса
string process_name = 9;
uint32 process_pid = 38;
string username = 40;
string container_id = 41;
oneof kind {
ProcessEvent process_create = 10;
ProcessEvent process_fork = 25;
ProcessEvent process_exit = 11;
FileEvent file_create = 12;
FileEvent file_modify = 13;
FileEvent file_delete = 14;
FileRenameEvent file_rename = 15;
NetworkEvent network_connect = 16;
NetworkEvent network_listen = 17;
DnsEvent dns_query = 18;
UserLogonEvent user_logon = 20;
PrivilegeChangeEvent privilege_change = 26;
ProcessVmEvent process_vm_access = 31;
MemfdCreateEvent memfd_create = 32;
KernelModuleEvent kernel_module_load = 30;
NamespaceChangeEvent namespace_change = 36;
CapabilityChangeEvent capability_change = 34;
// ... и другие
}
}
Поле seqno — монотонно возрастающий счётчик per-agent. Обеспечивает полную упорядоченность событий внутри хоста и служит вторичным ключом сортировки (после @timestamp) в запросах Elasticsearch, что даёт корректный порядок событий с субмиллисекундной точностью.
Серверный движок детекции
Сервер оценивает каждое входящее событие по набору правил алертов. Правила бывают двух типов:
Встроенные правила (21 правило)
Нативные Rust-правила, скомпилированные в бинарник сервера. Каждое правило реализует трейт AlertRule:
pub trait AlertRule: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn severity(&self) -> &str; // "low" | "medium" | "high" | "critical"
fn evaluate(&self, ctx: &AlertContext, events: &[AgentEvent]) -> Vec<AlertEvent>;
}
Правила получают батчи событий и AlertContext, включающий серверную таблицу процессов для разрешения lineage. Примеры:
- Reverse Shell — матчит shell-процессы (
bash,sh,zsh,dash,ksh), устанавливающие исходящие соединения к не-приватным IP - SSH Brute-Force — stateful-правило со sliding window счётчиком per (source_ip, agent_id) и cooldown-таймером для предотвращения флуда алертов
- Ransomware — multi-signal правило: создание ransom note, переименование файлов в encrypted-расширения, детекция скорости массового переименования (20+ за 60с) и паттерны команд уничтожения бэкапов
- Криптомайнер — cross-event-type правило, матчащее имена процессов, stratum-паттерны в командных строках, порты майнинг-пулов и DNS-запросы к известным доменам пулов
Каждое правило включает allowlist для известных легитимных процессов. Например, правило Privilege Escalation исключает sshd (privilege separation), su, sudo, cron и systemd-executor.
Кастомные Starlark-правила
Пользователи могут писать правила детекции на Starlark (Python-подобный конфигурационный язык). Кастомные правила хранятся в PostgreSQL и перезагружаются без перезапуска сервера. Они имеют доступ к тому же AlertContext и данным событий, что и встроенные правила.
Правила подавления
Starlark-based правила подавления позволяют отфильтровать известные ложные срабатывания. Они оцениваются после правил алертов и могут подавить конкретные алерты по имени правила, severity, имени процесса или любому полю события.
Хранение в Elasticsearch
События и алерты индексируются в Elasticsearch с отдельными шаблонами индексов:
- События:
secureexec-events-{org_id}-*— ежедневная ротация, 7-дневное хранение (настраивается) - Алерты:
secureexec-alerts-{org_id}-*— ежедневная ротация, 90-дневное хранение
Сервер использует _bulk API для высокопроизводительной индексации. Каждое событие индексируется со всеми полями конверта и типо-специфичным payload'ом, что позволяет выполнять полнотекстовый поиск по командным строкам, путям файлов, IP-адресам и DNS-запросам.
Возможности реагирования
При обнаружении угрозы операторы могут действовать прямо из веб-консоли:
- Сетевая изоляция — агент настраивает правила iptables для блокировки всего трафика, кроме gRPC-канала агент↔сервер. Хост эффективно изолирован, но остаётся управляемым.
- Уничтожение дерева процессов — отправляет SIGKILL всей группе процессов от PID-предка, завершая полную цепочку атаки.
- Блокировка по хешу или пути — использует fanotify для запрета exec-разрешений для конкретных хешей файлов или путей. Правила мгновенно применяются ко всем агентам организации.
- Глобальный блок-лист — централизованно управляемые правила блокировки, распространяемые на все конечные точки.
Команды реагирования доставляются агентам через gRPC control channel (CommandStream). Агент опрашивает наличие ожидающих команд на каждом heartbeat-интервале.
Таймлайн инцидентов
Таймлайн инцидентов восстанавливает хронологическую историю атаки из любой точки входа — алерта, процесса или временного окна на конкретном хосте.
Сервер запрашивает Elasticsearch на события, соответствующие scope (agent_id + process_guid + временной диапазон), опционально разрешает lightweight lineage процессов (one-hop lookup родителя из process_create/fork событий) и объединяет события с соответствующими алертами. Каждая запись размечается фазой атаки (Initial Access, Execution, Persistence, C2) на основе эвристик — например, исходящие соединения к внешним IP размечаются как C2, а записи в cron-директории — как Persistence.
Развёртывание
Агент поставляется как .deb или .rpm пакет и устанавливается менее чем за 60 секунд:
dpkg -i secureexec-agent.deb
systemctl start secureexec-agent
Поддерживаемые дистрибутивы: Ubuntu 20.04+, Debian 11+, RHEL 8+, Amazon Linux 2. Единственное требование к ядру — поддержка eBPF (kernel 5.4+). Для более старых ядер доступен опциональный kernel module fallback.
Серверные компоненты (API-сервер, Elasticsearch, PostgreSQL) разворачиваются через Docker Compose для single-node установок или Kubernetes для production-кластеров.
Открытые вопросы и roadmap
Активные направления разработки:
- File integrity monitoring со сравнением с baseline
- Интеграция YARA-сканирования для анализа файлов на хосте
- Библиотека Starlark-правил с community-контрибуциями
- Поддержка агентов для macOS и Windows (Endpoint Security framework и ETW соответственно)
По вопросам или для guided walkthrough на вашей инфраструктуре — запросите демо. Демо проходит на реальном Linux-хосте, без слайдов.