Skip to content

SecurityRonin/ewf-forensic

Repository files navigation

ewf-forensic

Forensic integrity analysis and repair for EWF / E01 images

Crates.io docs.rs License: MIT CI Rust 1.85+ Sponsor

Verify the image. Trust the evidence.

ewf-forensic is a pure-Rust library for forensic-grade read and write access to EWF v1 (E01) images — no libewf, no C toolchain, no build complexity. Drop it into any Rust project and get direct byte-level access to EWF segment data alongside a full integrity analyser and in-memory repair engine.

The analyser reports exactly what is structurally wrong across seven layers: signature forgery, broken section chains, cyclic chain attacks, Adler-32 descriptor corruption, volume geometry inconsistencies, table mismatches, out-of-bounds chunk pointers, and MD5 hash mismatches. Section descriptor CRC errors are repairable in-memory — patched bytes written to a fresh buffer, original untouched. Hash mismatches are surfaced as CannotRepair so you decide what to do next.

* md-5 is the only runtime dependency.


Install

[dependencies]
ewf-forensic = "0.1"

What It Checks

Layer 1 — File Header

Anomaly Severity
InvalidSignature — EVF magic bytes corrupted or absent Critical
SegmentNumberZero — segment number field is 0 (invalid) Error

Layer 2 — Section Descriptor Integrity

Anomaly Severity
SectionDescriptorCrcMismatch { offset, section_type, computed, stored } — Adler-32 over descriptor bytes [0..72] does not match stored checksum Error

Layer 3 — Section Chain

Anomaly Severity
SectionChainBroken { at_offset, next_offset }next pointer is zero, past EOF, or points backward (cycle) Critical
SectionGapNonZero { gap_offset, gap_size } — non-zero bytes exist between consecutive sections Warning
SectionGapZero { gap_offset, gap_size } — zero-filled bytes exist between consecutive sections (legitimate in alignment-padded images; noted as structural anomaly) Info

Layer 4 — Section Completeness

Anomaly Severity
VolumeSectionMissing — neither volume nor disk section found Critical
UnknownSectionType { offset, type_name } — section type string not in the EWF v1 spec Warning
DoneSectionMissing — chain ends without a done section Warning

Layer 5 — Volume Geometry

Anomaly Severity
BytesPerSectorInvalid { bytes_per_sector } — not 512 or 4 096 Error
ChunkSizeInvalid { sectors_per_chunk, bytes_per_sector } — zero or not a power of two Error
SectorCountMismatch { declared, expected }sector_count is outside the valid range ((chunk_count−1)×spc, chunk_count×spc]; last-chunk padding is normal and not flagged Error

Layer 6 — Table Integrity

Anomaly Severity
TableChunkCountMismatch { in_volume, in_table } — entry count in table header differs from volume Error
TableEntryOutOfBounds { chunk_index, entry_offset, file_size } — chunk offset resolves past EOF Error
TableEntryOutsideSectorsRange { chunk_index, entry_offset, sectors_start, sectors_end } — entry resolves inside the file but outside the sectors data body (e.g., into a descriptor or the table itself) Error

Layer 7 — Hash Verification

Anomaly Severity
HashMismatch { computed, stored } — MD5 of decompressed sector data does not match stored hash Error
HashSectionMissing — no hash section found Warning

Usage

Analyse an E01 image

use ewf_forensic::{EwfIntegrity, Severity};

fn main() -> std::io::Result<()> {
    let data = std::fs::read("evidence.E01")?;
    let findings = EwfIntegrity::new(&data).analyse();

    if findings.is_empty() {
        println!("clean — no anomalies detected");
        return Ok(());
    }

    for anomaly in &findings {
        let tag = match anomaly.severity() {
            Severity::Critical => "[CRITICAL]",
            Severity::Error    => "[ERROR]   ",
            Severity::Warning  => "[WARNING] ",
            Severity::Info     => "[INFO]    ",
        };
        println!("{tag} {anomaly:?}");
    }
    Ok(())
}

Triage by severity

use ewf_forensic::{EwfIntegrity, Severity};

let data = std::fs::read("evidence.E01").unwrap();
let findings = EwfIntegrity::new(&data).analyse();

let critical: Vec<_> = findings.iter()
    .filter(|a| a.severity() == Severity::Critical)
    .collect();

if !critical.is_empty() {
    eprintln!("{} critical finding(s) — image may be unreadable", critical.len());
}

Repair in-memory (non-destructive)

EwfRepair never touches your original file. It clones the bytes, applies only safe mechanical fixes (Adler-32 recomputation), and returns the patched buffer alongside a full audit trail of what was repaired and what could not be.

use ewf_forensic::{EwfIntegrity, EwfRepair};

let original = std::fs::read("evidence.E01").unwrap();
let report = EwfRepair::new(original.clone()).repair();

// What was fixed automatically
for r in &report.repairs {
    println!("repaired: {r:?}");
}

// What still needs human review
for c in &report.cannot_repair {
    println!("cannot repair: {c:?}");
}

// Verify the patched image is now clean
let post = EwfIntegrity::new(&report.data).analyse();
assert!(post.iter().all(|a| !matches!(
    a,
    ewf_forensic::EwfIntegrityAnomaly::SectionDescriptorCrcMismatch { .. }
)));

// Write the repaired copy — original is untouched
std::fs::write("evidence_repaired.E01", &report.data).unwrap();

What is and is not repairable

Anomaly Repairable? Reason
SectionDescriptorCrcMismatch Yes Adler-32 is deterministically recomputed from the bytes already present
HashMismatch No Cannot determine whether the sector data or the stored hash is authoritative
All others No Structural damage requires analyst judgement

Design

  • Zero allocation on clean images — the analyser returns an empty Vec and touches no heap beyond the slice you hand it.
  • No unsafe codeewf_forensic itself contains no unsafe blocks.
  • No panics on adversarial input — every parser path is bounded; cycle attacks and integer overflows are explicitly handled. Verified by libfuzzer (4.5 M iterations, zero crashes).
  • Validated against real acquisitions — zero false positives across three public E01 fixtures (exFAT, email corpus, MMLS) with full MD5 hash verification including per-chunk zlib decompression. Three small images are committed as test fixtures and run in CI. See docs/VALIDATION.md for image sources, download URLs, and reproduction steps.
  • MSRV 1.85 — no nightly, no unstable features.

Fuzzing

cargo +nightly fuzz run fuzz_integrity
cargo +nightly fuzz run fuzz_repair

Both targets run in CI for 30 seconds on every push. To run longer locally, remove -max_total_time.


Anomaly Catalog

docs/anomaly-catalog.md maps every detectable anomaly to its threat scenario — evidence suppression, modification, insertion, redirection, and parser exploitation — and documents known detection limits.


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

About

Forensic integrity analysis and repair for EWF (Expert Witness Format / E01) images

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages