Forensic imager in the spirit of FTK Imager, and with the same output.
MemoryLane acquires a bit-for-bit image of a drive or file into EWF/E01 or
raw (dd) evidence, does targeted logical collection of chosen paths,
hashes what it reads, verifies both the written evidence and the source by
reading them back, and drops the familiar image.E01.txt acquisition summary
next to the evidence.
The EWF container and the collection container are both implemented from the format up, no libewf, no zip crate, and cross-checked against the reference implementations on every CI run.
cargo build --release # target/release/mlane
cargo install --path .Six dependencies: md-5, sha1, sha2, flate2, crossbeam-channel and
libc. Prebuilt .deb packages for amd64 and arm64 are attached to each
release.
# what is attached?
mlane devices
# image a USB drive to E01 with case metadata, verifying the image and
# re-reading the source afterwards
sudo mlane acquire /dev/disk4 -o /cases/2026-042/usb --verify both \
--case-number 2026-042 --evidence-number 001 \
--description "Kingston DataTraveler 32GB" --examiner "N. Buisson"
# raw dd instead, split into 2 GB pieces, plus a SHA-256 for the report
sudo mlane acquire /dev/sdb -o /cases/2026-042/usb -f raw -s 2GB --hash sha256
# continue an acquisition that was interrupted, instead of starting over
sudo mlane acquire /dev/disk4 -o /cases/2026-042/usb --resume
# targeted logical collection: the paths that matter, not the whole disk
mlane collect /var/log /Users/suspect/Documents -o /cases/2026-042/files \
-x '*.iso' --max-size 500MB --case-number 2026-042
# re-verify evidence later against the digests stored inside it
mlane verify /cases/2026-042/usb.E01
mlane verify /cases/2026-042/files.zip
# read the metadata (ewfinfo-style); works on collections too
mlane info /cases/2026-042/usb.E01
# turn E01 back into raw so it can be mounted or fed to another tool
mlane export /cases/2026-042/usb.E01 -o /tmp/usb.ddSources can be devices or files, so acquire doubles as a converter:
raw → E01, split → single, E01 → E01 with different compression.
-o is a path on a mounted filesystem, never a target device: MemoryLane
images from a device to files, and does not clone disk to disk. A -o that
names a directory (or ends in a separator) means "write it in here", and the
file name comes from the source.
Every command has its own --help, and mlane help <command> reaches the same
text.
MemoryLane 0.1.0
Source: /dev/rdisk4 (64.0 MB, 131,072 x 512 byte sectors), cache bypassed
Model: Kingston DataTraveler 3.0 [USB]
Output: /cases/2026-042/usb.E01 (E01, fast compression, 1.5 GB segments, 11 workers)
Imaging [########################----] 86.1% 55.1 MB / 64.0 MB 612.5 MB/s ETA 00:00:01
Imaged 64.0 MB in 0.1s (945.9 MB/s) -> 1 segment(s)
MD5 4f251c2465e91c1a466373e5398ebd1f
SHA1 13bb9b4776db75917a25f68ff8cf44dc074253bb
MD5 4f251c2465e91c1a466373e5398ebd1f : verified
SHA1 13bb9b4776db75917a25f68ff8cf44dc074253bb : verified
MD5 4f251c2465e91c1a466373e5398ebd1f : source verified
SHA1 13bb9b4776db75917a25f68ff8cf44dc074253bb : source verified
Summary: /cases/2026-042/usb.E01.txt
VERIFIED
Written next to the first segment as <image>.E01.txt / <image>.001.txt,
line for line where FTK Imager puts it:
Created By MemoryLane 0.1.0
Case Information:
Acquired using: MemoryLane 0.1.0
Case Number: 2026-042
Evidence Number: 001
Unique description: Kingston DataTraveler 32GB
Examiner: N. Buisson
Notes: Seized 2026-08-20, bag A17
--------------------------------------------------------------
Information for /cases/2026-042/usb.E01:
Physical Evidentiary Item (Source) Information:
[Device Info]
Source Type: Physical
[Drive Geometry]
Cylinders: 3,824
Tracks per Cylinder: 255
Sectors per Track: 63
Bytes per Sector: 512
Sector Count: 61,440,000
[Physical Drive Information]
Drive Model: Kingston DataTraveler 3.0
Drive Serial Number: 60A44C4257A9F1B0C9A70C3E
Drive Interface Type: USB
Removable drive: True
Source data size: 30000 MB
Sector count: 61440000
[Computed Hashes]
MD5 checksum: a0c233753c61b6765dde368f27e2c30a
SHA1 checksum: 3ea152d0bff0b6f6badd37a95ebe2b1c2b35ba26
Image Information:
Acquisition started: Thu Aug 20 14:05:11 2026
Acquisition finished: Thu Aug 20 14:31:48 2026
Segment list:
/cases/2026-042/usb.E01
/cases/2026-042/usb.E02
Image Verification Results:
Verification started: Thu Aug 20 14:31:48 2026
Verification finished: Thu Aug 20 14:44:02 2026
MD5 checksum: a0c233753c61b6765dde368f27e2c30a : verified
SHA1 checksum: 3ea152d0bff0b6f6badd37a95ebe2b1c2b35ba26 : verified
A collection gets the same file, adapted to a logical source: a [Collection]
block with the object counts, the container's own digests, and the manifest
verification result. --verify source adds a Source Verification Results
block.
| EWF / E01 | EnCase 5/6 layout: header2/header2/header, disk|volume, then sectors+table+table2 groups, closed by error2, digest, hash, done. 32 KiB chunks (64 sectors), deflate or stored, adler32 on every descriptor, table and uncompressed chunk. Case metadata lands in the header sections; MD5 and SHA1 land in the hash and digest sections. Segments roll over at 1500 MB by default and are named .E01 … .E99, .EAA …. |
| Raw / dd | Byte-for-byte copy, optionally split into .001, .002, … or written as a single file. |
| Logical collection | A ZIP container holding the collected files under their full source paths, plus MemoryLane-manifest.csv / .json recording per-file size, MD5, SHA1, full-resolution timestamps, mode, ownership, symlink targets and any read error. ZIP64 is used per member and for the archive when the 32-bit fields cannot hold a value, so a 40 GB member or a 500,000-file collection is not a problem. |
| Summary | image.E01.txt in FTK Imager's layout: case block, source geometry, drive identity, computed hashes, segment list, verification result. |
Digests are always MD5 and SHA-1, those are what EWF stores, what FTK reports
and what existing case records are keyed on. Neither is collision-resistant any
more, so --hash sha256 (or sha512) computes one alongside them; that is the
digest to quote when the point is integrity rather than continuity with an
older record.
mlane collect gathers named paths instead of a whole disk, the shape most
incident response actually needs.
mlane collect /var/log /etc /Users/suspect/Documents \
-o /cases/2026-042/files \
-x '*.iso' -x '*/node_modules/*' --max-size 2GB \
--case-number 2026-042 --examiner "N. Buisson"Files land in a ZIP under their full source path (var/log/auth.log), so
provenance survives and nothing collides. Everything the ZIP format cannot
carry goes into MemoryLane-manifest.csv / .json: per-file MD5 and SHA1,
full-resolution mtime/atime/ctime, mode, uid/gid, symlink targets, and the
reason any file could not be read. Symlinks are recorded, not followed, unless
you ask.
Nothing is omitted silently. A path that is not there, one that cannot be
read, and one left out by --max-size all get a manifest record saying so. A
collection that quietly drops a file is worse than one that admits it could not
read it, because the first looks complete. A glob you passed to -x is the one
exception: you named it, so its absence is not news.
mlane verify re-reads every member and checks three separate claims: that
each member still hashes to what the manifest says, that everything the
manifest lists is present, and that nothing is present that the manifest does
not list. The last one is how a file added to the container after the fact
shows up.
The container is a plain ZIP on purpose: it opens anywhere, and every claim it makes is independently checkable.
EnCase's logical evidence format was the obvious container, and MemoryLane does
not write it. The ltree section that carries the file tree is an undocumented
text grammar; the section header geometry was recoverable by probing libewf
(48 bytes, tree size at offset 16), but the tree grammar itself was not. Every
structural variant tried left libewf's parser asking for one line past the end
of the input, independent of content. Fitting it by trial and error against one
reader's tolerance would produce a forensic container whose metadata encoding
nobody had verified, which is worse than not offering it. The ZIP container
above does the same job with claims that can be checked.
- Read-only, always. The source is opened read-only. MemoryLane never writes to the evidence device.
- A mounted source is refused. Imaging a volume still mounted for writing
cannot produce a consistent point-in-time copy, the image is a smear of
several moments and its hash describes nothing that ever existed on the
drive. APFS volumes on synthesized containers and LVM logical volumes are
traced back to the physical drive underneath, so the check is not fooled by
an indirect mount. The check runs before the device is opened, so an operator
who forgot
sudois told to unmount rather than told "permission denied".--forceproceeds and warns loudly, even under-q. (macOS and Linux; see the platform notes.) - The destination is checked before anything is written. Writing into
/dev,/procor/sysis refused outright. Those are RAM- or kernel-backed, and an image sent there consumes memory until the machine falls over. Naming a device node as the output is refused too. Writing onto the drive being imaged is refused: the source would grow as it is read. If the destination cannot hold what is left to write, that is reported up front rather than at 90%. - Bad media doesn't stop the job. A failed read is retried (
--retries, default 2) sector by sector; unreadable sectors are zero-filled, counted, listed by LBA in a[Read Errors]block in the summary, and recorded in the E01's ownerror2section so the defect list travels with the image instead of living only in a text file beside it. - Interrupted jobs resume. Ctrl-C closes the set without its
donesection, so it reads back as explicitly incomplete rather than as a finished image with a zero tail.--resumecontinues from the last committed chunk, rebuilding digest state from what is already on disk and trimming any fragment a hard kill left behind. Compression, chunk size, geometry and the set identifier are taken from the existing segments, not from the flags. An interrupted collection is closed with a manifest describing exactly what it managed to collect. - Verification is a real read-back. The written evidence is re-opened and
re-hashed from disk, not from a buffer, and compared against both the
acquisition hashes and the digest stored inside the E01.
--verify sourcere-reads the drive as well: verifying only the image cannot catch a source that changed underneath you. - Reads bypass the page cache by default, so verification reflects the
medium rather than what was written a moment ago.
--no-directturns it off. - Sector padding is stated. EWF records a sector count, so an image's media
size is always a multiple of the sector size. A file source that is not says
so on screen and in the summary, because otherwise an examiner comparing the
image digest against
md5 file.bingets a mismatch and no explanation. - Damage is reported, not raised. A corrupt chunk is zero-filled and named
in the output;
verifyexits non-zero rather than crashing, so a failing image still yields a usable report. Exporting a damaged or unfinished image still writes what can be recovered, and says what was wrong with it. - Exit codes:
0verified,1verification failed or damage found,2usage/IO error,130interrupted (partial evidence left in place).
--machine replaces the human output with one JSON object per line, for
scripts and front ends:
{"event":"start","source":"/dev/sdb","total":500107862016,"workers":7,...}
{"event":"progress","done":8589934592,"total":500107862016,"rate":412000000.0,"eta":1193.4,...}
{"event":"hashes","md5":"02d759...","sha1":"144ff4..."}
{"event":"verified","kind":"image","ok":true}
{"event":"done","status":0,"verified":true,"summary":"/cases/sdb.E01.txt","segments":[...]}
collect emits the same shape, and mlane devices --machine lists drives one
object per line, worth using over the table, whose size and bus columns can
both contain spaces.
Reading, hashing, compressing and writing all run at once:
+--> md5 thread
reader thread +--> sha1 thread
|
+--> job queue --> N deflate workers --> writer thread
Compression is the expensive stage, so it gets the worker pool. Chunks carry a sequence number and the writer reassembles them in order, so the output is what a single-threaded run would have produced, byte for byte, which the test suite asserts.
Measured end to end through the CLI on a 12-core M-series laptop, 600 MB of incompressible data, the worst case; real media compresses and goes faster. Both columns include MD5 + SHA1 over the whole stream.
| acquire | acquire + verify | |
|---|---|---|
-f raw |
707 MB/s | 361 MB/s |
-c none |
698 MB/s | 326 MB/s |
-c fast, --workers 1 |
258 MB/s | , |
-c fast, all cores |
646 MB/s | 301 MB/s |
-c best, all cores |
429 MB/s | 282 MB/s |
A standalone mlane verify of a compressed image runs at 609 MB/s, and
mlane export back to raw at 636 MB/s. Collection is bounded by file count
rather than bytes: 40,000 files (156 MB) collect and verify in 2.6 s.
FTK Imager is the tool this one is shaped after: same E01 layout, same
.txt summary, same defaults (1500 MB segments, fast compression, MD5+SHA1,
verify after writing). What MemoryLane adds is a command line worth scripting,
source verification, resume, and a logical collection whose metadata you can
check. What FTK has is a GUI, mounting, image conversion to more formats, and
two decades of court acceptance.
Guymager is the reference open-source imager: C++/Qt, Linux-only, fifteen years of casework behind it. Against it:
- Closed: speed (native code with a parallel pipeline, 646 MB/s compressed on a laptop, which saturates the source device on the hardware tested); source verification; page-cache bypass.
- Ahead: resume from an interrupted acquisition; cross-platform (macOS, Linux, Windows) where Guymager is Linux-only; targeted logical collection; an independent EWF implementation, which is why it is cross-checked against libewf rather than built on it.
- Behind: field maturity, above all. Guymager has been used in real
casework for years and this has not. For evidence that goes to court, that
outweighs any table above. Also: a Qt GUI, disk-to-disk cloning, AFF output,
and on Linux the cache bypass here is
posix_fadvise(DONTNEED)per block rather thanO_DIRECT, which keeps the cache clear without the alignment constraints but is a weaker guarantee.
Guymager could not be benchmarked head to head: it is Linux-only and no Linux
machine was available. The numbers above are this tool against dd and against
the hardware's own limits.
cargo test cross-checks both containers against the tools that already read
them, in both directions. Each test skips itself when its counterpart is
absent (brew install libewf / apt install ewf-tools).
EWF, against libewf: the implementation behind Autopsy, The Sleuth Kit and
ewfmount:
ewfverifyreports SUCCESS on every combination of compression level and split size;ewfinforeads the case metadata and theerror2defect list and identifies the files as EnCase 5.ewfexportreproduces the original source byte for byte.- In reverse, MemoryLane reads multi-segment images
ewfacquirewrote inencase5,encase6andftkformats, matching their stored digests.
Collections, against unzip and Python's zipfile: between them what an
examiner's other tools use:
unzip -tvalidates every member's CRC independently; extracting reproduces the source files byte for byte.- Python's
zipfilereads the central directory, the UTF-8 name flag and both manifests, and re-derives every per-file MD5, SHA-1 and size the manifest claims, knowing nothing about this tool.
Whole-disk devices need privileges: sudo on macOS/Linux, an elevated shell on
Windows. On macOS MemoryLane automatically switches /dev/diskN to the much
faster raw character device /dev/rdiskN. A hardware write-blocker is still
the right answer for real evidence.
sudo mlane acquire /dev/disk4 -o /mnt/target/case/usb # macOS
sudo mlane acquire /dev/sdb -o /mnt/target/case/usb # Linux
mlane acquire \\.\PhysicalDrive1 -o E:\case\usb # Windows (admin shell)What is unproven on Windows is physical-device acquisition: CI has no disks
to image, so \\.\PhysicalDrive reads have never run against real hardware.
The mounted-source guard and the destination-on-source check are also no-ops
there, because the volume-to-disk mapping is only implemented for macOS and
Linux, on Windows those two guards will not stop you. Everything else,
including the whole test suite, runs on Windows in CI. File and image sources
are fully exercised on all three platforms.
Local timestamps come from the C library's zone rules, so the .txt summary
reads in the examiner's own time zone on every platform; manifests record UTC
alongside the raw epoch value, because they are read by machines.
cargo test133 tests. They validate the written E01 against the EWF structure spec (descriptor chain, section order, every adler32, the volume and table fields), round-trip every compression level and segment layout, exercise resume across segments and after a simulated hard kill, and assert that tampered, truncated, aborted and corrupt images are detected rather than silently accepted.
The collection suite attacks the container from every side a container can be attacked from, a member's bytes changed in place, a member removed, a member added afterwards, a manifest edited, the two manifests made to disagree, and checks each is reported. The ZIP64 paths are exercised both by a member with 64-bit size fields and by an archive with more members than the plain end record can count.
Also covered: the calendar arithmetic over every day in four centuries, the
digests against their published vectors, mount-table and diskutil parsing
against captured output, glob matching, CSV and JSON round trips, and the
manifest parser's linear-time behaviour. It was quadratic once, which turned a
40,000-file collection from a second into three minutes.
MIT