disk4n6 is the fleet's universal forensic disk layer: point it at any container — E01, VMDK, VHDX, VHD, QCOW2, DMG, raw dd, or ISO — and it decodes the wrapper, identifies the partitioning scheme (MBR / GPT / APM), and runs the right forensic parser. Run it bare and it maps every physical disk and partition on the live system — macOS, Linux, and Windows in one unified output — with acquisition-integrity findings you need before you image.
The library is also the foundation for the fleet's universal forensic VFS: a layered trait model that will let issen, 4n6mount, and disk4n6 share one open-any-image entry point rather than three parallel detection stacks.
$ cargo install disk-forensic # crate: disk-forensic, binary: disk4n6Triage the live system — run it bare. No image to decode, no platform-specific tool to remember (diskutil / sfdisk / fdisk all in one):
$ disk4n6All storage (2 physical disks, 1.1 TB total):
disk0 [########################################################] 1.0 TB 94.1%
disk1 [### ] 64.0 GB 5.9%
/dev/disk0 1.0 TB
[.=####################################################+.]
. - free (unallocated) 24.6 KB 0.0%
= 1 disk0s1 C12A7328-F81F-11D2-BA4B-00A0C93EC93B 536.9 MB 0.1%
# 2 disk0s2 7C3457EF-0000-11AA-AA11-00306543ECAC 1.0 TB 99.4%
+ 3 disk0s3 52637672-7900-11AA-AA11-00306543ECAC 5.4 GB 0.5%
/dev/disk1 64.0 GB
[=#######################################################]
= 1 disk1s1 EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 200.0 MB 0.3%
# 2 disk1s2 EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 63.8 GB 99.7%
Acquisition-integrity findings:
disk0 [HIGH] LIVE-MOUNTED: device has mounted volume(s) during acquisition; live writes may alter the image — consistent with imaging a running system
The overview bar scales every disk against the largest so relative sizes read at a glance; each per-disk bar lays out partitions and free space proportionally.
Analyse an image — hand it the evidence:
$ disk4n6 evidence.E01 # an EnCase image straight off the shelfScheme: Gpt
MBR Forensic Analysis
disk signature : 0x00000000
boot code : AllZeros
partitioning : Unknown
Partition table (1 entries):
[0] GPT Protective MBR LBA 1..=409599 fs=Unknown
GPT cross-check: 131 GPT partition entries
GPT Forensic Analysis
================================================================================
Disk GUID: 9D71FE48-F2FB-43F1-9326-36644D4D4E70
Revision: 1.0
That E01 was decoded, the protective MBR cross-checked, and the GPT parsed — one command, no intermediate files. Exit code is 0 when clean and 1 when any anomaly is present, so it drops straight into a triage pipeline. Add --json (build with --features serde) for machine-readable output in either mode.
Before you acquire, you need to know what's attached and whether touching it is safe. disk4n6 enumerates the host's physical disks and partitions through the native interface on each platform — macOS IOKit, Linux sysfs (/sys/block), Windows DeviceIoControl — and normalises them into one model, so the same command and the same output work everywhere. The enumeration and rendering live in livedisk-core; the acquisition findings come from livedisk-forensic.
The findings are observations bearing on a forensically sound acquisition — never verdicts:
| Code | Meaning |
|---|---|
LIVE-MOUNTED |
a volume is mounted during acquisition (live writes may alter the image) |
LIVE-WRITABLE |
the device being acquired is writable — no write-blocker engaged. Shown when you point disk4n6 at a specific target device, not in the host overview |
LIVE-REMOVABLE |
removable media |
LIVE-SECTOR-4KN |
logical/physical sector sizes differ (512e / 4Kn) |
LIVE-SYNTHESIZED |
a synthesized container overlay (APFS container, device-mapper/LVM), not a backing physical store |
LIVE-SYNTHESIZED is informational, not a recommendation: whether to image the synthesized device or the underlying physical store is a forensic decision for the examiner, not the tool.
disk4n6 sniffs the container magic, decodes it to a Read + Seek view of the raw disk, and analyses that. Rename a .vmdk to .bin and it still works.
| Input | Handling |
|---|---|
Raw / dd |
analysed in place |
| E01 / EWF (EnCase) | decoded |
| VMDK (VMware) | decoded — follows snapshot/delta extent chains to the base image |
| VHDX (Hyper-V) | decoded |
| VHD (Virtual PC, fixed + dynamic) | decoded (built-in) |
| QCOW2 (QEMU/KVM) | decoded |
| DMG (Apple UDIF) | decoded — pure-Rust codecs (ADC / zlib / bzip2 / LZFSE / LZMA), no C dependencies |
| ISO 9660 (optical) | routed to filesystem analysis (see below) |
| AFF4 | recognised; decoder not yet wired |
A corrupt or unsupported-variant container fails loud with a clear decode error rather than silently producing wrong output.
An ISO is a filesystem, not a partitioned disk, so disk4n6 routes it to iso9660-forensic and renders the same normalized findings / provenance / timeline view — volume identity, mastering-tool fingerprint, Rock Ridge authoring owners, structural anomalies, and the reconstructed authoring window:
$ disk4n6 image.isoFilesystem: ISO 9660
Findings: none (clean)
Provenance:
volume label: DFTEST (iso9660-forensic)
system identifier: APPLE INC., TYPE: 0002 (iso9660-forensic)
sector mode: Iso2048 (iso9660-forensic)
extensions: Rock Ridge: true, Joliet: true (iso9660-forensic)
sessions: 1 (iso9660-forensic)
Rock Ridge owners: uids [501], gids [0] (iso9660-forensic)
A Timeline section then reconstructs the volume's authoring window from the PVD and file-recorded times — on real media these diverge into a span you can reason about.
[dependencies]
disk-forensic = "0.9"One call sniffs the container magic, decodes the wrapper, and hands back a uniform Read + Seek view of the raw disk. The caller never names a format:
use disk_forensic::container::{self, OpenedImage};
let img: OpenedImage = container::open(std::path::Path::new("evidence.E01"))?;
// img.format : ContainerFormat — which wrapper was detected
// img.size : u64 — decoded media size in bytes
// img.reader : Box<dyn ReadSeek> — a Read + Seek view of the raw disk
let mut disk = img.reader;
# Ok::<(), Box<dyn std::error::Error>>(())ReadSeek is Read + Seek, blanket-impl'd for every reader, so a decoded EWF/VMDK/QCOW2 reader and a plain raw File box into the same Box<dyn ReadSeek>. Need only the classification? container::sniff(&mut reader) (or the pure container::detect(header, footer)) returns the detected ContainerFormat without decoding.
| Container | container::open behaviour |
|---|---|
Raw / dd |
passed through in place |
| E01 / EWF (EnCase) | decoded |
| VMDK (VMware) | decoded — follows snapshot/delta extent chains to the base image |
| VHDX (Hyper-V) | decoded |
| VHD (Virtual PC, fixed + dynamic) | decoded |
| QCOW2 (QEMU/KVM) | decoded |
| DMG (Apple UDIF) | decoded — pure-Rust ADC / zlib / bzip2 / LZFSE / LZMA codecs |
| ISO 9660 (optical) | passed through to the filesystem analyser (a filesystem, not a partitioned disk) |
| AFF4 (physical) | decoded — an aff4:ImageStream / aff4:Map image opens as a Read + Seek disk view |
| AFF4 (logical) / AD1 | logical file containers, not disks — container::open refuses them with OpenError::LogicalContainer; open them with disk_forensic::logical::open |
A corrupt or unsupported-variant container fails loud with OpenError::Decode; a logical file container (AD1, or an AFF4-Logical aff4:FileImage collection) returns OpenError::LogicalContainer naming logical::open. There is no silent wrong-output path.
Some evidence is a file tree, not a raw disk: AccessData AD1 (FTK "Custom Content Image") and AFF4-Logical (aff4:FileImage). These have no partition table, so they live in disk_forensic::logical rather than container::open:
let mut img = disk_forensic::logical::open(std::path::Path::new("evidence.ad1"))?;
for e in img.entries() { // LogicalEntry: path, is_dir, size
if !e.is_dir { /* img.read_file(index) → the file's bytes */ }
}
# Ok::<(), Box<dyn std::error::Error>>(())disk-forensic::container opens the container — the wrapper down to raw disk bytes as Read + Seek. Mounting volumes and filesystems over those bytes belongs to forensic-vfs and its engine, which compose the full stack behind one Arc<dyn ImageSource> (e.g. E01 → GPT → BitLocker → NTFS). The pipeline:
- image file
- →
container::open→Read + Seek/ImageSource(raw disk bytes) - → volume system (MBR / GPT / APM)
- → filesystem (NTFS / ext4 / APFS / HFS+ / FAT / ISO)
- → files
disk-forensic itself parses the volume layer (MBR/GPT/APM) and the ISO filesystem; forensic-vfs defines the ImageSource byte-edge plus the volume/crypto/filesystem traits, and forensic-vfs-engine composes the concrete readers (NTFS/ext4/APFS/HFS+/FAT/…) into a Vfs::open(path) → Evidence walk.
use std::fs::File;
// Decode whatever container the evidence arrived in, then analyse the disk.
let opened = disk_forensic::container::open(std::path::Path::new("evidence.E01"))?;
let mut img = opened.reader;
match disk_forensic::analyse_disk(&mut img, opened.size)? {
disk_forensic::DiskReport::Gpt(a) => println!("GPT: {} partitions", a.partitions.len()),
disk_forensic::DiskReport::Mbr(a) => println!("MBR: {} partitions", a.partitions.len()),
disk_forensic::DiskReport::Apm(a) => println!("APM: {} partitions", a.partitions.len()),
}
# Ok::<(), Box<dyn std::error::Error>>(())analyse_disk takes any Read + Seek, so you can feed it a raw image directly. A disk with no recognised scheme returns [Error::UnknownScheme] rather than mis-parsing. Each analyser normalizes into the shared forensicnomicon::report model, so findings and provenance render uniformly across every scheme and the ISO filesystem layer.
For live enumeration, depend on livedisk-core directly:
for disk in livedisk::enumerate()? {
println!("{}: {} bytes, {} partitions", disk.name, disk.size_bytes, disk.partitions.len());
}
# Ok::<(), livedisk::Error>(())disk-forensic is pure orchestration — it classifies the scheme using the cited magics in forensicnomicon and delegates every real parse to a focused, dependency-light sibling. Use them directly when you already know the scheme, or through this crate when you don't:
| Crate | Scheme |
|---|---|
mbr-partition-forensic |
Master Boot Record — boot-code fingerprinting, gap/slack carving, per-partition VBR filesystem fingerprinting, protective-MBR/GPT detection |
gpt-partition-forensic |
GUID Partition Table — CRC32 integrity, primary/backup reconciliation |
apm-partition-forensic |
Apple Partition Map — classic Mac and hybrid optical media |
disk-forensic is being restructured as the fleet's universal forensic VFS — a single open-any-image entry point shared by issen, 4n6mount, and disk4n6, rather than three parallel detection stacks. See the full design doc for the detailed specification.
Six transform kinds, each a trait, compose as a graph. Every transform consumes an ImageSource and yields either another ImageSource or a terminal FileSystem. The resolver applies them in the order the evidence requires — crypto may appear before or after volume detection depending on the image:
PathSpec (locator, self-describing, serde-safe)
│ resolves (graph walk)
▼
ImageSource ── positioned read_at(&self), no write, no seek cursor ──┐
├── ContainerDecoder E01 / VMDK / VHDX / QCOW2 / DMG / AFF4 │
├── VolumeSystem MBR / GPT / APM / VSS / APFS-container │
├── CryptoLayer BitLocker / LUKS / FileVault │
└── FileSystem NTFS / ext4 / HFS+ / APFS / ISO / FAT → FsNode
ImageSource has read_at(&self) — no seek cursor, no write method anywhere in the trait. That single choice delivers parallel reads across a shared stack (&self composes across threads; &mut self Seek cannot) and makes evidence read-only by construction rather than by convention.
PathSpec is a self-describing locator that carries the full open-recipe for an artifact. It round-trips through a report or evidence row without carrying credentials (credentials are supplied at resolve time through a CredentialSource, so a serialized address is safe to log).
| Phase | Scope | Status |
|---|---|---|
| — | Today: container decode (E01/VMDK/VHDX/VHD/QCOW2/DMG/raw/ISO), MBR/GPT/APM parsing, live triage (macOS/Linux/Windows), ISO filesystem analysis, acquisition-integrity findings | ✅ Shipped |
| 1 | Extract forensic-vfs-core: ImageSource + adapters + PathSpec + FileSystem trait (relocate from 4n6mount). Non-breaking re-exports during transition. |
In development |
| 2 | forensic-vfs-engine: Vfs::open + registry over existing containers and schemes; per-partition filesystem mounting. |
Planned |
| 3 | One issen provider replaces 8 per-format wrapper crates (ADR-0010). | Planned |
| 4 | 4n6mount migrates onto the engine, dropping its own detect/dispatch. |
Planned |
| 5 | Crypto + snapshots + nesting: CryptoLayer (BitLocker/LUKS/FileVault), VSS VolumeSystem, nested images, TemporalCohort snapshots. |
Planned |
| 6 | In-tree trait impls per reader crate; shims deleted. | Planned |
Each phase is gated on the Case-001 Szechuan end-to-end ingest producing identical event counts and artifacts to the pre-phase baseline.
- Secure by default — one auto-detecting entry point: a caller cannot pick the wrong decoder or parser for a disk, and the zero-config path is the correct one.
- Read-only by construction — the
ImageSourcetrait has no write method; a write is uncompilable, not merely undocumented. - Fails loud — a corrupt container or unknown scheme returns a typed error with the offending bytes and offset; it never emits silently wrong output.
#![forbid(unsafe_code)]and fuzz-tested (cargo fuzz) against crafted/corrupted input.- Validated against real images — real EnCase/qemu/hdiutil containers and a genuine NTFS volume from a public CTF disk. See
docs/VALIDATION.md.
Privacy Policy · Terms of Service · © 2026 Security Ronin Ltd