Skip to content

Latest commit

 

History

11 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MemoryLane

CI Rust License: MIT

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.

Install

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.

Usage

# 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.dd

Sources 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.

Acquisition

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

The .txt summary

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.

What it writes

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.

Targeted collection

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.

Why not L01?

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.

Evidence-handling behaviour

  • 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 sudo is told to unmount rather than told "permission denied". --force proceeds and warns loudly, even under -q. (macOS and Linux; see the platform notes.)
  • The destination is checked before anything is written. Writing into /dev, /proc or /sys is 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 own error2 section 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 done section, so it reads back as explicitly incomplete rather than as a finished image with a zero tail. --resume continues 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 source re-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-direct turns 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.bin gets a mismatch and no explanation.
  • Damage is reported, not raised. A corrupt chunk is zero-filled and named in the output; verify exits 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: 0 verified, 1 verification failed or damage found, 2 usage/IO error, 130 interrupted (partial evidence left in place).

Machine-readable output

--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.

Speed

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.

Where this sits against the alternatives

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 than O_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.

Verified against the reference implementations

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:

  • ewfverify reports SUCCESS on every combination of compression level and split size; ewfinfo reads the case metadata and the error2 defect list and identifies the files as EnCase 5.
  • ewfexport reproduces the original source byte for byte.
  • In reverse, MemoryLane reads multi-segment images ewfacquire wrote in encase5, encase6 and ftk formats, matching their stored digests.

Collections, against unzip and Python's zipfile: between them what an examiner's other tools use:

  • unzip -t validates every member's CRC independently; extracting reproduces the source files byte for byte.
  • Python's zipfile reads 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.

Platform notes

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.

Tests

cargo test

133 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.

License

MIT

About

Forensic imager in Rust with FTK Imager-compatible output — bit-for-bit E01/raw acquisition, targeted logical collection, read-back verification of image and source, resume.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages