Skip to content

Repository files navigation

ewf-image

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.

Crates.io Documentation CI License

ewf-image project banner

There are two ways to use this project:

  • ewf-cli is 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-image for EWF and the experimental aff4-image for AFF4.

Highlights

  • 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.

Supported formats

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.

Command-line tool

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

Common tasks

# 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.

Rust library

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.

Read an image

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 an image

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.

Work with logical files

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.

Write an image

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:

  • AcquisitionWriter acquires physical E01 images with checkpoints and can resume after an interruption.
  • SequentialWriter streams known-length E01, Ex01, and Lx01 output and stages one segment at a time. The CLI uses it for E01 conversion.
  • LogicalWriter builds 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.

Documentation

Contributing and security

Read Contributing before opening a pull request. Report vulnerabilities privately as described in the security policy.

License

Licensed under the Apache License 2.0.

About

Pure-Rust toolkit and CLI for forensic evidence images: read, verify, acquire, and convert E01, L01, S01, Ex01, Lx01, AFF4, and raw.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Used by

Contributors

Languages