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
[]
= "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 ;
Triage by severity
use ;
let data = read.unwrap;
let findings = new.analyse;
let critical: = findings.iter
.filter
.collect;
if !critical.is_empty
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 ;
let original = read.unwrap;
let report = new.repair;
// What was fixed automatically
for r in &report.repairs
// What still needs human review
for c in &report.cannot_repair
// Verify the patched image is now clean
let post = new.analyse;
assert!;
// Write the repaired copy — original is untouched
write.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
Vecand touches no heap beyond the slice you hand it. - No unsafe code —
ewf_forensicitself contains nounsafeblocks. - 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
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