Перейти к содержанию

DFIR

DFIR-команды работают локально с копией образа. Перед анализом сохраните исходный SHA-256:

python mountfile_dfir.py hash evidence.img > evidence.img.sha256

Для одного исходного файла можно создать sidecar JSON manifest, не открывая источник на запись и не перезаписывая существующий manifest:

python -m mountfile evidence-manifest evidence.img \
  --output case-123/evidence.img.manifest.json

Хэш вычисляется потоково с ограниченным размером блока; O_NOATIME запрашивается там, где это поддерживается, а фактическое применение записывается в manifest. Manifest описывает только байты исходного файла: MountFile не определяет здесь raw/EWF/AFF4/VM-формат, не проверяет внутренние хэши контейнера и не вычисляет logical media stream. Это техническая запись одного hash-измерения, а не полная цепочка хранения доказательств; применяйте отдельный write blocker, документируйте источник и независимо проверяйте результат.

mountfile_dfir.py использует только стандартную библиотеку для grep, SHA-256, timeline CSV → SQLite, простого timestomp-детектора и plugin registry. REST-команда — отдельная опция и требует fastapi и uvicorn. Этот прототип не имеет аутентификации и поэтому отказывается слушать внешние адреса; используйте только loopback. Для сетевого доступа применяйте аутентифицированный API из mountfile.api.

python mountfile_dfir.py grep evidence.img "powershell" --encoding utf-16le --json
python mountfile_dfir.py timeline timeline.csv --db timeline.db
python mountfile_dfir.py timeline timeline.csv --db timeline.db --query --limit 100
python mountfile_dfir.py timestomp timeline.csv --json
python mountfile_dfir.py serve --db timeline.db --host 127.0.0.1 --port 8000

grep ищет байтовую строку в mmap raw-файла, в том числе вне выделенных файлов; это не файловый поиск и не антивирус. timestomp применяет простые сравнения времён (mtime, ctime, atime, birth) и выдаёт эвристические признаки.

Final-cluster slack extraction

Команда slack читает только slack последнего кластера по явно заданным параметрам extent. Она не ищет путь в файловой системе, не вычисляет цепочку кластеров и не проверяет, что диапазон действительно принадлежит указанному файлу. Независимо подтвердите offset последнего кластера, логический размер файла и размер кластера перед запуском:

python -m mountfile slack evidence.img --slack-only \
  --last-cluster-offset 0x1000 --file-size 300 --cluster-size 4096 \
  --output extracted/slack.bin

Образ открывается только для чтения. Выходной файл создаётся эксклюзивно и никогда не перезаписывает существующий файл. Без --output команда печатает JSON-сводку (offset, length, has_slack, created) с абсолютным offset и размером, но не выводит forensic bytes. Если slack нет (файл пустой или выровнен по границе кластера), файл не создаётся, а created равно false: пустой артефакт не появляется в каталоге вывода.

Explicit plugin discovery

Примеры examples/plugins/yara.py и examples/plugins/timeline.py экспортируют PLUGINS. Импорт не регистрирует и не запускает плагины автоматически; владелец процесса явно перечисляет доверенные файлы и открывает lifecycle:

from mountfile.plugins import PluginRegistry

registry = PluginRegistry()
registry.discover(paths=[
    "examples/plugins/yara.py",
    "examples/plugins/timeline.py",
])
with registry.session("evidence.img", {"size": 4096}):
    registry.file({"path": "/note.txt", "data": b"example", "size": 7})

discover(paths=...) импортирует исполняемый Python-код, поэтому загружайте только проверенные плагины. YARA-адаптер требует явно настроенных правил и опциональной зависимости yara-python; плагины не получают изменяемый handle на образ и не меняют поведение записи.

Tutorial: PDF на FAT32

Живой файл

Это безопасный read-only маршрут, если PDF присутствует в каталоге:

cp --reflink=auto evidence.img work.img
sha256sum work.img
python -m mountfile volumes work.img
python -m mountfile ls work.img / --auto
python -m mountfile get work.img /docs/report.pdf -o recovered --auto
sha256sum recovered/report.pdf

Параметр --auto можно заменить на известный --offset 0x.... get только извлекает файл; если файл не найден, это не означает, что он был удалён.

Удалённая запись через pytsk3

Встроенного file carving и отдельной DFIR-команды «восстановить PDF» нет. Для записи удалённой мета-записи используется API mountfile_ai.py: он может прочитать UNALLOC-записи, открыть inode и сохранить полученные байты во внешнюю папку. Сначала найдите offset и inode в read-only режиме:

# python recover_pdf.py; image — копия, не оригинал
import os
from pathlib import Path

os.environ["MOUNTFILE_READONLY"] = "1"
os.environ["MOUNTFILE_WORKDIR"] = "recovery-work"
import mountfile_ai as mf

image = Path("work.img").resolve()
mf.CONFIG.allow(image)
parts = mf.list_partitions(image)
fat = next(p for p in parts if "FAT" in (p.fs_type or "").upper())
hits = mf.search_files(image, fat.offset, r"\.pdf$", include_deleted=True)
for item in hits:
    print(item.inode, item.path, item.size, item.deleted)

