ntfs-forensic
Forensic-grade NTFS reader for Rust. A clean, from-scratch implementation that reads files and directories from any Read + Seek source — and goes beyond a normal filesystem driver to surface the artifacts an examiner needs: timestomping indicators, alternate data streams, deleted MFT records, and record slack that a "clean" reader is designed to hide.
Rust library
[]
= "0.1"
Quick start
use NtfsFs;
use File;
// Open an NTFS volume (a raw partition image, or any Read + Seek source).
let mut fs = open?;
// Read a file by path…
let hosts = fs.read_file?;
// …or list the root directory (MFT record 5).
let root = fs.read_record?;
for entry in fs.directory_entries?
# Ok::
What makes this different from a general-purpose NTFS crate
Most NTFS crates answer one question: "what files are on this volume?" ntfs-forensic answers the questions a digital forensics examiner actually needs:
| Capability | General-purpose NTFS crate | ntfs-forensic |
|---|---|---|
| MFT record + attribute parsing | ✅ | ✅ |
Directory index traversal ($INDEX_ROOT / INDX) |
✅ | ✅ |
| Data runs, sparse files, LZNT1 decompression | ✅ | ✅ |
$ATTRIBUTE_LIST (heavily fragmented files) |
partial | ✅ |
$SI-vs-$FN timestomping detection |
✗ | ✅ |
| Alternate data stream enumeration | ✗ | ✅ |
Deleted-record carving (unallocated FILE/BAAD) |
✗ | ✅ |
| MFT record slack extraction | ✗ | ✅ |
| Update-sequence (fixup) torn-write / tamper detection | ✗ | ✅ |
| Partition-window isolation (cannot read past the volume) | ✗ | ✅ |
| Adversarial-input hardening + fuzz testing | ✗ | ✅ |
#![forbid(unsafe_code)] |
— | ✅ |
Forensic capabilities
Every analysis is a pure function over already-parsed structures — exact and side-effect free.
Timestomping detection
NTFS keeps two timestamp sets: $STANDARD_INFORMATION (updatable via the Win32 API — what timestomp tools target) and $FILE_NAME (set by the kernel on create/rename). Divergence between them, or $SI times landing on a whole second, is a tampering tell.
use detect_timestomp;
let flags = detect_timestomp;
if flags.is_suspicious
Alternate data streams
use alternate_data_streams;
for ads in alternate_data_streams
Deleted records and record slack
use ;
// Scan a raw $MFT for FILE/BAAD records at record-size boundaries.
for offset in carve_file_records
# Ok::
Opening a partition inside a whole disk
OffsetReader re-bases a partition to offset 0 and structurally cannot read past the partition boundary — feed it the offset and length from mbr-forensic / gpt-forensic:
use ;
use File;
let part = new?;
let mut fs = open?;
# Ok::
API
| Item | Purpose |
|---|---|
NtfsFs::open / read_file / read_record / directory_entries / resolve_path |
Navigate a volume by path or MFT record number |
BootSector::parse |
Volume boot record (BPB / extended BPB) |
MftRecordHeader::parse / apply_fixup |
FILE records and update-sequence-array fixup |
parse_attributes / Attribute |
Resident and non-resident attribute walking |
StandardInformation / FileName |
The two timestamp sets |
decode_runlist / read_attribute_value |
Data runs (VCN→LCN), sparse + non-resident reads |
IndexRoot::parse / parse_index_buffer |
Directory B-tree ($INDEX_ROOT / INDX) |
parse_attribute_list |
Extension records for fragmented files |
decompress |
LZNT1 decompression |
detect_timestomp / alternate_data_streams / record_slack / is_deleted / carve_file_records |
Forensic Tier-2 |
OffsetReader |
Bounded partition window |
All parsers accept &[u8] or a Read + Seek source and return a typed Result<_, NtfsError>.
Security
ntfs-forensic is designed for use on untrusted disk images from potentially compromised systems:
- No panics on malicious input — every length and offset is validated against both the structure's declared size and the actual buffer; arithmetic is checked or saturating
#![forbid(unsafe_code)]across the whole crate- Bounded allocations —
try_reserve_exactand explicit ceilings refuse allocation bombs (e.g. a crafted runlist or LZNT1 stream) - Loop caps — attribute chains, runlists, and index entries are bounded against non-terminating walks
- Fixup verification — torn writes and USA tampering surface as
FixupMismatchrather than silently-wrong output - Partition isolation —
OffsetReadermakes reading past the volume boundary structurally impossible - Fuzz-tested — seven
cargo-fuzztargets, tens of millions of executions; the one panic they found (an LZNT1 chunk-size overflow) is fixed and pinned as a regression test
Running the fuzz targets
# Requires nightly Rust and cargo-fuzz
Testing
140 unit tests plus a real-image cross-validation test, covering every public API, every error path, and adversarial inputs (truncated records, crafted runlists, torn fixups, out-of-bounds indexes). The boot parser is cross-validated against The Sleuth Kit's fsstat on a real disk image. No source line is left uncovered — enforced in CI.
Aggregate line coverage can read slightly under 100% because the generic, reader-agnostic functions (
NtfsFs<R>,OffsetReader<R>) are monomorphized once per reader type in the tests; the CI gate confirms no source line is left uncovered (zero zero-hit lines inlcov).
Related
ntfs-forensic reads an NTFS volume. To get a Read + Seek over a disk image, and to locate the NTFS partition within it, these crates compose upstream:
| Crate | Role |
|---|---|
disk-forensic |
Orchestrator — auto-detects MBR / GPT / APM and yields each partition's offset / length |
mbr-forensic |
MBR partition table → NTFS partition offset / length |
gpt-forensic |
GPT partition table → NTFS partition offset / length |
apm-forensic |
Apple Partition Map (classic Mac / hybrid media — rarely hosts NTFS) |
ewf-forensic |
E01 / Expert Witness Format container |
vhdx-forensic |
VHDX container |
Sibling crates
One forensic reader per filesystem — each a Read + Seek library that composes with the container and partition crates above:
| Crate | Filesystem |
|---|---|
ext4fs-forensic |
ext2 / ext3 / ext4 |
| ntfs-forensic | NTFS |
Privacy Policy · Terms of Service · © 2026 Security Ronin Ltd