Skip to content

vmdk-forensic

Read VMware VMDK disk images others give up on, then audit them for tampering. Two crates in one workspace: the vmdk-core reader (imported as vmdk) presents the virtual disk as a plain Read + Seek byte stream — and recovers data from a damaged disk through the redundant grain directory that qemu-img and libvmdk throw away — while the vmdk-forensic analyzer turns the structure into severity-graded, evidence-grade findings.

The two crates

Crate Role Import cargo add
vmdk-core Read-only VMDK reader: decoded virtual-sector Read + Seek, RGD-fallback recovery, ddb provenance use vmdk::… cargo add vmdk-core
vmdk-forensic Integrity analyzer → canonical forensicnomicon::report::Finding (re-exports the reader) use vmdk_forensic::… cargo add vmdk-forensic

Quick start

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<dyn std::error::Error>>(())

What makes this different from qemu-img and libvmdk

Most VMDK readers answer one question: "give me the bytes." vmdk answers the questions a digital forensics examiner actually needs — and reads disks the others give up on: redundant-GD fallback recovery behind a damaged primary grain directory, redundant-GD content validation, structural integrity scans for dangling GD/GT/grain pointers, full ddb.* disk-database provenance (adapter, geometry, UUID, tools/HW version), header provenance (unclean-shutdown flag, FTP-ASCII-mangling check), Change Block Tracking reference, longContentID resolution, Raw Device Mapping extent enumeration, streaming SHA-256 + MD5 of the virtual disk — all in pure Rust with zero unsafe and no C library.

Forensic recovery

VMware writes the grain tables twice — the grain directory (GD) and a redundant copy (RGD) point to separate physical copies. qemu-img and libvmdk read only the primary and fail when it is damaged. vmdk uses the redundant copy to keep reading:

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<dyn std::error::Error>>(())

Recovery is opt-in and never changes a healthy read; without it a dangling pointer simply errors (the safe default).

Security

vmdk is built to run on untrusted, potentially crafted disk images: no panics on malicious input (bounds-checked header-derived allocations, clamped reads, capped compressed-grain sizes), allocation-amplification hardened (numGTEsPerGT capped at the spec value of 512), zero unsafe (unsafe_code = "forbid" workspace-wide), and four cargo fuzz targets covering the open path, full read surface, RGD recovery, and the forensic pipeline. COWD and seSparse output is cross-validated byte-for-byte against qemu-img convert -O raw.

See Validation and Implementation Notes for the detailed evidence, and the project README for the full CLI, format matrix, and API reference.


Privacy Policy · Terms of Service · © 2026 Security Ronin Ltd