SecurityRonin/dar-forensic
GitHub: SecurityRonin/dar-forensic
纯 Rust 编写的 DAR 归档取证读取器与异常审计器,为移动取证(Passware/Cellebrite)提取数据提供安全的只读解析、提取和审计能力。
Stars: 0 | Forks: 0
# dar-forensic
[](https://crates.io/crates/dar-forensic)
[](https://docs.rs/dar-forensic)
[](LICENSE)
[](https://github.com/SecurityRonin/dar-forensic/actions)
[](https://github.com/sponsors/h4x0r)
纯 Rust 编写的 Denis Corbin **DAR (Disk ARchiver)** 归档读取器 —— 移动取证工具(Passware Kit Mobile、Cellebrite)使用该格式进行全文件系统提取。它可以枚举目录,直接定位到任意文件进行随机提取 —— 透明地解压 gzip、bzip2、xz、zstd、lz4 和 lzo,并支持读取多卷(分片)归档 —— 同时经过安全加固,可安全处理不受信任的取证证据。零 `unsafe`,无 GPL,无 C 绑定。
## 两个 crate
| Crate | 作用 | crates.io |
|-------|------|-----------|
| **`dar-core`** | 只读解析器 —— 打开、枚举、定位提取、CRC 校验 | `cargo add dar-core` |
| **`dar-forensic`** | 取证级读取器 + 异常审计器(`audit()` → 分级发现,`write_bodyfile()`) | `cargo add dar-forensic` |
`dar-forensic` 重新导出了完整的 `dar-core` 读取器,因此对于取证工作来说,仅使用分析器 crate 就足够了:
```
[dependencies]
dar-forensic = "0.7"
```
## 快速开始
```
use std::fs::File;
use dar_forensic::DarReader;
// `open` takes anything Read + Seek — a File, or a Cursor over bytes.
let mut reader = DarReader::open(File::open("userdata.1.dar")?)?;
for entry in reader.entries() {
println!("{} ({} bytes)", entry.path_lossy(), entry.size);
}
// Extract one file — a direct seek to its catalog offset, no scanning.
let data = reader.extract("root/etc/hostname")?;
println!("{}", String::from_utf8_lossy(&data));
// Integrity check — recompute the stored per-file CRC over the data.
println!("{}", reader.verify("root/etc/hostname")?); // CRC match | CRC mismatch: …
// Forensic audit — flag catalogue anomalies (metadata only, no data read).
for finding in reader.audit() {
// e.g. [MEDIUM] DAR-PATH-TRAVERSAL: entry `../../etc/cron.d/x` contains a `..` …
eprintln!("{finding}");
}
// Timeline export — write a Sleuth Kit bodyfile straight into `mactime`.
reader.write_bodyfile(&mut std::io::stdout())?;
# Ok::<(), dar_forensic::DarError>(())
```
## 独特之处
DAR 是一种 C++ 格式;其参考实现(`libdar`)是带有 C 绑定的 GPL 软件,而 crates.io 上的 `dar` 名称只是一个空的占位符。`dar-forensic` 是第一个独立的、轻依赖的 Rust 读取器 —— 并且它是专为取证用途而构建的,因为这里的归档文件是*来自潜在恶意来源的证据*:
| | libdar (C++) | `dar-forensic` |
|---|---|---|
| 语言 / 链接 | C++、GPL、C FFI | 纯 Rust、MIT、`unsafe_code = "deny"` |
| 读取 DAR 格式 1–11 | ✅ | ✅(格式 1 和 7–11 已通过真实归档验证) |
| 禁用 Tape-marks 的归档(Passware / 移动端) | ✅ | ✅ |
| 随机访问提取(`Read + Seek`) | ✅ | ✅ — 可与 `ewf`、`vmdk` 等组合使用 |
| 透明的 gzip / bzip2 / xz / zstd / lz4 / lzo 解压 | ✅ | ✅ — 纯 Rust 解码器,无 C 依赖 |
| 多卷(分片)归档 | ✅ | ✅ — `open_slices()`;文件数据透明跨越多个分片 |
| 针对 90+ GiB 归档的尾部扫描(约读取 107 MiB,而非 99 GiB) | — | ✅ |
| 取证异常审计(`audit()` → 按严重程度分级的发现) | — | ✅ — 目录不完整、路径遍历、绝对路径等……(支持 serde 导出) |
| 时间线导出(Sleuth Kit bodyfile → `mactime`) | — | ✅ — 直接从目录调用 `write_bodyfile()` |
| 针对恶意输入进行加固(无 panic / OOM / 向后定位) | — | ✅ |
| 持续模糊测试 | — | ✅ `cargo fuzz` |
| 100% 行覆盖率,由 CI 强制执行 | — | ✅ |
### 关于“Passware 变体”的说明
由 Passware Kit Mobile 写入的归档没有 `seqt_catalogue` 转义符,这曾被认为是特定供应商的专有格式。事实并非如此:该转义符是一个*可选的顺序读取 tape mark*,而 Passware 只是禁用了 tape marks 来写入归档(等同于 `dar -at`)。它们是**标准的 DAR** —— 官方 dar 同样可以读取它们。在这种情况下,`dar-forensic` 通过其 `ref_data_name` 标签(一个真实的结构字段,与分片标签相同的 10 字节)来定位目录,因此它既能读取带有 tape marks 的归档,也能读取无 tape marks 的归档。
## 异常代码
`audit()` 仅读取目录(不包含条目数据),并返回按严重程度分级的 `Anomaly` 值,最严重的排在最前面。每个值都带有一个稳定的、机器可读的 `code`(已公开的契约)、一个 `severity`(严重程度)以及人类可读的说明。这些发现是**观察结果,而非最终结论** —— 结论由分析师自行得出。
| `code` | 严重程度 | 标记内容 |
|--------|----------|---------------|
| `DAR-CATALOG-INCOMPLETE` | 高 | 目录提前结束 —— 恢复的条目数少于归档声明的数量(截断或损坏) |
| `DAR-PATH-ABSOLUTE` | 中 | 条目路径以 `/` 开头 —— 提取时会超出预期根目录 |
| `DAR-PATH-TRAVERSAL` | 中 | 条目路径包含 `..` 组件 —— 提取时会导致目录遍历 |
| `DAR-PATH-DUPLICATE` | 低 | 同一路径在目录中出现多次 |
| `DAR-TIME-FUTURE` | 低 | 某个 `atime`/`mtime`/`ctime` 处于遥远的未来 —— 可能是时间戳被篡改 |
| `DAR-NAME-CONTROL` | 低 | 条目名称包含控制字符(`< 0x20` 或 `0x7f`) —— 可能会导致终端注入 / 隐蔽隐藏 |
启用 `serde` 特性后,`Anomaly` 即可实现 `Serialize`,以便进行 JSON 或结构化导出。
## 格式支持
| DAR 格式 | `version_string` | 状态 |
|------------|------------------|--------|
| 格式 11 (dar 2.7–2.8) | `"0;3"` (11.3) | 支持 —— 已通过 dar 2.8.5 测试用例验证 |
| 格式 10 (dar 2.6) | `"0:1"` | 支持 —— 已通过 dar 2.6.16 测试用例验证 |
| 格式 9 (dar 2.5) | `"090"` | 支持 —— 已通过 dar 2.5.3 测试用例**以及真实的 92 GiB Passware 归档**验证 |
| 格式 8 (dar 2.4) | `"081"` | 支持 —— 已通过 dar 2.4.24 测试用例验证 |
| 格式 7 (dar 2.3) | `"07"` | 支持 —— 已通过 dar 2.3.12 测试用例验证 |
| 格式 2–6 (dar 2.0–2.3) | `"02"`–`"06"` | 与格式 7 使用相同的旧版语法;已解析但尚未通过测试用例验证 |
| 格式 1 (dar 1.0.x) | `"01"` | 支持 —— 已通过真实的 dar 1.0.0 归档验证(无标志 inode,`size·offset` cat_file,无 CRC) |
| 开启**或**关闭 Tape marks | — | 两者均支持(例如 Passware 将其关闭) |
| 创建 / 写入归档 | — | 不支持(仅为读取器) |
格式版本即为头部的 `version_string`,每个字节为 `value + 48`(`"090"` → 9,`"0:1"` → 10.1)。格式 ≤ 7 在结构上有所不同 —— 没有 `seqt_catalogue` 转义符(通过末尾的 *terminateur* 尾部定位目录),使用 `u16` 的 uid/gid、纯秒数时间戳以及固定的 2 字节 CRC;格式 1 的差异更大 —— 没有 inode 标志字节,且只有 `size·offset` 的文件记录没有 CRC。压缩的 pre-8 归档不包含逐条目的编解码器字节,因此归档全局编解码器同时作用于目录和每个条目。完整的各版本布局(通过对权威 libdar 源码进行逆向工程记录得出)详见 [docs/implementation-notes.md](docs/implementation-notes.md) §11–§12。
### 适用范围与限制
- **只读** —— 不创建或修改归档。
- **解压:gzip、bzip2、xz、zstd、lz4、lzo** —— 这六种格式均会针对压缩目录和提取的条目数据进行透明解压(使用纯 Rust 解码器,针对解压炸弹进行了防御限制),适用于 dar 的单流和逐块(`block_compressor`)两种模式。加密条目会被*列出*,但 `extract()` 会返回明确的错误,而不是返回错误的字节 —— 解密不在适用范围内。
- **所有编解码器始终参与编译** —— 取证读取器必须能够读取它遇到的每一个变体,因此这六种解压缩编解码器不是可选的 Cargo 特性。唯一可选的特性是 `serde`(用于结构化的 `audit()` 导出)。
- **CRC 校验** —— `verify(path)` 会根据解压后的数据重新计算 libdar 的逐文件 CRC,并将其与目录中存储的值进行比较,返回 `Match`、`Mismatch { stored, computed }` 或 `NotStored`(edition-1 归档不记录 CRC)。它永远不会拒绝返回数据字节:未能通过 CRC 校验的数据依然可以被 `extract` 提取出来,用于对损坏情况进行分析。
## 安全性
`dar-forensic` 专为处理可能来自受损或对抗性源的归档而设计:
- **面对恶意输入不会崩溃 (panic)** —— 每个由攻击者控制的长度和偏移量都经过了边界或溢出检查。
- **无内存分配炸弹** —— 伪造的 `stored_size` 会在*任何分配之前*与真实的归档长度进行验证。
- **无向后定位** —— 转换为负 `i64` 定位的长度会被直接拒绝。
- **有界解码** —— infinint 要么是 `u64` 要么报 `Corrupt` 错误(绝不默默截断);以 NUL 结尾的名称有长度上限;terminateur 扫描也是有边界的。
- **零 `unsafe`** 并持续进行模糊测试。
### 运行模糊测试目标
```
rustup install nightly
cargo install cargo-fuzz
# 三个目标:解析器 (fuzz_open)、完整读取+提取 (fuzz_read),
# 以及审计管线 (fuzz_forensic)
cargo +nightly fuzz run fuzz_open
```
## 测试
包含 187 个测试 —— 单元测试(私有辅助函数 + 每个错误分支)、合成归档集成测试,以及真实测试用例集成测试 —— 达到 **100% 的库代码行覆盖率,并在 CI 中强制执行**(`cargo llvm-cov`,lcov 门控),同时设有第二道门控,确保公共 API(`tests/`)测试套件维持在同一标准。已提交且可复现的测试用例涵盖了格式 7–11(每个 dar 版本一个)、全部六种 `dar -z` 编解码器(gzip/bzip2/xz/zstd/lz4/lzo),以及逐块和多卷(分片)归档。解析过程还针对真实的 dar-1.0.0 edition-1 归档、机密的 92 GiB Passware Kit Mobile 归档(格式 9,637,698 个条目),以及使用 `dar_xform` 重新分片为 13 卷的真实 52 GB Android 提取数据(302,401 个条目;每一次提取的每一字节都与单文件读取器完全一致)进行了逐字节的额外验证 —— 这些均未提交到代码库中。最后一个真实的归档捕获到了两个任何合成测试用例都无法发现的漏洞(详见 `docs/implementation-notes.md`)。该解析器历经数百万次 `cargo fuzz` 执行,未发生任何崩溃。
```
cargo test
cargo install cargo-llvm-cov && cargo llvm-cov --lcov --output-path lcov.info
```
## 相关 crate
`dar-forensic` 读取 DAR 归档*内部*的文件。当归档本身被封装在磁盘映像容器中时,以下 crate 提供了相同的 `Read + Seek` 接口以供数据输入:
| Crate | 格式 |
|-------|--------|
| [`ewf`](https://github.com/SecurityRonin/ewf) | E01 / Expert Witness Format (EnCase, FTK Imager) |
| [`aff4`](https://github.com/SecurityRonin/aff4) | AFF4 v1 (Evimetry) |
| [`vmdk`](https://github.com/SecurityRonin/vmdk) | VMware VMDK |
| [`vhdx`](https://github.com/SecurityRonin/vhdx) | Microsoft VHDX (Hyper-V, Azure) |
| [`vhd`](https://github.com/SecurityRonin/vhd) | Legacy VHD |
| [`qcow2`](
标签:DAR格式, Rust, 可视化界面, 异常检测, 数字取证, 数据解析, 移动取证, 网络流量审计, 自动化脚本, 通知系统