Выберите подтверждённую удалённую запись, затем в отдельном процессе снимите read-only только для записи результата во внешнюю папку:

import os
from pathlib import Path

os.environ["MOUNTFILE_READONLY"] = "0"
os.environ["MOUNTFILE_WORKDIR"] = "recovery-work"
import mountfile_ai as mf

image = Path("work.img").resolve()
mf.CONFIG.allow(image)
result = mf.recover_deleted_file(
    image, offset=FAT_OFFSET, inode=PDF_INODE,
    output=Path("recovery-work/recovered.pdf"),
)
print(result.model_dump())

Замените FAT_OFFSET и PDF_INODE значениями из первого запуска. Требуются pydantic и optional pytsk3 extra (mountfile[legacy-tsk]). Метод читает inode, не карвит фрагменты и не гарантирует целостность PDF. complete=true означает равенство переданному original_size (или размеру мета-записи), а не успешное открытие PDF. Сохраните audit JSONL и отдельно проверьте тип файла, размер и SHA-256.

Tutorial: анализ Android boot image

В mountfile.android есть read-only parser API для Android sparse image и boot-image headers/chunks. Эти библиотечные функции не распаковывают ramdisk, не проверяют AVB и не прошивают устройство; они пока не подключены к основному CLI/GUI. Для поиска сигнатур можно также выполнить:

python mountfile_dfir.py hash boot.img > boot.img.sha256
python mountfile_ai.py inspect --image boot.img
python -m mountfile info boot.img

inspect смотрит сигнатуры в первых 1 MiB и пытается прочитать таблицу томов через pytsk3, если она есть. Отсутствие результата не доказывает отсутствие Android-разделов: boot image может быть контейнером с заголовком и сжатым ramdisk. Пример parser API для структурированных заголовков приведён в API reference.

Для отдельного извлечения используйте внешний инструмент, установленный и проверенный по его документации, например unpackbootimg или magiskboot:

