Read, verify, acquire, and convert forensic evidence images in pure Rust:
EnCase-style .E01, .L01, .S01, .Ex01, and .Lx01 images, AFF4
containers, and raw disk images.
There are two ways to use this project:
ewf-cliis a single command-line tool for inspecting, verifying, acquiring, converting, collecting, and extracting evidence in any supported format.- Rust libraries let you embed the same functionality in your own tools:
ewf-imagefor EWF and the experimentalaff4-imagefor AFF4.
- No external tools. Pure Rust, with no libewf or AFF4 tooling required. Both libraries forbid unsafe code.
- Verification you can check. Decoded media is hashed and compared with the MD5, SHA1, and SHA256 digests stored in the image, or with a digest you recorded independently.
- Acquisition from disks and files. Read-only device acquisition on Windows and Linux, with resumable E01 acquisition and checkpoints.
- Conversion between formats. Move decoded evidence between E01, Ex01, AFF4, and raw, or between Lx01 and AFF4 logical collections. Each conversion is verified, and metadata that cannot be carried over is reported.
- Logical evidence. Browse L01, Lx01, and AFF4 file catalogs, verify individual files, and extract them.
- Damaged images. Scan for damage and recover readable data with a provenance map.
- Safe by default. Commands never overwrite existing files, and every result is available as JSON for scripts.
| Format | Extension | Read | Write |
|---|---|---|---|
| EWF1 physical | .E01 |
Raw and zlib; X-Ways Zstandard; X-Ways AES-128/AES-256 encryption | Raw and zlib |
| EWF1 logical | .L01 |
Media and file catalog | Media and file catalog |
| EWF1 SMART | .S01 |
Media | Media (library only) |
| EWF2 physical | .Ex01 |
Raw, zlib, BZip2, and pattern-fill | Raw, zlib, BZip2, and pattern-fill |
| EWF2 logical | .Lx01 |
Media and file catalog | Media and file catalog |
| AFF4 physical (experimental) | .aff4 |
AFF4 1.0 single volumes and multi-volume sets | AFF4 1.0 single volumes |
| AFF4 logical (experimental) | .aff4 |
AFF4-L 1.1 | AFF4-L 1.1 |
| Raw | .raw, .dd, .img, .bin |
Yes (CLI) | Yes (CLI) |
Split EWF segment sets are found automatically. Encrypted X-Ways EWF1 images open
with a password. Encrypted EWF2 images, encrypted output, EWF delta (overlay)
images, and AFF4 encryption are not supported.
Compatibility describes tested producers and consumers,
limitations lists unsupported workflows, and the
aff4-image guide
lists the supported AFF4 profiles in detail.
ewf-cli is not yet published as a package or prebuilt binary. Build it from
this repository with Rust 1.96 or later:
git clone https://github.com/ebrig/ewf-image
cd ewf-image
cargo install --path crates/ewf-cli --locked# Inspect and verify
ewf-cli info case.E01
ewf-cli verify case.E01
ewf-cli verify case.E01 --sha256 HASH
# Acquire a disk (the output extension selects the format)
sudo ewf-cli acquire /dev/sdb case.E01
ewf-cli acquire \\.\PhysicalDrive2 case.aff4 --case-number CASE-123
# Convert between formats
ewf-cli convert case.E01 case.aff4
ewf-cli convert case.aff4 exported.raw
# Collect a folder into a logical image, then list and extract files
ewf-cli collect evidence-folder files.Lx01
ewf-cli files files.Lx01
ewf-cli extract files.Lx01 2 recovered.bin --restore-times
The output extension selects the format: .E01, .Ex01, .aff4, or a raw
extension for disk images, and .Lx01 or .aff4 for logical collections. Input
containers are detected by signature. Add --json for machine-readable results
and --quiet to hide progress. Run ewf-cli <command> --help for options.
Device acquisition may require administrator or root access. It does not freeze a live disk, so use a stable source or a snapshot. Directory collection does not create a filesystem snapshot either.
| Exit code | Meaning |
|---|---|
| 0 | Completed with its stated verification scope |
| 1 | Operational failure |
| 2 | Invalid command syntax |
| 3 | Verification mismatch or analysis errors |
| 4 | Incomplete references, metadata omissions, or other findings |
| 130 | Cancelled |
The CLI guide covers device acquisition, supported conversions, verification scope, and JSON output. The EWF command guide covers damage analysis, recovery, resume, and advanced acquisition options.
The examples below target ewf-image 0.6.0. See the
changelog for its changes. The
0.5 migration guide remains available for upgrades
from 0.4. For AFF4,
see the aff4-image crate.
ewf-image has no AFF4 dependencies.
[dependencies]
ewf-image = "0.6"The crate requires Rust 1.96 or later. Its runtime features are:
| Feature | Effect |
|---|---|
verify |
Media verification, per-file verification, and integrity analysis. Enabled by default. |
parallel |
Verification across multiple worker threads. Enables verify. |
serde |
Serialization of reports and metadata types. |
Stored-hash parsing, section integrity checks, and writer hashing remain available
with default-features = false.
Open the first segment. The remaining segments are found automatically.
use std::io::Read;
fn main() -> ewf_image::Result<()> {
let image = ewf_image::Image::open("case.E01")?;
let info = image.info();
println!("{:?}, {} bytes in {} segments", info.format, info.logical_size, info.segment_count);
// Read sequentially through a Read + Seek cursor.
let mut first_sector = [0u8; 512];
image.cursor().read_exact(&mut first_sector)?;
// Or read at an absolute offset without a cursor.
let mut buffer = [0u8; 4096];
image.read_at(&mut buffer, 1024 * 1024)?;
Ok(())
}Image is a cheap, shareable handle. Clones and cursors share bounded caches,
so one image can serve many readers. OpenOptions adjusts cache sizes, handle
limits, and strictness. Encrypted X-Ways images open with
Image::open_with_password. Segment files must remain unchanged while an image
is open.
verify decodes the complete media and compares it with the digests stored in
the image. Verification bypasses caches, so corrupt data cannot pass as valid.
use ewf_image::{Image, VerifyOptions};
fn main() -> ewf_image::Result<()> {
let image = Image::open("case.E01")?;
let result = image.verify()?;
println!("MD5 match: {:?}", result.md5_match);
println!("SHA256 match: {:?}", result.sha256_match);
// Compare against a digest recorded outside the image.
let acquisition_sha256 = [0u8; 32]; // Replace with the recorded value.
let options = VerifyOptions::default().with_expected_sha256(acquisition_sha256);
let report = image.verify_with_options(&options)?;
println!("references match: {:?}", report.references_match());
Ok(())
}A match value of None means the image stores no digest of that type. A match
shows that the decoded media equals what was hashed at acquisition. It cannot
show whether unreadable source sectors were replaced with zeros at that time. See
verification, analysis, and recovery.
Logical images (.L01 and .Lx01) contain a catalog of files and folders.
use ewf_image::{Image, SingleFileEntryType};
use std::{fs::File, io};
fn main() -> ewf_image::Result<()> {
let image = Image::open("files.L01")?;
let Some(root) = image.root_file_entry() else {
println!("not a logical image");
return Ok(());
};
for entry in &root.children {
println!("{} ({} bytes)", entry.name().unwrap_or("?"), entry.size().unwrap_or(0));
}
let first_file = root
.children
.iter()
.find(|entry| entry.entry_type() == Some(SingleFileEntryType::File));
if let Some(entry) = first_file {
let check = image.verify_single_file(entry)?;
println!("stored file hashes match: {:?}", check.references_match());
let mut output = File::create_new("extracted.bin")?;
io::copy(&mut image.single_file_cursor(entry), &mut output)?;
}
Ok(())
}Extraction copies file content only. Timestamps and other recorded metadata
remain available on each catalog entry but are not applied to extracted files.
The ewf-cli extract --restore-times option applies recorded file access and
modification times to a selected new output file.
EwfWriter creates EWF1 or EWF2 images from any readable source.
use ewf_image::{EwfWriter, WriteCompression, WriteFormat, WriteOptions};
use std::{fs::File, io};
fn main() -> ewf_image::Result<()> {
let mut options = WriteOptions {
format: WriteFormat::Ewf2Physical,
compression: WriteCompression::Zlib,
..WriteOptions::default()
};
options.metadata.set_header_value("case_number", "CASE-001");
let mut writer = EwfWriter::create("case.Ex01", options)?;
io::copy(&mut File::open("disk.raw")?, &mut writer)?;
let result = writer.finish()?;
println!("wrote {} segments", result.segment_paths.len());
Ok(())
}EwfWriter supports every output format and positioned writes, but it spools
the complete source to temporary storage. Specialized writers cover large or
long-running jobs:
AcquisitionWriteracquires physical E01 images with checkpoints and can resume after an interruption.SequentialWriterstreams known-length E01, Ex01, and Lx01 output and stages one segment at a time. The CLI uses it for E01 conversion.LogicalWriterbuilds L01 and Lx01 file catalogs from files you supply.
Writers return computed MD5, SHA1, and SHA256 digests but do not reread their output. Acquisition and writing explains resume, publication, and recovery for each writer.
- Command line: CLI guide and EWF command guide
- Library: API reference and runnable examples
- AFF4:
aff4-imageguide - Verification, analysis, and recovery
- Acquisition and writing
- Compatibility and limitations
- Architecture and testing
- Release process
- Upgrading: 0.5 guide, 0.4 release notes, 0.3 guide, 0.2 guide
Read Contributing before opening a pull request. Report vulnerabilities privately as described in the security policy.
Licensed under the Apache License 2.0.
