Перейти к содержимому

Восстановление таймлайна удаления файлов Linux: как проверять DFIR-выводы по journald и inode

Восстановление таймлайна удаления файлов Linux: как проверять DFIR-выводы по journald и inode
Время чтения: 12 мин.

На расследовании компрометации почтового сервера я выстроил таймлайн удаления конфигурационных файлов по journald — получилось чисто и логично. Удаление в 14:32 UTC, через 8 минут после SSH-входа с подозрительного IP. Потом проверил inode того же каталога через debugfs — ctime показал 17:32. Три часа разницы. Первая реакция: атакующий подменил временные метки. Вторая, правильная: сервер работал в UTC+3, journald записывал в UTC, а debugfs отобразил время в локальной таймзоне моей SSH-сессии. Я чуть не зафиксировал в отчёте ложную гипотезу о двухэтапной атаке.

С тех пор восстановление таймлайна удаления файлов Linux я строю только через обязательную перекрёстную проверку. Ниже — весь процесс с контрольными вопросами, которые задаю себе на каждом шаге.

Почему таймлайн удаления — самая ненадёжная часть DFIR-расследования на Linux-сервере

Удаление файла на Linux оставляет минимум следов. В отличие от создания или модификации, удаление не генерирует отдельного события в journald — если не настроен auditd (подсистема аудита на уровне ядра, которая умеет перехватывать системные вызовы вроде unlink). Файловая система обновляет только метаданные каталога, а не самого файла. Артефакты файловой системы Linux при удалении — это не красная стрелка «удалено здесь», а набор косвенных меток, которые нужно правильно прочитать.

