Repository navigation
Read EWF2 (Ex01) images - #183
Merged
Merged
Conversation
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>
# Conflicts: # CHANGELOG.md
| 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): |
| def _fh(self, seg): | ||
| h = self._handles.get(seg) | ||
| if h is None: | ||
| h = open(self.segment_paths[seg], "rb") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
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.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:
engine.ewf2, confirmed by direct byte comparison during development.tests/crosscheck.py): a newsynthetic:ewf2case compares Strata's reader againstpyewf/libewf automatically. Running it locally withlibewf-pythoninstalled 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_offsetis 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.pyis a from-scratch fixture builder, written independently ofengine/ewf2.py(same discipline as every other format fixture in this repo).tests/test_ewf2.pycovers:.Ex01/.Lx01naming scheme, including the lettered continuation past.Ex99engine.ewf.open_image()dispatch to the new readerTest plan
python -m unittest discover -s tests -p "test_*.py"— passes (two pre-existing, unrelated failures confirmed present on plainmaintoo: a Windows path-normalization quirk in the RAID feature, and the known VHD memory-measurement flake)python tests/smoke.py— all checks passpython tests/crosscheck.py --case "synthetic:ewf2"— agrees with libewf (pyewf installed locally for this run only)docs/ROADMAP.md, marked[~]rather than[x])🤖 Generated with Claude Code