vhdx-forensic
Audit a Hyper-V VHDX disk image for tampering and corruption in pure Rust — point it at raw bytes and get back graded structural findings across 63 anomaly codes.
[]
= "0.2"
use ;
let image = read?;
let anomalies = new.analyse;
// Surface only Error/Critical findings for triage
for a in anomalies_at_least
# Ok::
vhdx-forensic is the integrity analyzer. It reads the MS-VHDX container format through its sibling reader vhdx-core (imported as vhdx), then audits the raw structure for anomalies consistent with tampering, corruption, or anti-forensic GUID/log wiping. It emits forensicnomicon::report::Finding and offers optional in-memory CRC repair. No unsafe code, no C bindings, no GPL.
When to use this
You have a VHDX disk image (the native Windows virtual disk format used by Hyper-V, WSL2's ext4.vhdx, and Azure) and you want to:
- Audit structural integrity before mounting or analysing — detect tampered headers, BAT corruption, ghost data, and GUID wiping
- Produce an evidence-grade record of every structural anomaly with forensic significance and MITRE ATT&CK context attached
- Read raw sectors in a forensic context — offline, read-only, no Windows storage stack side-effects (via the bundled
vhdx-corereader)
This crate is the CONTAINER-layer analyzer in the Issen forensic stack: it sits between raw byte sources (E01/EWF via ewf, raw files) and filesystem parsers (ext4fs-forensic, ntfs-forensic).
Forensic integrity analysis (VhdxIntegrity)
VhdxIntegrity works on raw bytes and does not require a fully valid structure — it analyses as much as it can regardless of how many anomalies it finds. It produces findings across six phases: container/magic, CRC integrity, header semantics, region layout, metadata, and BAT/data-block analysis. Each VhdxIntegrityAnomaly variant carries a stable code string, a graded severity(), a forensic_significance() narrative, and mitre_techniques() — surfaced as "consistent with," never as a verdict.
use VhdxIntegrity;
let image = read?;
for anomaly in new.analyse
# Ok::
Reading sectors (VhdxReader)
The reader is re-exported from vhdx-core. VhdxReader implements std::io::Read + std::io::Seek, so it can be dropped in anywhere an ordinary file handle is expected.
use ;
use VhdxReader;
let mut reader = open?;
println!;
let mut sector = ;
reader.read_exact?;
reader.seek?;
reader.read_exact?;
# Ok::
In-memory repair (VhdxRepair)
use ;
let image = read?;
let mut repair = new;
let report = repair.attempt_repair;
if report.any_repaired
if report.any_unresolved
VhdxRepair reconstructs CRC32C checksums for header and region table copies from valid peer copies — it does not alter payload data.
Anomaly codes
Each variant exposes a stable code string (scheme-prefixed VHDX-…). There are 63 in total; representative codes by category:
| Severity | Category | Example code strings |
|---|---|---|
| Critical | Container / magic | VHDX-BAD-MAGIC, VHDX-CONTAINER-TRUNCATED, VHDX-BOTH-HEADER-COPIES-INVALID |
| Error | CRC integrity | VHDX-HEADER-CHECKSUM-MISMATCH, VHDX-REGION-TABLE-CHECKSUM-MISMATCH |
| Error | Header semantics | VHDX-HEADER-COPY-MISMATCH, VHDX-REGION-TABLE-COPY-MISMATCH |
| Error | Region layout | VHDX-REGIONS-OVERLAP, VHDX-REGION-BEYOND-CONTAINER, VHDX-LOG-IN-RESERVED-ZONE |
| Error | Log integrity | VHDX-LOG-ENTRY-CRC-MISMATCH, VHDX-LOG-ENTRY-GUID-MISMATCH |
| Error | BAT structure | VHDX-BAT-ENTRIES-OVERLAP, VHDX-BAT-ENTRY-BEYOND-CONTAINER |
| Error | Metadata | VHDX-METADATA-ITEMS-OVERLAP, VHDX-MISSING-PARENT-LOCATOR, VHDX-VIRTUAL-DISK-SIZE-UNDERREPORTED |
| Warning | GUID wiping | VHDX-FILE-WRITE-GUID-ALL-ZEROS, VHDX-DATA-WRITE-GUID-ALL-ZEROS, VHDX-VIRTUAL-DISK-ID-ALL-ZEROS |
| Warning | BAT anomalies | VHDX-GHOST-DATA-IN-ABSENT-BLOCK, VHDX-UNDEFINED-BLOCK-STATE, VHDX-UNMAPPED-BLOCK-IN-NON-DIFFERENCING |
| Warning | Structural | VHDX-DIFFERENCING-DISK, VHDX-LEAVE-BLOCKS-ALLOCATED-SET, VHDX-TRAILING-DATA |
| Info | Log state | VHDX-DIRTY-LOG, VHDX-INTER-REGION-GAP-NON-ZERO |
Hardening against crafted images
VHDX headers and region tables are CRC32C-protected, but the BAT (Block Allocation Table) and metadata fields are not. A crafted image can carry semantically invalid values while maintaining valid CRCs. This crate validates all of the following before any arithmetic that depends on them:
| Field | Constraint enforced |
|---|---|
BlockSize |
Power-of-two in [1 MB, 256 MB] |
LogicalSectorSize |
Exactly 512 or 4096 |
VirtualDiskSize |
Non-zero, ≤ 64 TiB, multiple of sector size |
Region entry file_offset + length |
Within container bounds |
Region entry_count |
Capped at 2048 (DoS guard) |
| Container size | Minimum 2.5 MB before any offset arithmetic |
| BAT offset arithmetic | checked_mul/checked_add — AddressOverflow instead of panic |
Differencing disks (HasParent = true) can be opened via VhdxReader::from_bytes_with_parent(child, parent). VhdxReader::from_bytes still rejects them without a parent to prevent silent data loss. VhdxIntegrity analyses the raw structure regardless and emits DifferencingDisk (Warning).
Supported formats
- VHDX Version 1 (Windows 8 / Server 2012 and later)
- Dynamic disks (sparse BAT-addressed data blocks)
- Fixed disks (all blocks preallocated)
- Differencing disks (via
VhdxReader::from_bytes_with_parent)
Dirty-log recovery is applied automatically on open: if the active header carries a non-zero LogGuid, the log region is replayed into the in-memory buffer before any BAT or metadata parsing.
Trust but verify
- Panic-free on hostile input. No
.unwrap()/.expect()/panic!or unchecked indexing in production code (unwrap_used/expect_usedare harddenylints). Every length, offset, and count field is bounds-checked before any arithmetic — see Hardening against crafted images above. - Fuzzed. A
cargo-fuzzworkspace exercises the parse and analyse paths; the invariant is "must not panic." - Validated against real artifacts, not only synthetic fixtures (below).
Testing
The forensic crate ships a suite of integration tests across nine test files. Real images from two independent sources are committed to the repository:
| Source | Images | Purpose |
|---|---|---|
| log2timeline/dfvfs corpus | ext2.vhdx, fat-parent.vhdx, fat-differential.vhdx, ext2.vhd |
Doer-checker: images built by a separate tool verify our parser against independently created data |
| QEMU v11.0.0 (Homebrew) | qemu_empty_dynamic.vhdx, qemu_fixed.vhdx |
Zero-FP baseline and injection tests; virtual disk sizes cross-validated with qemu-img info |
The decoded virtual byte stream is verified byte-identical to qemu-img convert -O raw (an independent C codebase) on the committed corpus — the load-bearing correctness oracle. Detection capability is verified by injecting corruptions at spec-mandated MS-VHDX §2.0 byte offsets into real QEMU images, then asserting the expected anomaly variant is detected — independently of our builder code.
See the validation report (source) for the full per-capability evidence, oracle/corpus tables, and documented gaps.
Related
vhdx-core— Pure-Rust VHDX container reader (published asvhdx-core, imported asvhdx); the reader layer this crate depends onewf— EWF/E01 container reader; pairs with this crate in the Issen stackewf-forensic— Integrity auditor and Adler-32 repair for EWF images; the EWF counterpart to this crate- libvhdi — C-based VHDX/VHD reader (LGPL); a candidate independent forensic-analysis oracle (a
libvhdidifferential is a documented validation gap, see the validation report)
Privacy Policy · Terms of Service · © 2026 Security Ronin Ltd