ntfs-forensic 0.6.0

Forensic anomaly auditor for NTFS — timestomping, alternate data streams, deleted records, and MFT-record slack as graded report::Finding, built on ntfs-core
Documentation

ntfs-forensic

ntfs-forensic ntfs-core Docs.rs License: MIT CI Sponsor

Hand it a raw MFT record, get back severity-graded NTFS anomalies — timestomping, alternate data streams, deleted records, and record slack as forensicnomicon::report::Findings.

[dependencies]
ntfs-forensic = "0.5"   # pulls in ntfs-core
use ntfs_forensic::audit_record;
use forensicnomicon::report::Source;

let src = Source { analyzer: "ntfs-forensic".into(), scope: "NTFS".into(), version: None };

for anomaly in audit_record(&mft_record_bytes) {
    let finding = anomaly.to_finding(src.clone());
    println!("[{:?}] {}{}", finding.severity, finding.code, finding.note);
    // e.g. [Some(High)] NTFS-TIMESTOMP — $SI created before $FN}

audit_record parses the record header and attributes, extracts $STANDARD_INFORMATION/$FILE_NAME, and grades what it finds. A record whose header does not parse yields no anomalies (structural corruption is surfaced by the reader/carver, never a panic). Already have parsed components? Skip the re-parse and call audit_components(record_number, header, record, attributes, si, fname).

The anomaly codes

Each anomaly is an observation ("consistent with …"); the examiner draws the conclusions. Codes are a stable, published contract.

Code Severity What it observes
NTFS-TIMESTOMP High $STANDARD_INFORMATION times show forgery tells vs. the harder-to-forge $FILE_NAME times ($SI predates $FN, or lands on a whole second)
NTFS-ADS Low A named $DATA attribute — an alternate data stream (also used benignly, e.g. Zone.Identifier)
NTFS-SLACK-RESIDUE Low Non-zero residue in an MFT record's slack, past its used size
NTFS-DELETED-RECORD Info An MFT record not in use — a recoverable deleted file
NTFS-MFTMIRR-MISMATCH High A system record in $MFT differs from its $MFTMirr copy
NTFS-LOGFILE-CLEARED Medium $LogFile shows restart-area gaps consistent with the journal having been cleared

The first four come from audit_record / audit_components. The volume-level pair come from audit_mft_mirror($MFT, $MFTMirr) and audit_logfile($LogFile), returning ArtifactAnomalys that also convert via to_finding(source).

The building blocks

audit_record is composed from pure, side-effect-free primitives you can call directly:

  • detect_timestomp(si, file_name)TimestompIndicators { si_created_before_fn, created_mismatch, si_whole_second }
  • alternate_data_streams(attributes) → the named $DATA attributes
  • record_slack(record, header) → the bytes past the record's used size
  • is_deleted(header) → record not currently allocated
  • carve_file_records(mft, record_size) → offsets of FILE/BAAD records in a raw $MFT region

$UsnJrnl:$J change-journal analysis

Beyond MFT-record anomalies, this crate analyses the USN change journal (decoded by ntfs-core):

  • rules — a configurable rule engine (RuleSet / Rule, glob + regex filename and reason-flag matching) whose hits convert to graded report::Findings via RuleMatch::to_finding.
  • analysis — pattern detectors for secure deletion (SDelete / cipher), USN-journal clearing, ransomware, and timestomping.
  • correlation — cross-references USN ↔ $LogFile$MFT to surface ghost records, coverage gaps, entry reuse, and timestamp conflicts.
  • triage — a TriageEngine with 12 built-in investigative questions over reconstructed records.

These power the thin usnjrnl-forensic CLI, which adds output formats (JSON / CSV / SQLite / TLN / body) and live monitoring on top.

The two-crate split

This crate is the analyzer; the reader is ntfs-core ($MFT, attributes, indexes, data runs, LZNT1, the full $UsnJrnl:$J reader stack — streaming reader, carver, RewindEngine path reconstruction, MftData — and NtfsFs path navigation over any Read + Seek source). The split mirrors vmdk-core/vmdk-forensic. Together they back issen and usnjrnl-forensic.

Trust, but verify

Built for untrusted disk images from potentially compromised systems: #![forbid(unsafe_code)]; panic-free on crafted input (the workspace denies clippy::unwrap_used / expect_used in production code); ntfs-core is fuzzed with seven cargo-fuzz targets, cross-validated against The Sleuth Kit and the mft crate on real disk images, and held at 100% line coverage in CI.


Privacy Policy · Terms of Service · © 2026 Security Ronin Ltd