Forensic knowledge as code — zero-dependency, std-only, embeds in any Rust binary.
- DFIR Handbook
- Module Source Map
- Canonical Source Inventory
- Windows IR Archive Index
- GitHub Pages / API docs:
https://securityronin.github.io/forensic-catalog/
[dependencies]
forensic-catalog = "0.1"use forensic_catalog::ports::is_suspicious_port;
use forensic_catalog::catalog::{CATALOG, TriagePriority};
// Instant port check — no allocations
assert!(is_suspicious_port(4444)); // Metasploit default
// Pull every Critical-priority artifact, sorted for triage
let critical: Vec<_> = CATALOG
.for_triage()
.into_iter()
.filter(|d| d.triage_priority == TriagePriority::Critical)
.collect();| Module | Covers | Key function / constant |
|---|---|---|
ports |
C2 ports, Cobalt Strike, Tor, WinRM, RAT defaults | is_suspicious_port(port) |
lolbins |
Windows + Linux LOLBins | WINDOWS_LOLBINS, LINUX_LOLBINS |
processes |
Known malware process names | MALWARE_PROCESS_NAMES |
commands |
Reverse shells, PowerShell abuse, download cradles | pattern slices |
paths |
Suspicious filesystem paths | path slices |
persistence |
Windows Run keys, Linux cron/init, macOS LaunchAgents | WINDOWS_RUN_KEYS, LINUX_PERSISTENCE_PATHS, MACOS_PERSISTENCE_PATHS |
catalog |
150+ ArtifactDescriptors with MITRE ATT&CK, triage priority, decode logic |
CATALOG |
antiforensics |
Anti-forensics indicator paths and patterns | indicator slices |
encryption |
FDE artifact paths, credential store locations | path slices |
remote_access |
Remote access tool indicators (RMM, RAT, VPN) | indicator slices |
third_party |
OneDrive, PuTTY, and other third-party app artifact paths | path slices |
pca |
Windows Program Compatibility Assistant artifacts | path / key constants |
references |
Queryable authoritative source map for each public module | module_references(name) |
The catalog module is the power feature. Every artifact descriptor is a const-constructible ArtifactDescriptor — MITRE ATT&CK tags, triage priority, retention period, cross-correlation links, and embedded decode logic all in one static struct. No I/O, no allocation until you query.
use forensic_catalog::catalog::CATALOG;
// All artifacts relevant to process injection
let artifacts = CATALOG.by_mitre("T1055");
for d in &artifacts {
println!("{} — {}", d.id, d.meaning);
}let ordered = CATALOG.for_triage(); // Critical → High → Medium → Low
for d in ordered.iter().take(10) {
println!("[{:?}] {} — {}", d.triage_priority, d.id, d.name);
}let hits = CATALOG.filter_by_keyword("prefetch");
// matches on name or meaning, case-insensitiveuse forensic_catalog::catalog::{ArtifactQuery, DataScope, HiveTarget};
let hits = CATALOG.filter(&ArtifactQuery {
scope: Some(DataScope::User),
hive: Some(HiveTarget::NtUser),
..Default::default()
});use forensic_catalog::catalog::CATALOG;
let descriptor = CATALOG.by_id("userassist").unwrap();
let record = CATALOG.decode(descriptor, value_name, raw_bytes)?;
// record.fields — decoded field name/value pairs
// record.timestamp — ISO 8601 UTC string, if present
// record.mitre_techniques — inherited from the descriptorArtifactDescriptor fields
| Field | Type | Description |
|---|---|---|
id |
&'static str |
Machine-readable identifier (e.g. "userassist") |
name |
&'static str |
Human-readable display name |
artifact_type |
ArtifactType |
RegistryKey, RegistryValue, File, Directory, EventLog, MemoryRegion |
hive |
Option<HiveTarget> |
Registry hive, or None for file/memory artifacts |
key_path |
&'static str |
Path relative to hive root |
scope |
DataScope |
User, System, Network, Mixed |
os_scope |
OsScope |
Win10Plus, Linux, LinuxSystemd, etc. |
decoder |
Decoder |
Identity, Rot13Name, FiletimeAt, BinaryRecord, Utf16Le, … |
meaning |
&'static str |
Forensic significance |
mitre_techniques |
&'static [&'static str] |
ATT&CK technique IDs |
fields |
&'static [FieldSchema] |
Decoded output field schema |
retention |
Option<&'static str> |
How long artifact typically persists |
triage_priority |
TriagePriority |
Critical / High / Medium / Low |
related_artifacts |
&'static [&'static str] |
Cross-correlation artifact IDs |
- Zero dependencies —
Cargo.tomlhas no[dependencies]. No transitive supply-chain risk. - No I/O — every function operates on values passed in. Reading files, registry, or memory is the caller's job.
const/static-friendly —ArtifactDescriptorand all its enums are constructible inconstcontext. Extend the catalog at compile time.- Test-driven — every indicator table has positive and negative test cases. Run
cargo testto verify coverage. - Additive — each module is independent. Pull in only what you need.
This project is a forensic catalog first, not a full DFIR parsing engine.
- Parsing knowledge is layered:
ContainerProfilemodels how to open and enumerate the outer container such as an offline Registry hive, SQLite database, EVTX log, OLE compound file, or memory image.ContainerSignaturemodels how to recognize or carve that container from raw bytes in unallocated space or memory.ArtifactDescriptoridentifies where the artifact lives inside that container or on disk.ArtifactParsingProfilecaptures artifact-specific semantics such asUserAssistROT13 or BITS job reconstruction.RecordSignaturemodels how to recognize or validate individual records or payloads inside a container, including carved fragments.Decoderis reserved for compact, stable transforms we can safely implement in-core.- Keep
ArtifactDescriptorfor artifact location, significance, field schema, ATT&CK mapping, triage value, and authoritative citations. - Keep
ArtifactParsingProfilefor format knowledge and analyst guidance that does not fit a small stable decoder. - Implement in-core decoders only for compact, stable encodings where the logic is intrinsic to the artifact model, such as
UserAssist,MRUListEx,FILETIME,REG_MULTI_SZ, or PCA record layouts. - Do not keep pushing large or evolving formats such as
hiberfil.sys, BITS job stores, or full WMI repository parsing into this crate's core decode engine. - If execution-grade parsers are needed later, put them in a separate parsing module or companion crate rather than turning the catalog itself into a full parser framework.
The repository keeps DFIR knowledge in multiple linked layers: source corpus inventory, module-level references, artifact descriptors, parsing guidance, and carving/signature guidance.
flowchart TD
A[Curated DFIR Source Corpus] --> A1[archive/sources/manual-sources.json]
A --> A2[archive/sources/catalog-directories.json]
A --> A3[archive/sources/dfir-feeds.opml]
A1 --> B[scripts/normalize_sources.py]
A2 --> B
A3 --> B
B --> C[archive/sources/source-inventory.json]
C --> D[src/references.rs]
C --> E[docs/module-sources.md]
D --> F[ModuleReference]
E --> G[Maintainer Docs]
C --> H[src/artifact.rs]
H --> I[ArtifactDescriptor]
H --> J[ContainerProfile]
H --> K[ContainerSignature]
H --> L[ArtifactParsingProfile]
H --> M[RecordSignature]
I --> N[CATALOG API]
J --> N
K --> N
L --> N
M --> N
flowchart TD
A[Raw Bytes or Acquired Files] --> B[ContainerSignature]
B --> C[ContainerProfile]
C --> D[ArtifactDescriptor]
D --> E[ArtifactParsingProfile]
E --> F[Decoder]
D --> G[RecordSignature]
G --> E
F --> H[ArtifactRecord]
UserAssist is the canonical example:
ContainerSignature: Registry hive carving guidance likeregf,hbin,nk, andvkContainerProfile: openNTUSER.DATas an offline Registry hiveArtifactDescriptor: locateUserAssist\\{GUID}\\CountArtifactParsingProfile: ROT13 the value name and interpret the Count payloadRecordSignature: validate the 72-byteUserAssistCount payload when carving fragmentsDecoder: perform the actual ROT13 and binary field extraction
Module-level research provenance is available through forensic_catalog::references.
use forensic_catalog::references::module_references;
let refs = module_references("persistence").unwrap();
assert!(refs.urls.iter().any(|url| url.contains("attack.mitre.org")));Artifact-level provenance remains embedded directly in the catalog:
use forensic_catalog::catalog::CATALOG;
let desc = CATALOG.by_id("userassist_exe").unwrap();
assert!(!desc.sources.is_empty());RapidTriage— live incident response triage tool