MITRE ATT&CK (открытая база тактик и техник атак; T-коды вроде T1070, T1070.002 — идентификаторы конкретных техник) описывает зачистку следов через T1070 (Indicator Removal). Подтехника T1070.002 (Clear Linux or Mac System Logs) покрывает удаление системных логов: rm -rf /var/log/journal/*, обнуление syslog, journalctl --vacuum-*. Подтехника T1070.003 (Clear Command History) — очистку истории команд: history -c, rm ~/.bash_history, HISTSIZE=0. Обе направлены на уничтожение артефактов, по которым аналитик строит хронологию.

Но даже без антифорензики атакующего аналитик может ошибиться сам. Три сценария, которые я видел не раз:

  • Рассинхрон часов — journald хранит время в UTC, debugfs показывает в таймзоне текущей сессии, stat — тоже. Без явной нормализации появляются фантомные разрывы в хронологии.
  • Путаница ctime и mtime — ctime каталога обновляется при изменении его метаданных, mtime — при изменении записей внутри. Различие тонкое, и его легко упустить.
  • Единственный источник — таймлайн, построенный только по journald или только по inode, ломается от одной ошибки в данных.

Расследование инцидентов Linux надёжно только при перекрёстной проверке между минимум двумя независимыми источниками.

Анализ inode Linux: что метаданные расскажут об удалении

Inode (index node) — структура данных файловой системы, хранящая метаданные файла: размер, владельца, права доступа и временные метки. Каждому файлу и каталогу присвоен уникальный номер inode. Для тех, кто только начинает разбираться в форензике: inode — это «паспорт» файла, и именно он становится основным артефактом для timeline analysis.

Четыре метки времени ext4 и одна ловушка для аналитика

Файловая система ext4 хранит в inode четыре временных метки:

atime (access time) — последний доступ к содержимому. На практике часто неточна: серверы монтируют разделы с relatime или noatime ради производительности. Проверьте опции через mount | grep /dev/sdXN. Если в выводе relatime — atime обновляется только когда оно старше mtime. Если noatime — не обновляется вообще.

mtime (modification time) — последнее изменение содержимого файла. Для каталога это последнее добавление, переименование или удаление записи внутри него. Именно mtime каталога — ваш главный индикатор времени удаления.

ctime (change time) — последнее изменение метаданных inode: права, владелец, количество жёстких ссылок. Ключевой факт: ctime нельзя подменить через touch или системный вызов utimensat — оно обновляется только ядром. Это делает ctime надёжнее mtime для форензики удалённых файлов.

crtime (creation time, birth time) — время создания файла. Появился в ext4, отсутствует в ext2/ext3. Не все инструменты его показывают: stat из современных coreutils отображает его как «Birth», но ls — нет.

Ловушка: dtime (deletion time) существует в структуре inode ext2/ext3 и записывается при удалении файла. По данным из книги Dan Farmer и Wietse Venema «Forensic Discovery» — на которую ссылаются практически все материалы по Linux-форензике — в ext3/ext4 при удалении inode зачищается, блоки данных отвязываются. Но inode может быть быстро переиспользован новым файлом, и dtime теряется безвозвратно. Полагаться на dtime как на единственный индикатор — ошибка.

Практический вывод: чтобы определить время удаления файла, смотрите не на inode самого удалённого файла, а на mtime и ctime родительского каталога. Именно они обновляются, когда из каталога удаляется запись.

debugfs и istat — практика чтения inode

Зачем два инструмента для одной задачи? debugfs (из пакета e2fsprogs, есть в любом дистрибутиве) работает напрямую с ext2/ext3/ext4 и показывает низкоуровневые детали. istat из пакета sleuthkit (The Sleuth Kit) — кроссплатформенный форензик-инструмент для работы с образами дисков и разными файловыми системами. На расследовании я использую оба, чтобы сверить результат.

Предпосылки: раздел отмонтирован или смонтирован read-only. Права root. Для istat — установить sleuthkit (apt install sleuthkit на Debian/Ubuntu, dnf install sleuthkit на RHEL-семействе).

Пример: нужно проверить, когда последний раз удалялись или создавались файлы внутри /var/log/:

# Узнаём номер inode каталога
ls -id /var/log
# Вывод: 131073 /var/log

# Читаем метаданные inode через debugfs
# ВАЖНО: задаём TZ=UTC для нормализации времени
TZ=UTC sudo debugfs -R "stat <131073>" /dev/sda1

В выводе debugfs ищите строки с метками времени. Если mtime и ctime каталога совпадают — последняя операция над записями каталога (создание, удаление или переименование файла внутри) произошла именно в это время. Если ctime новее mtime — после файловой операции кто-то изменил права или владельца каталога.

Вот тут критический момент: без TZ=UTC команда debugfs покажет время в таймзоне вашей сессии. Подключились по SSH из Москвы (UTC+3) к серверу в Амстердаме (UTC+1) — debugfs покажет московское время, journalctl без --utc — амстердамское, а файлы журнала записаны в UTC. Три разных времени для одного события. Это та самая ловушка, которая стоила мне трёх часов отладки в начале статьи.

istat из Sleuth Kit делает аналогичную работу на образе диска: istat -o 2048 disk.img 131073, где -o 2048 — смещение раздела в секторах (берётся из mmls). По умолчанию istat показывает UTC — и это одно из его преимуществ перед debugfs.

Journald анализ логов: второй источник для восстановления таймлайна

systemd-journald — демон логирования systemd. Если анализ inode показал, что mtime каталога /var/log обновился в 14:32 UTC, нужно найти в journald событие, которое это подтвердит или опровергнет. Без второго источника таймлайн — гипотеза, а не факт.

Какие события journald связаны с удалением файлов

systemd-journald не фиксирует удаление файлов напрямую — это задача auditd (и он должен быть настроен до инцидента; установка после компрометации не восстановит прошлые события). Но анализ логов systemd-journald даёт косвенные зацепки:

SSH-сессии — подключение, аутентификация, разрыв. По ним определяется, кто находился на сервере в момент удаления. Фильтр: journalctl --utc _COMM=sshd.

Действия systemd — остановка и запуск сервисов, logrotate, cron-задачи, которые могли удалить файлы по расписанию. Фильтр: journalctl --utc _SYSTEMD_UNIT=logrotate.service.

Сообщения ядра — SELinux/AppArmor deny, ошибки I/O, mount/umount. Если файл был удалён процессом без прав — ядро запишет отказ.

Фильтрация по временному окну, полученному из анализа inode:

# События в окне ±15 минут от подозрительного mtime каталога
# --utc ОБЯЗАТЕЛЕН для сопоставления с данными debugfs
journalctl --utc --since "2024-07-24 14:17:00" --until "2024-07-24 14:47:00"

# Только SSH-сессии в этом окне
journalctl --utc --since "2024-07-24 14:17:00" --until "2024-07-24 14:47:00" _COMM=sshd

Ожидаемый результат: список событий с метками в UTC. Видите SSH-аутентификацию в этом окне — есть связка «кто + когда». Окно пустое — либо journald очищен (проверьте journalctl --verify, PASS/FAIL для каждого файла), либо ваше временное окно вычислено с ошибкой таймзоны.

Рассинхрон часов: где journald и файловая система Linux расходятся

Три причины расхождения из моей практики:

Таймзона. journald хранит время в микросекундах от epoch (1 января 1970, UTC). Но journalctl без --utc показывает время в таймзоне из /etc/localtime сервера. debugfs использует TZ текущей сессии. stat — тоже. Правило: перед любым сравнением нормализуйте все источники к UTC. Задокументируйте таймзону сервера и свою.

NTP-дрифт. Если на сервере не настроена синхронизация времени (timedatectl покажет NTP synchronized: no), часы могут уходить на секунды или минуты за неделю. journald и ext4 записывают «одинаково неправильное» время, но после восстановления NTP новые записи пойдут с правильным — на стыке образуется скачок. На одном расследовании я видел дрифт в 4 минуты, который создавал иллюзию «подозрительной паузы» в действиях атакующего.

Ручная подстановка через touch. Атакующий может использовать touch -t для подмены mtime и atime файла. Но touch не влияет на ctime — оно обновляется ядром и всегда отражает реальное время операции. Если mtime файла — прошлый год, а ctime — вчера, кто-то вручную подменил mtime вчера. Одна из причин, почему ctime надёжнее для форензики.

Восстановление таймлайна удаления файлов Linux: делай раз, делай два, делай три

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

Предпосылки: доступ к серверу с правами root (или образ диска, снятый через dd или dc3dd). ОС — systemd-based дистрибутив (Ubuntu 18.04+, RHEL/CentOS 7+, Debian 9+). Установлены e2fsprogs и sleuthkit. Работаете на живой системе — не пишите на исследуемый раздел. Не входите под учётной записью атакующего — иначе перезапишете оставшиеся артефакты.

Шаг 1 — формулируем гипотезу из журнала

Начинаем с journald, а не с inode. Причина: журнал даёт контекст (кто подключался, какие сервисы работали), а inode — только метки времени без привязки к пользователям.

Выполните journalctl --utc --list-boots. Это покажет все зафиксированные загрузки системы. Если между подозрительным событием и текущим моментом была перезагрузка, а journald настроен на volatile storage (проверьте: grep Storage /etc/systemd/journald.conf) — данные до ребута утрачены. Storage=volatile или отсутствие каталога /var/log/journal/ означает, что у вас только журнал текущей загрузки.

Просмотрите SSH-сессии через journalctl --utc _COMM=sshd. Зафиксируйте все временные метки входа и выхода. Сформулируйте гипотезу: «удаление произошло между 14:20 и 14:45 UTC, когда была активна SSH-сессия пользователя admin с IP 203.0.113.42».

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

Шаг 2 — проверяем гипотезу через анализ inode

Задача: подтвердить или опровергнуть временное окно из Шага 1.

Определите inode каталога, из которого удалены файлы: ls -id /path/to/directory. Запросите метки через TZ=UTC sudo debugfs -R "stat <INODE>" /dev/sdXN. Нормализация к UTC обязательна.

Сверьте mtime каталога с окном из Шага 1. Три исхода:

  • mtime попадает в окно — гипотеза подтверждается первичной проверкой. Переходите к Шагу 3.
  • mtime отличается на целое количество часов — вероятно, проблема таймзоны. Проверьте timedatectl на сервере и echo $TZ в своей сессии.
  • mtime не совпадает вообще — гипотеза неверна. Вернитесь к Шагу 1, расширьте временное окно или ищите другие сессии. Не подгоняйте данные под ожидание.

Шаг 3 — перекрёстная валидация и чек-лист самопроверки

Финальный шаг — контрольные вопросы, которые я задаю себе перед фиксацией вывода в отчёте. Привычка, дважды спасавшая от ложных выводов.

Чек-лист:

  1. Таймзона нормализована? Все метки (journald, debugfs, stat, istat) приведены к UTC? Сдвиг задокументирован?
  2. ctime подтверждает mtime? Если mtime каталога равен ctime — одна операция. Если ctime новее — после удаления файлов кто-то изменил метаданные каталога. Почему?
  3. Не перепутан каталог и файл? mtime каталога обновляется при удалении записи из каталога. mtime файла — при изменении его содержимого. Смотрите mtime восстановленного файла — это не время удаления.
  4. NTP был активен? timedatectl покажет NTP synchronized: yes/no. Если no — все метки могут быть смещены. Оцените порядок дрифта.
  5. Журнал не повреждён? journalctl --verify выведет PASS/FAIL для каждого journal-файла. FAIL означает повреждение или подмену — таким данным нельзя доверять без дополнительной верификации.

Типичные ошибки в DFIR-расследовании при timeline analysis форензика

За несколько лет расследований я систематизировал ошибки — свои и чужие — при восстановлении таймлайна удаления. Три из них повторяются с завидной регулярностью.

Ошибка 1: принять mtime файла за время удаления. mtime файла — время последней записи в файл. При удалении через rm mtime файла не обновляется. Обновляется mtime каталога. Если файл частично восстановлен через extundelete или debugfs, его mtime показывает время последней модификации содержимого — а она могла произойти за дни или недели до удаления. Начинающие аналитики записывают это как время удаления — и строят на этом всю хронологию.

Ошибка 2: не учитывать relatime. Большинство серверов монтируют ext4 с relatime. Это означает: atime не отражает реальное время последнего доступа. Строить выводы по atime без проверки опций монтирования (mount | grep target) — верный путь к ложным заключениям. Если хотите понять, как эту формулу превратить в осмысленный анализ на практике — на HackerLab.pro в категории forensics есть задачи, где нужно разобраться с метками времени в контролируемой среде.

Ошибка 3: доверять единственному источнику. Таймлайн только по journald уничтожается одной командой journalctl --vacuum-time=1s. Таймлайн только по inode подменяется через touch. Перекрёстная проверка между journald и файловой системой — минимальный стандарт. Идеально — три источника: journald + inode + auditd (если настроен). В SigmaHQ (публичный репозиторий правил детектирования) есть правило lnx_shell_clear_cmd_history.yml, которое детектирует очистку истории команд. Если такое правило сработало в SIEM — это третья независимая точка для таймлайна.

По классификации D3FEND (knowledge graph защитных техник MITRE — по сути, зеркало ATT&CK, но для обороняющихся) для противодействия T1070.002 рекомендуется D3-FIM (File Integrity Monitoring) — мониторинг целостности файлов. На практике это AIDE, OSSEC или Tripwire, настроенные на /var/log/. Если FIM работал до инцидента — у вас будет независимый лог с собственными метками. Если не работал — расследование опирается на два источника вместо трёх, и каждый вывод требует усиленной валидации.

OWASP относит недостатки логирования к категории A09:2021 — Security Logging and Monitoring Failures: без адекватного логирования взлом нельзя ни обнаружить, ни расследовать. Это прямо касается ситуации, когда auditd не настроен, а journald хранит данные в volatile-режиме.

Большинство DFIR-руководств описывают инструменты — какие команды запускать, какие артефакты собирать. Они почти никогда не объясняют, как проверить собственный вывод. А в реальном расследовании ошибка аналитика стоит не меньше, чем ошибка конфигурации сервера.

Я видел отчёт, где таймлайн был построен по одному журналу journald без нормализации таймзон, а заказчик перестроил инфраструктуру на основе хронологии с трёхчасовым сдвигом. Не потому что аналитик был некомпетентен — потому что у него не было привычки сомневаться в собственных данных.

Привычка самопроверки — инженерная дисциплина. В разработке есть code review. В финансах — двойная запись. В DFIR почему-то до сих пор принято принимать первую версию таймлайна, если она «выглядит логично». Мой опыт: чем логичнее таймлайн с первого прохода, тем выше вероятность, что в нём есть ошибка — потому что вы подсознательно подогнали данные под ожидание.

Если в тексте выше была хоть одна мысль, заставившая пересмотреть подход к форензике, — хорошо. Базовый трек IB Basics на codeby.school — это то, что я бы прошёл вместо того, чтобы собирать методологию расследований по обрывкам из разрозненных write-up’ов.

Эту тему и смежные навыки разбирают на практике в курсе «Цифровая криминалистика и реагирование на инциденты (DFIR)» Codeby Academy.