SecurityRonin/vmdk
GitHub: SecurityRonin/vmdk
纯Rust实现的VMDK只读读取器,支持损坏镜像恢复和取证元数据展示。
Stars: 0 | Forks: 0
# vmdk
[](https://crates.io/crates/vmdk)
[](https://docs.rs/vmdk)
[](LICENSE)
[](https://github.com/SecurityRonin/vmdk/actions)
[](https://github.com/sponsors/h4x0r)
纯 Rust,只读 VMware VMDK 磁盘镜像读取器。将虚拟磁盘呈现为普通的 `Read + Seek` 字节流——并且独特的是,**通过 `qemu-img` 和 `libvmdk` 丢弃的冗余粒度目录恢复损坏磁盘中的数据**,同时呈现它们丢弃的取证元数据。
## 命令行工具
```
$ cargo run --bin vmdk -- info disk.vmdk
```
```
File: disk.vmdk
Format: VMDK v1 (monolithicSparse)
Virtual disk size: 4,194,304 bytes (4.00 MiB)
Sector size: 512 bytes
Sectors: 8,192
Grain size: 128 sectors (64 KiB)
Compressed: no
CID: dc80b6c7
Descriptor: 17 lines (see --descriptor)
```
六个子命令——`info`、`map`、`dump`、`hash`、`verify`、`diff`——将常见的 `qemu-img` 工作流程折叠到一个二进制文件中:
```
$ vmdk verify disk.vmdk
RGD: OK (matches primary GD)
Allocated grains: 3 (196,608 bytes)
Integrity: OK (3 grains checked, no out-of-bounds pointers)
Status: OK
```
`dump`、`hash`、`map` 和 `verify` 接受 `--recover`:当主粒度目录损坏时,读取通过冗余粒度目录解决,因此损坏后的数据仍然可以提取。
```
$ vmdk verify damaged.vmdk # primary GD is corrupt
Integrity: FAIL — 1 out-of-bounds grain table(s) … Status: FAILED
$ vmdk verify --recover damaged.vmdk # resolve through the redundant GD
Integrity: OK (1 grains checked, no out-of-bounds pointers)
Recovered 1 grain(s) via the redundant grain directory
Status: OK
```
`dump` 将原始虚拟磁盘字节写入 stdout 或文件(`-o`),字节范围(`--offset` / `--length`),或十六进制视图(`--hex`)——直接将其传递给文件系统工具(NTFS、ext4、…)以读取虚拟机的文件。`verify` 在干净的情况下退出 `0`,在损坏的情况下退出 `1`,因此它可以直接进入分类管道。
## Rust 库
```
[dependencies]
vmdk = "0.4"
```
## 快速入门
```
use vmdk::VmdkReader;
use std::io::{Read, Seek, SeekFrom};
// Open any `Read + Seek` source — a File, a Cursor, another container reader.
let mut disk = VmdkReader::open(std::fs::File::open("disk.vmdk")?)?;
println!("virtual size: {} bytes", disk.virtual_disk_size());
// Read decoded virtual sectors like any byte stream — sparse/compressed grains
// are decompressed and zero-filled transparently.
let mut first_mib = vec![0u8; 1 << 20];
disk.seek(SeekFrom::Start(0))?;
disk.read_exact(&mut first_mib)?;
# Ok::<(), Box>(())
```
对于基于路径的图像(具有伴随文件)——`monolithicFlat`、`twoGbMaxExtent*` 分割集、原始设备映射——使用 `VmdkFileReader::open_path`,它会为您定位和打开范围文件。对于快照/增量树,使用 `VmdkChainReader::open`,它在父链上叠加一个增量。
## 与 `qemu-img` 和 `libvmdk` 的不同之处
大多数 VMDK 读取器回答一个问题:“给我字节。”`vmdk` 回答数字取证检查员真正需要的问题——并且读取磁盘,其他人放弃:
| 功能 | qemu-img / libvmdk | vmdk |
|---|---|---|
| 稀疏 / 流优化 / 平坦读取 | ✅ | ✅ |
| COWD (`vmfsSparse`/`vmfsThin`) + seSparse (VMFS6) | 部分支持 | ✅ |
| 快照 / 增量链遍历 | ✅ | ✅ |
| **从损坏的主 GD 恢复数据**(冗余 GD 回退) | ✗ | ✅ |
| **从冗余副本恢复单个丢失的粒度表条目** | ✗ | ✅ |
| 冗余-GD 验证(粒度表 *内容*,而不是指针) | ✗ | ✅ 通过 `vmdk-forensic` |
| 结构完整性扫描(悬空 GD/GT/粒度指针) | ✗ | ✅ 通过 `vmdk-forensic` |
| `ddb.*` 磁盘数据库(适配器、几何形状、UUID、工具/HW 版本) | 被丢弃 | ✅ |
| 标题来源——不干净关闭标志、FTP-ASCII 损坏检查 | ✗ | ✅ 通过 `vmdk-forensic` |
| 变更块跟踪 (`-ctk`) 参考 | ✗ | ✅ |
| `longContentID` 解析(`CID == 0xFFFFFFFE` 的哨兵) | ✗ | ✅ |
| 原始设备映射 (`VMFSRDM`) 范围枚举 | ✗ | ✅ |
| 流式 SHA-256 + MD5 的虚拟磁盘 | ✗ | ✅ |
| 对抗性输入加固 + 模糊测试 | ✗ | ✅ |
| 纯 Rust,零 `unsafe`,没有 C 库 | ✗ | ✅ |
## 格式
VMware 虚拟磁盘格式规范中的每个 VMDK `createType` 和范围类型(与 QEMU `block/vmdk.c` 和 `libvmdk` 进行交叉检查):
| `createType` | 备注 |
|---|---|
| `monolithicSparse`、`streamOptimized` | 标题 v1/v2/v3;DEFLATE 粒度;`GD_AT_END` 脚注 |
| `monolithicFlat`、`vmfs`、`vmfsPreallocated`、`vmfsEagerZeroedThick` | 预分配平坦范围 |
| `twoGbMaxExtentSparse`、`twoGbMaxExtentFlat` | 分割 2 GB 范围集 |
| `vmfsSparse`、`vmfsThin` | ESXi COWD 副本写入稀疏 |
| `seSparse` | vSphere 6.5+ 空间高效稀疏(半字节类型,位旋转粒度) |
| `vmfsRaw`、`vmfsRawDeviceMap`、`vmfsPassthroughRawDeviceMap`、`fullDevice`、`partitionedDevice` | 设备 / 原始 LUN 映射 |
| `custom` | 任意范围混合,由范围类型路由 |
范围类型:`FLAT`、`VMFS`、`VMFSRAW`、`VMFSRDM`、`ZERO`、`SPARSE`、`VMFSSPARSE`、`SESPARSE`;访问 `RW` / `RDONLY` / `NOACCESS`。`ZERO` 和 `NOACCESS` 区域读取为零,而不接触磁盘。
## 取证恢复
VMware 写入粒度表 **两次**——粒度目录(GD)和冗余副本(RGD)指向不同的物理副本。`qemu-img` 和 `libvmdk` 只读取主副本,当它损坏时失败。`vmdk` 使用冗余副本来继续读取:
```
use vmdk::VmdkReader;
use std::io::Read;
let mut disk = VmdkReader::open(std::fs::File::open("damaged.vmdk")?)?;
// Opt in to recovery, then read normally — damaged pointers resolve through the RGD.
disk.enable_rgd_fallback();
let mut buf = vec![0u8; 1 << 20];
let _ = disk.read(&mut buf);
println!("recovered {} grain(s) via the RGD", disk.rgd_recovery_count());
# Ok::<(), Box>(())
```
恢复是可选的,并且永远不会更改健康的读取;如果没有它,悬空指针只是错误(安全的默认值)。要首先对损坏的图像进行分类——RGD 可以恢复多少主粒度目录,以及篡改/异常检测——请使用配套的 `vmdk-forensic` crate。
## 取证元数据
文本描述符携带其他读取器解析然后丢弃的来源信息。`vmdk` 展示了所有这些:
```
use vmdk::VmdkReader;
let mut disk = VmdkReader::open(std::fs::File::open("disk.vmdk")?)?;
let ddb = disk.disk_database(); // ddb.* disk database
println!("adapter: {:?}", ddb.adapter_type); // ide / lsilogic / pvscsi …
println!("geometry: {:?}", ddb.geometry); // CHS cylinders/heads/sectors
println!("disk UUID: {:?}", ddb.uuid);
println!("HW / tools: {:?} / {:?}", ddb.virtual_hw_version, ddb.tools_version);
println!("CBT file: {:?}", disk.change_track_path()); // -ctk.vmdk reference
println!("content ID: {}", disk.effective_content_id()); // resolves longContentID
# Ok::<(), Box>(())
```
标题来源(不干净关闭标志、FTP-ASCII 损坏检查)和完整性/异常分析位于配套的 `vmdk-forensic` crate 中——请参阅 [相关](#related)。
## API 突出
| 方法 | 目的 |
|---|---|
| `VmdkReader::open(reader)` | 打开任何 `Read + Seek` 源 |
| `VmdkFileReader::open_path(path)` | 打开基于路径的图像(平坦/多范围/设备映射) |
| `VmdkChainReader::open(path)` | 在其父快照链上叠加一个增量 |
| `read` / `seek` (`std::io`) | 解码虚拟扇区字节流 |
| `info()` → `VmdkInfo` | 版本、CID、几何形状、压缩、描述符、磁盘数据库 |
| `is_allocated(lba)` / `iter_allocated_grains()` | 稀疏映射查询 |
| `hash()` → `VmdkDigest` | 虚拟磁盘的流式 SHA-256 + MD5 |
| `validate_rgd()` / `check_integrity()` | 冗余-GD + 结构完整性 |
| `grain_directory_recovery()` / `enable_rgd_fallback()` / `rgd_recovery_count()` | RGD 恢复 |
| `disk_database()` / `header_provenance()` / `change_track_path()` / `effective_content_id()` | 取证元数据 |
`serde` 在公共报告类型上派生,位于 `serde` 功能之后。
## 安全性
`vmdk` 是构建在不受信任、可能被篡改的磁盘镜像上运行的:
- **恶意输入不会引发恐慌**——每个从标题字段派生的分配都会进行边界检查;读取会被限制;压缩粒度大小会被限制。
- **分配放大加固**——`numGTEsPerGT` 限制在规范值(512)上,与 QEMU 匹配,因此一个被篡改的标题不能驱动多吉字节粒度表的分配。
- **零 `unsafe`**——`unsafe_code = "forbid"` 工作区范围;没有 C 依赖。
- **模糊测试**——三个 `cargo fuzz` 目标涵盖了公开路径、完整的读取/扫描/完整性表面和 RGD 恢复路径;在每次更改时在 CI 上运行,并按计划进行更深入的测试。
```
# 需要 nightly Rust 和 cargo-fuzz
rustup install nightly
cargo install cargo-fuzz
cargo +nightly fuzz run fuzz_open
cargo +nightly fuzz run fuzz_read
cargo +nightly fuzz run fuzz_recover
```
## 测试
280 多个测试(单元 + 集成)覆盖了每个公共 API、每个格式分支、恢复路径和对抗性输入。COWD 和 seSparse 输出与 `qemu-img convert -O raw` 进行逐字节交叉验证——合成固定装置和读取器不能共享盲点。覆盖率在 CI 中得到强制执行。
```
cargo test
cargo +stable llvm-cov --workspace --all-features --summary-only
```
## 相关
**vmdk** 给您虚拟磁盘的字节。这些 crate 以相同的方式读取其他容器格式——解码扇区流的纯 `Read + Seek`:
| Crate | 格式 |
|---|---|
| [`ewf`](https://github.com/SecurityRonin/ewf) | E01 / 专家证人格式(EnCase、FTK Imager) |
| [`vhdx`](https://github.com/SecurityRonin/vhdx) | 微软 VHDX(Hyper-V、Azure) |
| [`vhd`](https://github.com/SecurityRonin/vhd) | 旧版 VHD(Virtual PC / Hyper-V Gen-1) |
| [`qcow2`](https://github.com/SecurityRonin/qcow2) | QEMU / KVM QCOW2 |
| [`dd`](https://github.com/SecurityRonin/dd) | 原始 / 平坦 / dd 图像 |
使用其取证兄弟对 VMDK 进行篡改、损坏和可恢复性审计:
| Crate | 角色 |
|---|---|
| [`vmdk-forensic`](https://github.com/SecurityRonin/vmdk) | VMDK 完整性分析——RGD 判决、悬空指针扫描、恢复分类、标题来源、分级异常 |
一旦您有了字节,这些解析器就会分析分区布局:
| Crate | 方案 |
|---|---|
| [`mbr-forensic`](https://github.com/SecurityRonin/mbr-forensic) | 主引导记录——异常、空闲区挖掘、引导代码 ID |
| [`gpt-forensic`](https://github.com/SecurityRonin/gpt-forensic) | GUID 分区表——备份标题协调、CRC32 |
| [`disk-forensic`](https://github.com/SecurityRonin/disk-forensic) | **协调器**——自动检测 MBR/GPT/APM 并调度 |
容器格式知识(魔术数字、标题布局、编码规则)位于 `forensicnomicon` 中。
[隐私政策](https://securityronin.github.io/vmdk/privacy/) · [服务条款](https://securityronin.github.io/vmdk/terms/) · © 2026 Security Ronin Ltd
标签:Rust, VMDK, VMware, Zenmap, 代码库, 冗余, 取证, 只读, 可视化界面, 开源, 数据完整性, 数据恢复, 数据恢复工具, 数据提取, 文件格式, 磁盘分析, 磁盘管理, 磁盘镜像, 系统工具, 网络流量审计, 虚拟化, 许可证, 读取, 赞助, 软件开发, 通知系统