Skip to content

Read EWF2 (Ex01) images - #183

Merged
switch-nz merged 2 commits into
mainfrom
ewf2-reader
Oct 2, 2026
Merged

switch-nz merged 2 commits into
mainfrom
ewf2-reader

Conversation

@switch-nz

Copy link
Copy Markdown
Owner

Summary

  • Reads EWF2 (EnCase Evidence File Format Version 2, "Ex01"), introduced in EnCase 7. Previously detected and refused outright (closes Read Ex01 / Lx01 images (EWF v2) #44).
  • Zlib-compressed, stored and pattern-fill (sparse) chunks are read, along with device and case metadata and MD5/SHA1 verification.
  • This turned out not to be simply "E01 with wider fields": EWF2 sections are read back-to-front — each section's 64-byte descriptor sits at its own end and points to the previous section, not the next one — and metadata uses a different key/value text format entirely. The chunk table also carries each chunk's exact size and flags explicitly (including a pattern-fill mechanism for sparse chunks that store no data at all), rather than EWF1's "size is the gap to the next entry" scheme.
  • Encrypted volumes and bzip2-compressed chunks are refused with the reason — EnCase 7 has never exposed bzip2 as an option in its own UI, and Ex01 encryption is undisclosed even to libewf's own author, so neither is guessable.
  • Generalizes engine.ewf.discover_segments() to the Ex01/Lx01 five-character segment naming scheme (.Ex01 .. .Ex99, .ExAA .. .ExZZ), alongside the existing four-character E01-style one.
  • EWF2-Lx01 (logical evidence: files and folders, not a disk image) is a different, adjacent format and is not read here.

Verification

Built from libyal's "EWF 2 specification" and cross-checked line-for-line against libewf's own C source for the one detail the prose alone doesn't pin down: what the "previous section offset" field actually encodes. A literal reading suggests the previous section's start offset; it's actually that section's descriptor position (= this section's start − 64) — confirming that is what makes the back-to-front walk actually work.

Verified two ways beyond the project's usual from-scratch fixture:

  • pyewf (libewf's own Python bindings, the real reference implementation) reads this module's synthetic test files — including mixed compressed/stored/pattern-fill chunks and escaped metadata — identically to engine.ewf2, confirmed by direct byte comparison during development.
  • This PR also wires EWF2 into the cross-check harness that just landed in Cross-check Strata against independent readers (synthetic corpus) #182 (tests/crosscheck.py): a new synthetic:ewf2 case compares Strata's reader against pyewf/libewf automatically. Running it locally with libewf-python installed reports agree — 0 differences.

Building and inspecting real EWF2 files with pyewf during development also surfaced two things worth recording: compression method 2 (bzip2) is never actually reachable from EnCase 7's own UI (confirmed in the spec itself), and a bug in pyaff4-adjacent tooling is not an issue here, but a genuine quirk in how previous_offset is described in the prose was caught precisely because the primary source was cross-checked against real code rather than trusted at face value.

Tests

tests/imagebuild_ewf2.py is a from-scratch fixture builder, written independently of engine/ewf2.py (same discipline as every other format fixture in this repo). tests/test_ewf2.py covers:

  • Uncompressed, compressed, and mixed compressed/stored chunks; partial, overlapping and boundary-crossing reads
  • Pattern-fill (sparse) chunks
  • Device/case metadata, including escaped tabs and newlines
  • Stored-hash verification (MD5/SHA1, matching and mismatched)
  • Refusals: encrypted, bzip2-compressed, unknown compression method, not an EWF2 file at all, no chunk table
  • Segment discovery for the .Ex01/.Lx01 naming scheme, including the lettered continuation past .Ex99
  • Resilience: a corrupted compressed chunk and a gap in the chunk table both zero-fill and report a finding rather than failing the read
  • engine.ewf.open_image() dispatch to the new reader

Test plan

  • python -m unittest discover -s tests -p "test_*.py" — passes (two pre-existing, unrelated failures confirmed present on plain main too: a Windows path-normalization quirk in the RAID feature, and the known VHD memory-measurement flake)
  • python tests/smoke.py — all checks pass
  • python tests/crosscheck.py --case "synthetic:ewf2" — agrees with libewf (pyewf installed locally for this run only)
  • No real Ex01 image written by EnCase itself has been checked against this yet (noted in docs/ROADMAP.md, marked [~] rather than [x])

🤖 Generated with Claude Code

github-actions Bot and others added 2 commits October 3, 2026 09:36
EWF2 (EnCase Evidence File Format Version 2, "Ex01"), introduced in
EnCase 7, was previously detected and refused outright. It turns out
not to be simply E01 with wider fields: sections are read back-to-front
(each section's 64-byte descriptor sits at its own end and points to
the previous section, not the next one), metadata is a different
key/value text format, and the chunk table carries each chunk's exact
size and flags explicitly, including a pattern-fill mechanism for
sparse chunks that store no data at all.

Built from libyal's "EWF 2 specification" and cross-checked line-for-
line against libewf's own C source for the one detail the prose alone
doesn't pin down: what the "previous section offset" field actually
encodes (the previous section's descriptor position, not its start
offset -- this is what makes the back-to-front walk work).

Verified two ways beyond the usual from-scratch fixture:
- pyewf (libewf's own Python bindings, the real reference
  implementation) reads this module's synthetic test files -- including
  mixed compressed/stored/pattern-fill chunks and escaped metadata --
  identically to engine.ewf2, confirmed by direct byte comparison.
- Building and inspecting real EWF2 files with pyewf during development
  surfaced that compression method 2 (bzip2) is never actually
  reachable from EnCase 7's own UI, and that EWF2 encryption is
  undisclosed even to libewf's own author -- both are refused outright
  rather than guessed at.

Also generalizes engine.ewf.discover_segments() to the Ex01/Lx01
five-character segment naming scheme (.Ex01 .. .Ex99, .ExAA .. .ExZZ),
alongside the existing four-character E01-style one.

EWF2-Lx01 (logical evidence: files and folders, not a disk image) is a
different, adjacent format and is not read here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread engine/ewf.py
r"^" + re.escape(prefix) + r"\." + re.escape(type_letter)
+ r"[a-z]([0-9]{2}|[A-Z]{2})$", re.IGNORECASE)
found = []
for name in os.listdir(directory):
Comment thread engine/ewf2.py
def _fh(self, seg):
h = self._handles.get(seg)
if h is None:
h = open(self.segment_paths[seg], "rb")
@switch-nz
switch-nz merged commit dbb8649 into main Oct 2, 2026
32 of 33 checks passed
@switch-nz
switch-nz deleted the ewf2-reader branch October 2, 2026 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Read Ex01 / Lx01 images (EWF v2)

2 participants