unpackbootimg -i boot.img -o boot-unpacked   # внешний инструмент, optional
# затем анализируйте извлечённые файлы штатными локальными средствами
sha256sum boot.img boot-unpacked/*

Эти программы не входят в зависимости Mount File. Mount File не подтверждает формат, AVB-цепочку, подпись, адрес загрузки или пригодность образа для прошивки; не записывайте результат обратно в устройство без отдельной процедуры производителя.

Tutorial: поиск malware в NTFS

Для имён и метаданных NTFS требуется pytsk3. Сначала осмотрите раздел и подберите offset без записи:

python -m pip install pytsk3
python -m mountfile volumes evidence.img
python -m mountfile autopick evidence.img --json > candidates.json
python -m mountfile ls evidence.img / --offset 0xNTFS_OFFSET

Для рекурсивного поиска имён используйте read-only функции mountfile_ai.py:

import os
from pathlib import Path

os.environ["MOUNTFILE_READONLY"] = "1"
import mountfile_ai as mf

image = Path("evidence.img").resolve()
mf.CONFIG.allow(image)
for part in mf.list_partitions(image):
    if "NTFS" not in (part.fs_type or "").upper():
        continue
    hits = mf.search_files(
        image, part.offset,
        r"\.(exe|dll|sys|ps1|vbs|js|scr|lnk)$",
        include_deleted=True,
    )
    for hit in hits:
        print(hit.model_dump())

Для содержимого, включая невыделенные области, добавьте независимый raw-поиск:

python mountfile_dfir.py grep evidence.img "powershell" --encoding utf-16le --json
python mountfile_dfir.py grep evidence.img "http://" --encoding latin-1 --max-hits 200

Это triage, а не вердикт о вредоносности: совпадение имени или строки не доказывает запуск malware, а отсутствие совпадения не доказывает чистоту. Проверяйте временную шкалу, подпись, хэш и контекст отдельными инструментами. include_deleted=True видит удалённые записи, доступные через метаданные; он не выполняет carving и может не обойти удалённые каталоги.

Read-only библиотечные API

Дополнительно доступны composable Python API. Они пока не являются командами mountfile_dfir.py и не подключены к GUI. Примеры ниже работают с явно переданными метаданными, диапазонами или образом только для чтения; они не определяют allocation map и не доказывают наличие malware или timestomping.

Timeline и timestamp triage

Нормализатор принимает caller-supplied records и переводит даты в UTC. CSV экспорт открывает только явно заданный выходной файл:

from mountfile.dfir_timeline import normalize_timeline, export_timeline_csv
from mountfile.dfir_timestomp import TimestampRecord, analyze_timestomp

records = [
    TimestampRecord(
        path="/evidence/a.bin",
        created=1_700_000_000,
        modified=1_699_999_000,
        accessed=1_700_000_100,
        record_id="inode-17",
    )
]
events = normalize_timeline(records)
export_timeline_csv(records, "timeline-normalized.csv")
for finding in analyze_timestomp(records):
    print(finding.as_dict())

Порядок времён — эвристика: файловые системы и collectors могут сообщать семантически разные ctime/creation times и иметь отличающуюся точность. Boundary/range alerts по умолчанию отключены и требуют явных thresholds.

YARA и сигнатуры в переданных unallocated ranges

YARA доступен через optional yara-python; он не нужен для импорта остальных DFIR модулей. Правила поставляет пользователь. В package CLI доступны yara и unallocated-scan; последний читает JSON-файл с явными [offset, length] ranges:

python -m pip install yara-python  # optional, только для YARA
python -m mountfile yara evidence.img --rules user-rules.yar
printf '[[1048576, 2097152]]\n' > ranges.json
python -m mountfile unallocated-scan evidence.img --ranges ranges.json

Чтобы выгрузить нормализованный UTC timeline CSV из метаданных существующего collector CSV:

python -m mountfile timeline-export \
  --csv collector.csv --output normalized-timeline.csv

Правила и ranges не скачиваются автоматически; сканеры не определяют allocation map. Примеры Python API:

from mountfile.dfir_yara import scan_yara
from mountfile.dfir_unallocated_scan import scan_unallocated

hits = scan_yara(
    "evidence.img",
    'rule marker { strings: $s = "powershell" wide condition: $s }',
)
for hit in hits:
    print(hit["rule"], hit["offsets"])

# Укажите предварительно известные half-open byte ranges (offset, length).
for hit in scan_unallocated("evidence.img", [(0x100000, 0x200000)]):
    print(hit.offset, hit.signature)

Сигнатурный поиск читает диапазоны bounded chunks и возвращает только magic prefix + абсолютный offset. Это triage, не полноценная проверка формата или carving; allocation ranges должны быть найдены вызывающей стороной.

File slack при уже известном extent

Хелпер читает slack последнего кластера только если вызывающий код уже знает его абсолютный offset; он не разрешает путь в inode/cluster chain:

from mountfile.dfir_slack import read_file_slack

result = read_file_slack(
    "evidence.img",
    last_cluster_offset=0x400000,
    file_size=5000,
    cluster_size=4096,
)
print(result.offset, len(result.bytes))

Для пустого или выровненного по границе кластера файла результат пустой и offset равен None. Проверяйте путь, cluster chain и offset независимо до вызова; slack может содержать остатки прежних данных.


Пакетные DFIR-команды (issue #87)

Помимо standalone-скриптов, те же возможности доступны через пакетный CLI (python -m mountfile <команда>). Все команды работают read-only и никогда не изменяют образ-источник.

Timeline (Task 1.1)

python -m mountfile timeline-build evidence.img -o timeline.db --csv timeline.csv
python -m mountfile timeline-query --db timeline.db \
    --start 2024-03-12T14:00 --end 2024-03-12T15:00 --event mtime --json

Схема: timestamp | source | path | event | sha256. События формируются по четырём меткам pytsk3 (created, mtime, atime, ctime); timeline-query отвечает на вопрос «что происходило в окне …» — это хук для AI-интеграции. Разбор $STANDARD_INFORMATION против $FILE_NAME требует отдельного MFT-парсера и в эту команду не входит.

Content search (Task 1.2)

python -m mountfile search evidence.img "password=" --json
python -m mountfile search evidence.img "powershell" --encoding utf-16le
python -m mountfile search evidence.img "id=\d+" --regex
python -m mountfile search evidence.img "AKIA" --files   # по содержимому файлов

Без --files поиск идёт потоково по сырым байтам с перекрытием чанков (находит строки в unallocated и slack), с --files — по содержимому каждого файла с указанием пути. Для literal-поиска перекрытие автоматически увеличивается до длины шаблона, чтобы совпадение через границу чанка не терялось. Для regex используется заданное перекрытие: выражение с совпадением длиннее этого перекрытия может пересечь границу чанка и быть пропущено. В range-based API смещения должны быть неотрицательными, длина — положительной, а весь диапазон должен лежать внутри образа; некорректный диапазон возвращает явную ошибку.

YARA (Task 1.3)

python -m mountfile yara-scan evidence.img --rules rules/ --json
python -m mountfile yara-scan evidence.img --rules rules/pii.yar --targets unallocated

--rules принимает как один файл, так и каталог. Встроенные правила: rules/pii.yar (карты, IBAN, SSN, приватные ключи) и rules/anti_forensics.yar (CCleaner, timestomp-утилиты, очистка журналов, secure-delete). Требуется pip install yara-python.

Timestomp (Task 1.4)

python -m mountfile timestomp-scan evidence.img --skew 2.0 --json

Четыре независимых правила с причиной в отчёте: timestamp в будущем, mtime == atime == ctime, нулевые наносекунды и $SI.mtime < $FN.mtime (последнее — только если вызывающий код передал пару значений).

Slack (Task 1.6)

python -m mountfile slack-extract evidence.img -o slack/

Slack-область каждого файла выгружается в отдельный .bin. Offset считается абсолютным: slack_end = (last_run.addr + last_run.len) * block_size, поэтому данные читаются из правильного места образа.

Live diff (Task 2.2)

python -m mountfile live-diff before.img after.img -o diff.md

Отчёт Markdown: added / modified / removed со размерами и SHA-256.