关键词:只读挂载、losetup、loop 设备、mmls、fls、icat、Sleuth Kit、atime、journal replay、写保护
难度:进阶
前置知识:Linux 挂载机制、磁盘分区结构、NTFS/ext4 文件系统基础
一、概述
拿到镜像之后,最自然的想法是"挂载起来看看里面有什么"。这个想法在便利性上无可指摘,在证据安全上却极其危险——因为操作系统的挂载过程本身就会写盘。
即使加了 -o ro,某些文件系统仍会在挂载瞬间修改超级块、回放日志、更新访问时间:
| 后果 |
严重程度 |
| 原始哈希被破坏,镜像无法再证明与检材一致 |
★★★★★ 证据灭失 |
$MFT / inode 时间戳被更新,污染时间线分析 |
★★★★★ 破坏核心证据 |
| 损坏或恶意构造的文件系统可触发内核漏洞 |
★★★★ 安全风险 |
| 挂载产生的新文件让文件系统状态与镜像初始状态不符 |
★★★ 逻辑不自洽 |
更严重的是,Autopsy、X-Ways、FTK Imager 在设计时就假设"挂载会破坏证据",所以它们根本不提供直接挂载。这一取舍反映了取证学科对挂载风险的共识——这不是工具功能缺失,是刻意的设计。
本篇给出的三条路径,按安全等级排序:
| 路径 |
是否经过内核 |
是否可能写盘 |
适用场景 |
| Sleuth Kit 直接解析 |
❌ 否 |
绝无 |
取证分析的首选,绝大多数场景够用 |
| 只读环设备 + 内核挂载 |
✅ 是 |
有条件(noload + noatime + blockdev --setro 三层) |
需要 ls / cp / grep 等常规工具时 |
| Windows 侧写保护器 + 挂载 |
✅ 是 |
几乎必然 |
只在必要时,且必须有硬件写保护 |
核心原则一句话:能不挂载就不挂载。
Sleuth Kit 直接读镜像文件、自己解析数据结构,完全不经过内核文件系统驱动,因此在物理上不可能触发任何写入——这不是"风险较低",是"风险为零"。
为什么内核挂载挡不住写入是本篇最需要先理解的一件事:-o ro 只保证文件系统层不主动写,而内核 VFS 之外的机制仍会绕过它。
典型翻车场景是 ext4 报告 needs journal recovery(断电后的正常现象),你挂上去,内核照常回放日志,把几十 MB 的元数据改动写进镜像——挂完算一下哈希,已经和采集时完全不同了。
绕过 -o ro 的四类机制(具体对应第四章 4.1 组):
| 机制 |
触发条件 |
写入内容 |
对策 |
| 日志回放 |
ext3/ext4 标记为 dirty、XFS 有未完成日志 |
把日志里的元数据操作重做一遍 |
ext 用 -o noload,XFS 用 -o norecovery |
| 超级块修复 |
文件系统检测到不一致 |
修正 s_state、空闲块计数 |
只能靠块层锁死(blockdev --setro)挡住 |
| atime 更新 |
读取文件时(除非显式 noatime) |
inode 的访问时间 |
-o noatime |
| relatime 重算 |
相对时间戳重算 |
最多每 24 小时一次 |
同上 |
atime 这一条值得单独强调:读取证据文件会改变被读取文件的时间戳,而用 find 遍历目录、用反取证工具扫未分配空间时,几百上千个文件的访问时间会被一次性改掉。
而"最后访问时间"恰恰是取证里最有用的字段之一——不改 atime 不是保守,是保护证据。只要没有显式指定 noatime,现代内核的 relatime 默认也仍然会更新它。
在取证流程中的位置:本篇是分析环节的第一道关卡,处在镜像制作(镜像制作)之后、任何实际内容分析之前。上游是格式问题——E01 需要 EWF 库或先转 RAW,见证据格式与转换;它自己的前置是挂载前的哈希校验,镜像在被接入之前应该先确认哈希与采集记录一致,否则你连"自己有没有毁掉证据"都判断不了。
下游分两条:静态解析路线直接进NTFS 结构与 MFT 解析、ext4 结构与 inode;需要观察运行态行为的走虚拟机仿真。
两条路线的关键区别是:挂载给你文件系统视图,仿真给你程序执行能力——后者风险高一个量级。
本篇能回答什么:镜像里有哪些分区、每个分区的起止扇区与文件系统类型、目录树长什么样、某个文件的元数据与内容是什么、删除文件与未分配空间里有什么、以及接入操作之后镜像是否还是原样(这是必须自己回答的问题)。
本篇不能回答什么:挂载不等于能读——加密卷挂上去也只是一堆随机字节,解密是另一条线(BitLocker 全卷加密取证);挂载给不了你程序运行态——正在运行的进程、内存里的凭据、网络连接的当前状态,静态挂载一个都看不到;以及挂载也不能替代仿真——恶意程序的行为、加密密钥的运行时驻留、自毁逻辑的触发条件,都必须在受控环境里实际运行才观察得到。
读者前提:你需要能读懂 mmls 的分区表输出(本篇 2.5 逐列解释了每个字段,不掌握它后面每一步都会算错偏移)、理解扇区与字节的换算(offset = Start × 512)、并清楚**"只读"是三层机制叠加的结果而不是一个参数**。
如果你还没做镜像或者还没确认镜像哈希,先回镜像制作。Linux 内核安全机制(Landlock、SELinux)的内容在扩展阅读里有指向,本篇不展开。
二、核心原理
2.1 三条接入路径的安全等级
| 路径 |
是否调用内核文件系统驱动 |
是否写盘 |
适用 |
| Sleuth Kit 直接解析 |
❌ 否 |
绝无 |
取证分析的首选 |
| 只读环设备 + 内核挂载 |
✅ 是 |
有条件(见 2.2) |
需要 ls/cp 等常规工具时 |
| Windows 写保护器 + 挂载 |
✅ 是 |
几乎必然 |
只在必要时,且必须用硬件写保护 |
核心原则:能不挂载就不挂载。 Sleuth Kit 直接读取镜像文件、自己解析数据结构,完全不经过内核,因此在物理上不可能触发任何写入。
2.2 为什么"只读挂载"仍然会写盘
这是本篇最重要的原理,也是最多人误解的地方。mount -o ro 只保证文件系统层不主动写,并不禁止一切写入。 内核的 VFS 层把超级块标记为只读,但以下机制仍可能绕过:
| 机制 |
触发条件 |
写入内容 |
| 日志回放(journal replay) |
ext3/ext4 标记为 dirty,XFS 有未完成日志 |
把日志中的元数据操作重做一遍 |
| 超级块修复 |
文件系统检测到不一致 |
修正 s_state、空闲块计数等 |
| atime 更新 |
读取文件时(除非 noatime) |
inode 的访问时间 |
| relatime 更新 |
相对时间戳的重算 |
最多每 24 小时一次 |
最典型的翻车场景:fsck 报告 ext4 "needs journal recovery"(断电时的正常现象),你用 mount -o ro 挂上去——内核照常回放日志,把几十 MB 的元数据改动写进镜像。挂完 sha256sum 一算,哈希和采集时完全不同了。
解决办法:ext 用 -o noload(不加载/回放 journal),XFS 用 -o norecovery(不回放 log),再叠加 blockdev --setro 从块层锁死只读。
2.3 为什么必须禁用 atime
Linux 传统上每次读文件都更新访问时间(atime)。现代内核默认使用 relatime(一天最多更新一次),但只要没有显式指定 noatime,这个行为就仍然存在。
对取证来说这是不可接受的:读取证据文件会改变被读取文件的时间戳;如果还用了 find 遍历目录、用反取证工具扫描未分配空间,几百上千个文件的 atime 会被改。而"访问时间"恰恰是取证分析中最有用的字段之一——它能证明"谁在什么时候碰过这个文件"。
不改 atime 不是保守,是保护证据。
规范做法:所有取证挂载一律加 noatime。如果环境里 /etc/fstab 配了 relatime,命令行参数也要显式覆盖。
2.4 Sleuth Kit 分层架构
TSK 的设计本身就是"不挂载"的产物——它把磁盘解析分成五层,每层工具只做一件事:
| 层 |
工具 |
职责 |
| Image |
img_stat |
镜像格式、扇区大小 |
| Volume |
mmls / mmstat / mmcat |
分区表解析,提取卷 |
| File System |
fsstat / fls / icat / istat |
目录树、文件元数据、文件内容 |
| Data Unit |
blkcat / blkls |
按块号提取 |
| Journal |
jls / jcat |
日志分析 |
这个架构带来三个直接好处:
- 零写入:所有工具都是
open() + pread() 模式
- 能访问内核不给看的东西:删除文件、未分配空间、文件系统元数据
- 能精确定位:每个文件有唯一的 inode/MFT 编号,可作为证据引用
2.5 mmls 输出格式详解
mmls 的输出是后续所有操作的基础,必须读懂每一列:
Slot Start End Length Description
00: Meta 0000000000 0000000000 0000000001 Primary Table (#0)
01: ----- 0000000000 0000000062 0000000063 Unallocated
02: 00:00 0000000063 0009510479 0009510417 NTFS (0x07)
03: ----- 0009510480 0009514259 0000003780 Unallocated
| 列 |
含义 |
用法 |
Slot |
槽位编号 |
提取卷时作为参数(mmcat ... 02) |
Start / End / Length |
起止扇区与长度 |
全部以 512 字节为单位(除非另有提示) |
Description |
卷类型 |
MBR/GPT 类型代码 + 文件系统识别结果 |
00: 00:00 的含义:第一段 00: 是分区表序号(第几个分区表),第二段是该表内的槽位号。00:00 = 第一个分区表的第一项。嵌套的 DOS 扩展分区会出现 01:00 这样的二级编号。
Unallocated 行的价值:它们是分区表未占用的空间。这是反取证软件的默认藏身点,必须用 blkls 或关键字搜索专门检查。
关于 [ntfs] 这类方括号标记:TSK 工具在不同输出位置使用方括号表示"推测/强制"的类型,而 mmls 的 Description 列用的是 NTFS (0x07) 这种"类型名 + MBR 类型码"格式。写脚本解析时不要混淆这两种表示法。
换算偏移:offset_bytes = Start × 512。例如 Start = 0000000063 对应 32256 字节。
三、操作步骤
3.1 第 1 步:Sleuth Kit 不挂载访问(首选)
sudo apt install sleuthkit
img_stat /evidence/DigiForensics.dd # Image type: raw Sector size: 512
mmls /evidence/DigiForensics.dd # 分区布局
fsstat -o 63 /evidence/DigiForensics.dd # 簇大小、$MFT 位置、卷序列号
# 递归全部目录,完整路径(-r 递归,-p 显示完整路径)
fls -o 63 -r -p /evidence/DigiForensics.dd > /evidence/full-listing.txt
# 提取文件(inode 编号从 fls 输出中获取)
icat -o 63 /evidence/DigiForensics.dd 1234-128-3 > /evidence/extracted-file.bin
# 提取文件系统内未分配块做关键字搜索(-o 是文件系统起始扇区,不是 Slot 号)
blkls -o 63 /evidence/DigiForensics.dd | strings | grep -i 'target-string'
# 按 Slot 提取整个卷
mmcat /evidence/DigiForensics.dd 2 > /evidence/partition2.dd
inode 编号格式:元数据地址-序列号-类型。NTFS 中 11443-128-4 表示 MFT 条目 11443、属性序列 128、类型 4($DATA)。
这个编号本身就是证据引用坐标——报告里写清"文件 X 对应 MFT 条目 11443",比"某文件夹里有个文件"精确得多。
整个流程零写入,可以在原始主副本上直接运行,无需制作工作副本。
3.2 第 2 步:只读环设备挂载
当确实需要用 ls/cp/grep 等常规工具浏览文件内容时才用。核心是三层只读防护:
# ① 建立只读环设备(-r 只读,-P 扫分区生成 /dev/loop0p1、p2 ...)
sudo losetup -r --find --show -P /evidence/DigiForensics.dd # 输出:/dev/loop0
# ② 在块层锁死只读(双保险)
sudo blockdev --setro /dev/loop0
blockdev --getro /dev/loop0 # 输出 1 = 已只读
losetup -r 的关键意义:它让块设备层拒绝一切写请求,即使后续想 mount -o rw 也会失败——这是防止"手滑"最有效的技术手段。
挂载、浏览、卸载(必须完整走完):
sudo mkdir -p /mnt/evidence
# 挂载:noload / norecovery / noatime 三者缺一不可
sudo mount -t ext4 -o ro,noload,noatime,noexec /dev/loop0p1 /mnt/evidence
sudo mount -t xfs -o ro,norecovery,noatime,noexec /dev/loop0p2 /mnt/evidence
sudo mount -t ntfs3 -o ro,noatime,noexec /dev/loop0p3 /mnt/evidence
# NTFS 备选:装 ntfs-3g 后用 -t ntfs-3g
# 裸文件系统镜像(无分区表):sudo mount -o ro,noload,noatime,loop 镜像文件 /mnt/evidence
# 浏览结束后立即卸载并分离环设备
sudo umount /mnt/evidence
sudo blockdev --setrw /dev/loop0 # 解除块层只读
sudo losetup -d /dev/loop0 # 分离,否则环设备泄漏
3.3 第 3 步:挂载后立即验证哈希
这是整个流程中不可省略的一步。 挂载前后各算一次:
sha256sum /evidence/DigiForensics.dd # 挂载前
# ...执行挂载与浏览,卸载后...
sha256sum /evidence/DigiForensics.dd # 卸载后
两个哈希必须一致。 不一致意味着挂载过程修改了镜像,此时立即停止所有操作,记录修改前后的哈希值,从主副本重新制作工作副本,并在报告中如实记录这一事件——隐瞒比事件本身更严重。
3.4 第 4 步:挂载 E01 镜像
E01 是压缩容器,不能直接 losetup,需先用 FUSE 桥接(命令见第七节)。ewfmount 暴露为原始镜像文件供 TSK 读取;xmount 支持更多格式且输出虚拟可写的 dd——所有写入只落在内存缓存中,