Masstin is a high-speed DFIR tool written in Rust that parses forensic artifacts and unifies lateral movement data into a single chronological timeline. It supports Windows EVTX, Linux logs, Winlogbeat JSON, and EDR APIs — all merged into one CSV, ready for analysis or graph database visualization (Neo4j, Memgraph).
Named after the Mastín Leonés — the guardian dog from the mountains of León, Spain. Like its namesake, Masstin watches over your network and tracks every movement.
Evolved from Sabonis (Python), rewritten in Rust for ~90% faster performance.
We Investigate Anything — Masstin is part of the WIA project, a DFIR knowledge base where you'll find detailed documentation for every artifact masstin parses, investigation guides, and real-world case studies — all in English and Spanish.
Temporal path reconstruction — the attacker's chronologically-valid route between two hosts, rendered from a masstin timeline in Memgraph. Each hop is validated as happening strictly after the previous one.
- Key Features
- Install
- Usage
- Output Format
- Graph Visualization
- All Options
- Supported Artifacts
- Documentation
- Roadmap
- License
- Contact
| Feature | Description | Details |
|---|---|---|
| Unified cross-OS image parsing | Single parse-image command auto-detects OS per partition — NTFS partitions get Windows parsing (EVTX + UAL + VSS + registry), ext4 partitions get Linux parsing (auth.log, wtmp, secure, audit, systemd-journald binary logs) — all merged into one chronological timeline. Zero manual mounting. |
Forensic images |
| Bulk evidence processing | Point -d at an evidence folder and masstin recursively walks it, finds every E01/VMDK/dd image, auto-detects OS, extracts all artifacts from live + VSS, and produces a single unified timeline. Per-image artifact grouping in the summary tells you exactly which image produced which events. One command, entire incident. |
Forensic images |
| Unified 14-column timeline | All sources merged into a single chronological CSV with a canonical 14-column schema. Every event classified as SUCCESSFUL_LOGON, FAILED_LOGON, LOGOFF or CONNECT with human-readable failure reasons. logon_id carried through for session correlation. |
CSV format |
| Custom parsers (YAML) | parse-custom parses arbitrary VPN / firewall / proxy logs via YAML rule files with three extractor types (csv, regex, keyvalue) and nested sub-extract with strip_before preprocessing. Ships with 8 researched rules / 31 sub-parsers out of the box: Palo Alto GlobalProtect, Palo Alto TRAFFIC (with User-ID filter), Cisco AnyConnect, Cisco ASA, Fortinet SSL VPN, FortiGate, OpenVPN, Squid. Every rule backed by vendor documentation — see rules/README.md#references. Full schema in docs/custom-parsers.md. |
Custom parsers |
| Noise filtering | Four opt-in flags to cut output down to signal only: --ignore-local drops records with no usable source (loopback IPs, LOCAL markers, service/interactive logons without src, MSTSC/default_value placeholders); --exclude-users, --exclude-hosts, --exclude-ips accept comma-separated lists, glob wildcards (svc_*, *$) and @file.txt imports; --exclude-ips also accepts CIDR ranges (10.0.0.0/8). Combine with --dry-run for a pre-flight stats report showing exactly what would be filtered. |
Noise filtering |
| Triage-aware discovery | When the directory walker hits a ZIP, masstin lists its top-level entries and matches against three known triage tool layouts: KAPE (_kape.cli / Console/KAPE.log / <host>/C/Windows/System32/winevt/Logs/), Velociraptor Offline Collector (client_info.json + collection_context.json / uploads.json), and Cortex XDR Offline Collector (output/cortex-xdr-payload.log). Detected packages surface as => Triage found: <type> [host: ...] lines in phase 1 with hostname extracted from the ZIP filename when possible. |
Triage detection |
| Per-source breakdown | The phase-2 summary groups every parsed artifact by its source — forensic image, triage zip, plain archive, or loose folder — instead of by its leaf directory name. Each group shows the total event count plus the per-EVTX list underneath. Lets the analyst tell at a glance how many events came from HRServer.e01 vs from a Cortex XDR triage of WIN-DC01 vs from a folder of loose EVTX dropped in D:\evidence\. |
Per-source breakdown |
| Graph visualization | Direct upload to Neo4j or Memgraph with connection grouping and IP-to-hostname resolution. Ships with a Cypher query for temporal path reconstruction — find the chronologically coherent attacker route between any two nodes. | Neo4j |
| Automation-ready | --silent for Velociraptor / SOAR pipelines, single cross-platform binary for Windows / Linux / macOS, no runtime dependencies. |
| Feature | Description | Details |
|---|---|---|
| Forensic images | E01 (ewf), VMDK (flat + sparse + streamOptimized with zlib-compressed grains), raw dd, multi-part images. Handles OVA exports, cloud templates, vSphere backups, and incomplete SFTP uploads via .filepart fallback. |
Forensic images |
| Mounted volumes | Point -d D: at a mounted drive or pass --all-volumes to scan every NTFS disk on the host — live EVTX + VSS recovery without imaging first. |
|
| BitLocker detection | Automatically detects BitLocker-encrypted partitions via the -FVE-FS- VBR signature, warns with the exact offset, and skips encrypted volumes instead of crashing on unreadable data. |
Forensic images |
| Compressed triage | Recursive ZIP extraction with auto-detection of standard forensic passwords (infected, kape, etc.). |
| Feature | Description | Details |
|---|---|---|
| Multi-source Windows EVTX | 32+ Windows Event IDs across 11 EVTX providers: Security, TerminalServices-LocalSessionManager + RemoteConnectionManager, RDPClient, RDPCoreTS, SMBServer, SMBClient + Connectivity, WinRM, WMI-Activity, plus Scheduled Tasks XML. | Artifacts |
| VSS snapshot recovery | Detect and extract EVTX from Volume Shadow Copies — recover event logs an attacker deleted from the live volume. Uses vshadow-rs. | VSS recovery |
| EVTX carving | carve-image scans raw disk data for EVTX chunks (ElfChnk) in unallocated space — recovers lateral movement events even after logs AND VSS are deleted. Implements Tier 1 (full 64 KB chunks) and Tier 2 (orphan record detection); Tier 3 (template matching) is planned. Hardened via thread isolation + catch_unwind + per-file timeout; corrupted chunks can be skipped with --skip-offsets. |
EVTX carving |
| UAL (User Access Logging) | Auto-detect and parse SUM/UAL ESE databases — 3-year server logon history that survives Security event log clearing. Essential for Windows Server forensics where attackers wipe EVTX but forget UAL. | UAL |
| MountPoints2 registry | Extract NTUSER.DAT from every user profile and parse MountPoints2 registry keys — reveals which user connected to which remote share (\\SERVER\SHARE) with timestamps. Survives event log clearing. Supports dirty hives with transaction log recovery (.LOG1 / .LOG2). |
MountPoints2 |
| Linux logs | auth.log, secure, wtmp, audit.log with smart inference: auto-detects hostname, infers year from dpkg.log, supports Debian and RHEL, RFC3164 and RFC5424 formats. Plus pure-Rust systemd-journald binary reader that walks /var/log/journal/<machine-id>/*.journal[~] (compact mode + zstd) and extracts sshd Accepted/Failed events — essential on Ubuntu 22 / RHEL 8+ with SSSD + Active Directory, where /var/log/auth.log is nearly empty because PAM routes auth through the journal. Works on Windows analyst hosts without libsystemd. |
Linux artifacts |
| EDR & SIEM feeds | Winlogbeat JSON export (parser-elastic), Cortex XDR legacy export (parse-cortex), Cortex XDR forensics EVTX export (parse-cortex-evtx-forensics). All feeds normalized to the same 14-column schema so they merge cleanly with host-side artifacts. |
Build note. Masstin builds on stable Rust with a plain
cargo build --release. No nightly, no feature flags, no opt-ins. The pre-built release binaries on the Releases page are compiled from the same stable build path as a localcargo build.
No Rust toolchain needed. Just download and run.
| Platform | Download |
|---|---|
| Windows | masstin-windows.exe |
| Linux | masstin-linux |
| macOS (Apple Silicon, M1+) | masstin-macos-arm64 |
| macOS (Intel) | masstin-macos-x86_64 |
Go to Releases and download the binary for your platform. That's it.
macOS first run: if Gatekeeper blocks the binary ("Apple cannot check it for malicious software"), clear the quarantine attribute once and run normally:
chmod +x masstin-*-macos-* xattr -d com.apple.quarantine masstin-*-macos-*
cargo install masstingit clone https://github.com/jupyterj0nes/masstin.git
cd masstin
cargo build --release
# Binary at ./target/release/masstinQuick reference. The three Windows actions differ by what you feed them, not by the parser underneath — EVTX dispatch is the same in all of them (Provider.Name routing, unknown providers silently skipped).
| Source | parse-windows |
parse-image |
parse-massive |
|---|---|---|---|
| Loose EVTX files and directories | ✅ | ❌ | ✅ |
| Recursive ZIP walk (unlimited nesting) | ✅ | ❌ | ✅ |
Provider.Name fallback for archived / renamed EVTX |
✅ | ✅ | ✅ |
| Forensic disk images (E01, VMDK, raw, dd, img) | ❌ | ✅ | ✅ |
NTFS walker → winevt/Logs + VSS recovery |
❌ | ✅ | ✅ |
UAL databases (LogFiles/Sum/*.mdb) |
❌ | ✅ | ✅ |
Scheduled Tasks XML (System32/Tasks/) |
❌ | ✅ | ✅ |
| MountPoints2 (NTUSER.DAT registry hive) | ❌ | ✅ | ✅ |
| Triage detection (KAPE / Velociraptor / Cortex XDR) with per-source labels | ❌ | ❌ | ✅ |
Loose-artifact promotion of -d directories into the pipeline |
❌ | ❌ | ✅ |
Rule of thumb:
- Folder / ZIP / single EVTX →
parse-windows - Forensic image (
.e01,.vmdk,.raw) →parse-image - Mixed evidence (image + zip + triage + loose files) →
parse-massive
In all three, any EVTX whose Provider.Name matches a channel masstin knows (Security-Auditing, SMBServer, SMBClient, TerminalServices-*, RdpCoreTS, WinRM, WMI-Activity) is parsed — regardless of the filename. Archived logs (Security-YYYY-MM-DD-HH-MM-SS.evtx), operator-renamed copies, and extracts from third-party tooling all route correctly.
Parses Windows EVTX files and UAL databases from directories or individual files, extracting lateral movement events and merging them into a single chronological CSV. Supports compressed triage packages directly — masstin recursively decompresses and identifies all EVTX files, handling archived logs with duplicate filenames.
EVTX dispatch happens by Provider.Name read from the XML, not by filename, so files that do not follow the canonical Windows naming scheme — Security-<YYYY-MM-DD-HH-MM-SS>.evtx produced by the "Archive the log when full" retention policy, operator-renamed files, extracts from third-party tooling — are still routed to the right parser. If you want strict canonical-only matching for speed on a huge noisy tree, point -d directly at the winevt/Logs folder and the walker only opens .evtx and .zip anyway.
Note: The legacy command
parseis still supported as an alias for backwards compatibility.
# Single directory (or compressed triage package)
masstin -a parse-windows -d /evidence/logs/ -o timeline.csv
# Multiple machines
masstin -a parse-windows -d /machine1/logs -d /machine2/logs -o timeline.csv --overwrite
# Individual EVTX files
masstin -a parse-windows -f Security.evtx -f System.evtx -o timeline.csvParses Linux system logs and accounting entries to extract SSH sessions and authentication events. Supports both Debian/Ubuntu (auth.log) and RHEL/CentOS (secure) log formats, with both RFC3164 (legacy syslog) and RFC5424 (structured) timestamp formats.
masstin -a parse-linux -d /evidence/var/log/ -o linux-timeline.csvOne command. Any OS. Any image format. Masstin opens forensic disk images directly, auto-detects every partition type (NTFS or ext4), and applies the right parser to each — all without mounting, external tools, or manual OS identification.
- NTFS partitions → Windows parsing: EVTX + UAL from live volume + VSS snapshot recovery
- ext4 partitions → Linux parsing: auth.log, secure, messages, audit.log, wtmp, btmp, lastlog, and
/var/log/journal/systemd-journald binary logs
All results are merged into a single chronological CSV, deduplicated across sources. This means a folder full of mixed Windows and Linux images — from a ransomware incident spanning dozens of servers — becomes a single unified timeline with one command.
Supports E01, dd/raw, and VMDK (sparse, flat, split sparse, streamOptimized, VMFS/ESXi). Detects BitLocker-encrypted partitions and warns the analyst. Handles incomplete SFTP uploads (.filepart fallback). Pure Rust parsers for all formats. VSS recovery via vshadow-rs. Full documentation →
# Single image — auto-detects OS
masstin -a parse-image -f HRServer.e01 -o timeline.csv
# Mix Windows E01 + Linux VMDK — single merged timeline
masstin -a parse-image -f DC01.e01 -f "kali-linux.vmdk" -o timeline.csv
# Multiple images of any OS
masstin -a parse-image -f DC01.e01 -f SRV-FILE.vmdk -f ubuntu-server.e01 -o incident.csvPoint -d at a folder containing forensic images and masstin recursively scans for all E01, VMDK, and dd/raw files. Each image is opened, partitions are auto-detected (NTFS or ext4), artifacts are extracted with the appropriate parser, and everything is merged into a single chronological timeline. No need to separate Windows and Linux images — masstin handles it all.
# Scan an entire evidence folder — finds all images, any OS
masstin -a parse-image -d /evidence/all_machines/ -o full_timeline.csv
# Mix: evidence folder + individual images + mounted volume
masstin -a parse-image -d /evidence/ -f extra.e01 -d F: -o timeline.csvMasstin automatically filters VMDK split extents (-s001.vmdk), snapshots (-000001.vmdk), flat data files (-flat.vmdk) and change tracking blocks (-ctk.vmdk), keeping only the base descriptor. For E01, only the first segment (.E01) is processed — subsequent segments (.E02, .E03) are loaded automatically.
When you have a mix of forensic images and loose EVTX/log files in the same evidence folder, parse-massive processes everything together. It combines parse-image (for E01/VMDK/dd) with directory scanning (for extracted triage packages and individual EVTX files), producing a single unified timeline.
# Process all images AND loose artifacts from evidence directories
masstin -a parse-massive -d /evidence/ -o everything.csvDifference from
parse-image:parse-imageonly processes forensic images found in-ddirectories.parse-massivealso includes any loose EVTX and log files in those directories — useful when evidence arrives as a mix of disk images and extracted triage packages.
Backward compatibility: The legacy commands
parse-image-windowsandparse-image-linuxare still accepted as aliases forparse-image.
Point masstin at a drive letter and it reads the raw volume directly — extracting all EVTX from the live filesystem and from every VSS snapshot found on the disk. No need to image the disk first. Ideal for triage or when working with a write-blocker.
# Single volume (requires Administrator on Windows)
masstin -a parse-image -d D: -o timeline.csv
# Multiple volumes
masstin -a parse-image -d D: -d E: -o timeline.csv
# Scan all NTFS volumes on the system
masstin -a parse-image --all-volumes -o timeline.csvNote: Reading raw volumes requires elevated privileges — run as Administrator on Windows or with
sudoon Linux.
PowerShell users: Do not end paths with
\inside single quotes — PowerShell interprets\before the closing quote as an escape character, corrupting the command arguments. Masstin detects this and warns you, but the safest approach is to omit the trailing\or use double quotes:-d "C:\evidence\image.vmdk".
Masstin auto-detects UAL databases (.mdb files from C:\Windows\System32\LogFiles\Sum) and extracts server access records going back up to 3 years — surviving event log clearing and rollover. UAL records include username, source IP, role (File Server/SMB, Remote Access/RDP, etc.), access count, and first/last seen timestamps.
# Automatic: UAL is detected when scanning directories or forensic images
masstin -a parse-windows -d /evidence/Windows/System32/LogFiles/Sum/ -o timeline.csv
# Direct: point at individual .mdb files
masstin -a parse-windows -f Current.mdb -f SystemIdentity.mdb -o timeline.csv
# From forensic images: UAL databases are extracted and parsed automatically
masstin -a parse-image -f DC01.e01 -o timeline.csvEach UAL record generates two timeline entries (first seen + last seen). Server hostname is resolved from SystemIdentity.mdb. Roles are mapped to protocols: File Server → SMB, Remote Access → RDP, Web Server → HTTP, etc. Full documentation →
Parses Winlogbeat JSON logs forwarded to Elasticsearch. Extracts the same lateral movement data from JSON format when EVTX files are unavailable.
masstin -a parser-elastic -d /evidence/winlogbeat/ -o elastic-timeline.csvQueries the Cortex XDR API directly to retrieve network connection data or EVTX forensic artifacts collected by Cortex agents.
# Network connection data
masstin -a parse-cortex --cortex-url api-xxxx.xdr.xx.paloaltonetworks.com \
--start-time "2024-08-12 00:00:00" --end-time "2024-08-14 00:00:00" \
-o cortex-network.csv
# EVTX forensics collected by Cortex agents
masstin -a parse-cortex-evtx-forensics --cortex-url api-xxxx.xdr.xx.paloaltonetworks.com \
--start-time "2024-08-12 00:00:00" --end-time "2024-08-14 00:00:00" \
-o cortex-evtx.csvparse-cortex-evtx-forensics queries the Cortex XDR forensics_event_log dataset —
the backing store for Cortex's forensic triage feature, where the XDR forensic
agent collects Windows Event Logs from endpoints on demand. The same dataset also
receives logs uploaded by the Cortex XDR offline collector, so triage packages
gathered from air-gapped or unreachable hosts and pushed into the tenant are
queried through the exact same path. masstin mirrors the event IDs and extraction
logic of parse-windows, so output from this action merges cleanly with host-side
artifacts.
For any log format masstin doesn't natively support (Palo Alto GlobalProtect, Cisco AnyConnect, Fortinet SSL VPN, OpenVPN, Squid, etc.), the parse-custom action reads YAML rule files that describe how to turn each line into a masstin LogData record. The repo ships with a library of 8 researched rules in rules/ that you can use out of the box.
# Run a single rule against a log file
masstin -a parse-custom --rules rules/vpn/palo-alto-globalprotect.yaml -f vpn.log -o timeline.csv
# Run the ENTIRE library — every log file is tried against every rule
masstin -a parse-custom --rules rules/ -f vpn.log -f firewall.log -f proxy.log -o timeline.csv
# Dry-run: show first matches + rejected samples, no CSV written
masstin -a parse-custom --rules rules/vpn/palo-alto-globalprotect.yaml -f vpn.log --dry-run
# Debug: preserve rejected lines sample alongside the output
masstin -a parse-custom --rules rules/ -f vpn.log -o timeline.csv --debugThe library currently covers:
| Rule | Parsers | Format |
|---|---|---|
vpn/palo-alto-globalprotect.yaml |
5 | Palo Alto SYSTEM log subtype=globalprotect (legacy CSV syslog) |
vpn/cisco-anyconnect.yaml |
4 | Cisco ASA %ASA-6-113039/722022/722023 + %ASA-4-113019 |
vpn/fortinet-ssl-vpn.yaml |
3 | FortiGate type=event subtype=vpn (tunnel-up/down/ssl-login-fail) |
vpn/openvpn.yaml |
4 | OpenVPN free-form syslog (Peer Connection / AUTH_FAILED / SIGTERM) |
firewall/palo-alto-traffic.yaml |
2 | PAN-OS TRAFFIC log CSV — authenticated sessions (User-ID) only |
firewall/cisco-asa.yaml |
6 | ASA 113004/113005/605004/605005/716001/716002 |
firewall/fortinet-fortigate.yaml |
4 | FortiGate subtype=system|user admin login, user auth |
proxy/squid.yaml |
3 | Squid access.log CONNECT tunnel, HTTP, TCP_DENIED |
Every rule is researched against vendor official documentation and validated against realistic sample log lines committed under each category's samples/ directory. See rules/README.md for the full references table and docs/custom-parsers.md for the schema specification.
Real forensic cases often generate CSVs with 50%+ of rows that carry no useful lateral movement signal — service logons from LOCAL SYSTEM, RDP failures where the source IP was never captured, brute force attempts from noisy internal jumpboxes, and so on. Masstin ships with four opt-in flags that let you cut the output down to just the records that matter. All four are off by default, so existing workflows are not affected.
# Drop records with no usable source (loopback, service/interactive logons
# without src, LOCAL markers, MSTSC/default_value placeholders)
masstin -a parse-image -d /evidence/ -o timeline.csv --ignore-local
# Exclude known noisy service accounts and machine accounts
masstin -a parse-image -d /evidence/ -o timeline.csv --ignore-local \
--exclude-users 'svc_*,*$,@corpsvc.txt'
# Exclude known jumpbox hostnames
masstin -a parse-image -d /evidence/ -o timeline.csv --ignore-local \
--exclude-hosts 'JUMP01,JUMP02,*-MON,@jumpboxes.txt'
# Exclude internal subnets via CIDR
masstin -a parse-image -d /evidence/ -o timeline.csv --ignore-local \
--exclude-ips '10.0.0.0/8,172.16.0.0/12,fe80::/10'
# Pre-flight: --dry-run with any filter shows a stats breakdown without
# writing the CSV — validate the filter composition before committing
masstin -a parse-image -d /evidence/ -o timeline.csv --ignore-local --dry-run
# Re-filter an existing CSV via merge (no re-parsing of images)
masstin -a merge -f old-timeline.csv --ignore-local --exclude-users @svc.txt \
-o filtered.csvFilter rules
| Flag | Drops records where... | Applies to |
|---|---|---|
--ignore-local |
Neither src_ip nor src_computer carries a useful value. IP useful = valid, non-loopback, non-link-local. Computer useful = non-empty, non--, non-LOCAL, non-MSTSC, non-default_value, non-self-reference. |
All parser actions |
--exclude-users LIST |
subject_user_name OR target_user_name matches any glob in the list (case-insensitive). |
All parser actions + merge |
--exclude-hosts LIST |
dst_computer OR src_computer matches any glob. |
All parser actions + merge |
--exclude-ips LIST |
src_ip matches any individual IP or CIDR range in the list. |
All parser actions + merge |
List syntax (same for all three --exclude-* flags):
- Inline CSV:
svc_backup,svc_monitor,svc_sql - File import:
@users.txt— one entry per line,#for comments - Mix:
svc_foo,@bigfile.txt,admin* - Glob wildcards:
svc_*(prefix),*$(suffix, matches machine accounts),*admin*(contains),exact_match(exact) - CIDR (ips only):
10.0.0.0/24,fe80::/10, individual IPs
Filter summary
After every run with any filter flag active, masstin prints a breakdown:
🧹 Filter summary:
Total records seen: 178,274
Total kept: 110,070 (61.7%)
Total filtered: 68,204 (38.3%)
--ignore-local: 68,204 (38.3%)
both_noise 67,703
self_reference 134
service_logon 306
interactive_logon 21
literal_LOCAL 39
loopback_ip 1
--exclude-users: 523 (0.3%) [3 patterns]
--exclude-hosts: 245 (0.1%) [2 patterns]
--exclude-ips: 12 (0.0%) [1 ranges]
The stats always attribute each filtered record to exactly one cause (the first filter layer that matched), so the numbers add up. Use --dry-run to see this report without writing the CSV.
Safety guarantee: records with a valid routable public src_ip are never filtered by --ignore-local, regardless of what src_computer contains. This preserves brute force and external attack signal even when Windows couldn't resolve a workstation name — the most common missing-metadata case in real forensics.
When the directory walker encounters a ZIP archive, masstin reads its top-level entry list and runs pattern matching against three known triage tool layouts. Detected packages surface as => Triage found: lines in phase 1 and drive the per-source grouping in phase 2 — so the analyst can tell at a glance which events came from which source.
Detection signatures
| Triage tool | Marker (any of) | Hostname extracted from |
|---|---|---|
| KAPE | _kape.cli at any level, Console/KAPE.log, or 5+ entries matching <host>/C/Windows/System32/winevt/Logs/*.evtx |
Filename pattern <host>_<digits>...zip (only when the shape is unambiguous; KAPE has no enforced filename) |
| Velociraptor Offline Collector | Top-level client_info.json + (collection_context.json OR uploads.json); encrypted variant uses metadata.json + data.zip |
Filename pattern Collection-<host>-<YYYY-MM-DD>T...Z.zip |
| Cortex XDR Offline Collector | Any entry ending in cortex-xdr-payload.log (this filename is unique to the XDR collector) |
Filename pattern offline_collector_output_<host>_<YYYY-MM-DD>_<HH-MM-SS>.zip |
Phase 1 output (folder containing 2 triages plus a forensic image with NTUSER.DAT hives). Notice that every counter — triages, EVTX inside compressed archives, MountPoints2 from registry, Scheduled Tasks from XML — appears as => lines INSIDE the same [1/3] block, not scattered before/after the phase header:
[1/3] Searching for artifacts...
=> Triage found: Velociraptor Offline Collector [host: WIN-DC01]
source: K:/CEN26-1164N-B/SFTP/triages/Collection-WIN-DC01-2026-04-13T15_30_00Z.zip
entries inside: 247 (EVTX or other matched files)
=> Triage found: Cortex XDR Offline Collector [host: TESTHOST01]
source: K:/CEN26-1164N-B/SFTP/triages/offline_collector_output_TESTHOST01_2026-04-13_15-30-00.zip
entries inside: 173 (EVTX or other matched files)
420 EVTX artifacts found inside 2 of 2 compressed archives
=> 432 EVTX artifacts found total
=> 12 MountPoints2 remote share events found
=> 13 remote Scheduled Task events found
The source: line under each triage shows the full path to the zip — critical because real cases often have duplicate copies of the same host's triage in different folders (e.g. one in SFTP/... and another in To-Unit42/...). Showing only the filename would make them look identical even though they're physically different files.
Phase 2 output — every artifact is grouped by SOURCE (image, triage, archive, or loose folder), each group showing the total event count plus the per-EVTX list. VSS-recovered events are tagged inline so the analyst can tell at a glance which logs came from a shadow copy vs which came from the live partition:
[+] Lateral movement events grouped by source (4 sources):
=> [IMAGE] HRServer_Disk0.e01 (4521 events total)
- Security.evtx (3220)
- Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx (134)
- Security.evtx (1095) [VSS]
- Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx (72) [VSS]
=> [TRIAGE: Cortex XDR] triages/offline_collector_output_TESTHOST01_2026-04-13_15-30-00.zip [host: TESTHOST01] (834 events total)
- Security.evtx (612)
- Microsoft-Windows-WinRM%4Operational.evtx (89)
- Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx (133)
=> [TRIAGE: Velociraptor] triages/Collection-WIN-DC01-2026-04-13T15_30_00Z.zip [host: WIN-DC01] (4521 events total)
- Security.evtx (4380)
- Microsoft-Windows-WinRM%4Operational.evtx (141)
=> [FOLDER] D:/evidence/loose/extracted_evtx (131 events total)
- Security.evtx (120)
- Microsoft-Windows-TerminalServices-LocalSessionManager%4Operational.evtx (11)
VSS tagging is automatic — the helper detects partition_<N>_vss_<M>/ paths in the temp extraction tree and labels matching entries with a [VSS] suffix (or [VSS-0], [VSS-1] when multiple snapshots from the same image coexist, so each one stays visually distinct). Live entries carry no annotation. Within each source group, items are sorted live-first then by VSS index, so the analyst reads "what the system has now" at the top and "what masstin recovered from snapshots" underneath as a clearly demarcated bonus section. This is exactly the forensic story masstin's VSS recovery feature is supposed to tell.
Triage source labels include the immediate parent directory of the zip (triages/<filename> above) so two physical copies of the same host's triage living in different folders (e.g. SFTP/host.zip vs To-Unit42/host.zip) appear as DIFFERENT source groups instead of collapsing into one bucket with duplicated entries inside.
Source tags are ASCII only — no emoji — so they render correctly in conhost legacy on Windows Server 2016/2019, RDP sessions, mosh/tmux, and any analyst environment regardless of fonts or terminal capabilities. Each tag is colour-coded for visual distinction:
[IMAGE]— cyan — forensic image extract (works for E01, VMDK, dd, all formats)[TRIAGE: <type>]— yellow — detected triage package, with hostname and parent-directory hint[ARCHIVE]— white — ZIP that doesn't match any known triage layout[FOLDER]— dim — loose artifacts in a regular directory, identified by their full parent path (not just the leaf name)[VSS]/[VSS-N]suffix — yellow — appended to individual EVTX entries within an[IMAGE]group when they were recovered from a Volume Shadow Copy
This applies to every parser action that walks directories: parse-windows, parse-image, parse-massive, parse-linux. The same source labels show up regardless of which action you ran, so the breakdown format is consistent across the whole tool.
After the summary, the action prints a load-into-graph hint with both Memgraph and Neo4j commands ready to copy-paste, with the output path canonicalised to the long form (no 8.3 short names like C00PR~1.DES leaking into the suggestion):
Load into graph (pick one):
Memgraph: masstin -a load-memgraph -f C:/Users/c00pr/.../timeline.csv --database localhost:7687
Neo4j: masstin -a load-neo4j -f C:/Users/c00pr/.../timeline.csv --database bolt://localhost:7687 --user neo4j
When the attacker cleared the logs, wiped VSS, and deleted the UAL databases, there's still one place where event data can survive: the unallocated space of the disk itself. carve-image scans the raw image looking for 64 KB EVTX chunks (ElfChnk\x00 magic), validates them, groups them by provider, builds synthetic EVTX files, and feeds them through the normal masstin pipeline.
# Carve a single image
masstin -a carve-image -f server.e01 -o carved.csv
# Carve multiple images at once
masstin -a carve-image -f DC01.e01 -f SRV-FILE.vmdk -o carved.csv
# Skip known-bad offsets on a pathological E01 (corrupted EWF chunks)
masstin -a carve-image -f broken.e01 --skip-offsets 0x6478b6000 -o carved.csv
# Keep rejected synthetic EVTX files for post-mortem / upstream bug reports
masstin -a carve-image -f image.e01 -o carved.csv --debugWhat it implements today:
- Tier 1 — full chunk recovery: complete 64 KB chunks recovered from unallocated space, parsed with full fidelity through the regular pipeline. Events are indistinguishable from live ones in the output.
- Tier 2 — orphan record detection: individual records outside recoverable chunks are counted and reported (header metadata only; full XML reconstruction is Tier 3).
- Tier 3 — template matching: planned. Will reconstruct XML from orphan records using templates harvested from Tier 1 chunks plus a common Windows template library.
Hardened against a hostile ecosystem: the upstream evtx crate was designed to parse well-formed live logs, not arbitrary corrupted 64 KB buffers from unallocated space. We found three classes of bugs during development (infinite loop on malformed BinXML and two unbounded multi-GB allocations that aborted the whole process), reported them upstream, and they were fixed in evtx 0.11.2. A fourth path — a Vec::with_capacity(~16 GiB) inside read_template_values_cursor driven by a corrupt BinXML template-values count — still aborts the process on evtx 0.11.2 because the Rust allocator resolves OOM with abort() (not a panic), so catch_unwind and thread isolation cannot contain it.
To survive that without blocking on an upstream fix, the phase-2 validator spawns a child process per synthetic EVTX via MASSTIN_VALIDATE_EVTX=<path>, which runs masstin::validate_evtx_file and exits 0 on success. If the child aborts by OOM the parent sees a non-zero exit code (Windows 0xC0000409, Linux signal), rejects the offending file, and keeps carving. Verified end-to-end on a 50 GB ws01-wipe-novss.raw: one pathological chunk used to kill the entire run; now it gets quarantined as masstin_rejected_evtx/panic_oom__Security.evtx while the remaining 107 synthetic files parse cleanly.
Defenses kept on top of the subprocess boundary:
std::panic::catch_unwindinside the child for any ordinary panic path in malformed BinXML- 60-second wall-clock poll deadline on the child; hangs are killed and the file is rejected
--skip-offsetslets you tell masstin to jump over a 32 MB window around a problematic E01 offset on re-runs--debugpreserves rejected synthetic EVTX files to<output_dir>/masstin_rejected_evtx/for post-mortem
Full technical breakdown: EVTX carving article.
masstin -a merge -f timeline1.csv -f timeline2.csv -o merged.csv# Neo4j
masstin -a load-neo4j -f timeline.csv --database localhost:7687 --user neo4j
# Memgraph
masstin -a load-memgraph -f timeline.csv --database localhost:7687The loader supports two modes depending on what you are investigating:
Grouped (default) — one edge per unique (destination, user, logon_type) combination. The edge carries a count property (how many events collapsed into it) and a time property (earliest event). This produces a clean, readable graph that answers "who talks to whom and how" — the global picture. Ideal for understanding network topology, mapping trust boundaries, and presenting findings.
Ungrouped (--ungrouped) — one edge per CSV row with its real timestamp. This preserves full temporal granularity so you can query for chronologically coherent paths: "the attacker logged in from A to B at 10:00, then from B to C at 10:05". This is the mode for active hunting. Always pair it with --start-time / --end-time to scope the window — loading an ungrouped 250k-row timeline without a time filter will create an unusable graph.
| Mode | Edges | Best for |
|---|---|---|
| Grouped (default) | ~100-200 | Global overview, topology, presentations |
--ungrouped |
1 per CSV row | Temporal path hunting, incident timeline |
| Flag | Effect |
|---|---|
--ungrouped |
One edge per CSV row (CREATE) instead of grouping. Preserves real timestamps for temporal path queries. Pair with --start-time / --end-time. |
--start-time "YYYY-MM-DD HH:MM:SS" |
Drop rows whose time_created is earlier than this. |
--end-time "YYYY-MM-DD HH:MM:SS" |
Drop rows whose time_created is later than this. |
# Global overview — who talks to whom (default, grouped)
masstin -a load-neo4j -f timeline.csv --database localhost:7687 --user neo4j
# Temporal hunting — every individual event in a 30-minute window
masstin -a load-neo4j -f timeline.csv --database localhost:7687 --user neo4j \
--ungrouped --start-time "2026-03-15 14:00:00" --end-time "2026-03-15 14:30:00"The same physical host often appears as both an IP and a hostname depending on which event populated each row. Both loaders build an internal ip → hostname map and resolve them to a single graph node automatically. Events 4778 (Session Reconnected) and 4779 (Session Disconnected) get an x1000 weight in the frequency map because Windows always populates both fields reliably for those events, so a single 4778/4779 outweighs hundreds of conflicting normal events.
When the loader can't tie an IP to a hostname (for example an external attacker IP with no matching session), the IP stays as its own node.
The loader is built so that every CSV row that makes it past the filters lands as exactly one edge in the graph — no silent drops, no retries hiding failures. Five design choices add up to that guarantee while keeping the load near-linear in edge count:
CREATE INDEX :host(name)at connect time (idempotent). Without it, every per-edgeMERGE (h:host {name: ...})does a full label scan, turning the load into O(V·E) — super-linear and effectively unusable past a few hundred thousand edges. With it, each MERGE is an indexed lookup.- One-pass resolution in Rust. IP→hostname unification, self-loop filtering and relationship-type sanitization all happen client-side once, before anything touches Bolt. The database phase is a pure write — no logic to retry.
UNWINDbatches of 5000 edges per round-trip, bucketed by relationship type (Cypher cannot parametrize a relationship type, and masstin's schema uses the sanitized username as the rel type). One Bolt round-trip per 5000 edges instead of one per edge.- One-shot host-node pre-create, then
MATCH(notMERGE) for the endpoints inside each edge batch. With the index already in place, MATCH is the cheapest possible lookup, and the edge batches no longer reason about node existence. - Strictly serial execution. An opt-in concurrent path was tried and reverted: Memgraph 3.9.x SIGSEGVs under concurrent writes (timing-dependent race), Neo4j tolerated concurrency but the deadlock-retry-backoff overhead actually made it slower than serial on the test corpus, and any retry policy admitted the theoretical risk of silently dropping edges. Serial = no contention = zero retries needed = zero loss by construction.
Measured impact (cumulative load of a 1 M-edge test corpus on the same Memgraph 3.9 host):
| Time | Throughput | Load curve | |
|---|---|---|---|
| Before this optimization | 50.9 min | 327 edges/s | super-linear (exp ≈ 1.20) |
| After (current default) | 23.0 min | 726 edges/s | near-linear (exp ≈ 1.09) |
On Neo4j 4.2 the same loader hits ~12 000 edges/s on the same corpus — Neo4j is about an order of magnitude faster per write than Memgraph for this workload, so a 4 GB / ~27 M-edge CSV that takes ~13 h on Memgraph extrapolates to ~37 min on Neo4j.
By default load-neo4j prompts for the password interactively (rpassword), which is fine on the desk but breaks scripts and CI. Set the NEO4J_PASSWORD environment variable before invoking masstin to skip the prompt:
# Linux / macOS
NEO4J_PASSWORD='your-pass' masstin -a load-neo4j -f timeline.csv \
--database bolt://localhost:7687 --user neo4j
# PowerShell
$env:NEO4J_PASSWORD = 'your-pass'
masstin.exe -a load-neo4j -f timeline.csv `
--database bolt://localhost:7687 --user neo4jWhen the variable is unset or empty the loader falls back to the interactive prompt. The password lives in the process environment — do not export it from a shared shell or a logged dotfile.
Once the graph is loaded, masstin can run 7 detectors against it that surface lateral-movement anomalies — novel edges, chain motifs, credential rotation, community bridges, PageRank/betweenness spikes, rare logon types. Two flavors:
-a graph-hunt— targets Memgraph. Uses MAGE algorithms (pagerank.get,community_detection.get,betweenness_centrality.get) that ship with the default Memgraph install. No extra plugin needed.-a graph-hunt-neo4j— targets Neo4j. Uses the Neo4j Graph Data Science (GDS) library. Requires GDS to be installed in the target Neo4j instance (see prerequisites below).
The detector calls procedures from the Graph Data Science plugin. masstin uses the GDS 2.x API (gds.graph.project, gds.pageRank.stream, gds.louvain.stream, gds.betweenness.stream), which means Neo4j 5.x or later with GDS 2.x is required. The cleanest install path is via Neo4j Desktop's UI:
- Open Neo4j Desktop → select your instance.
- Click the
...menu (top-right of the instance card) → Plugins. - Find Graph Data Science in the list → click Install.
- Restart the instance so the plugin loads into the JVM. Desktop shows the badge "Installed" once the JAR is in place; restarting is what makes the procedures actually callable.
To verify GDS is live after restart:
CALL gds.version() // returns the installed GDS version
CALL gds.list() YIELD name // lists every gds.* procedure availableIf those return Neo.ClientError.Procedure.ProcedureNotFound, the plugin JAR is in plugins/ but the JVM didn't load it — restart the DBMS.
Both actions take a --investigation-from cutoff (any datetime in the corpus). Events strictly before the cutoff form the baseline; events at-or-after form the investigation window. The detectors compare the two.
# Memgraph
masstin -a graph-hunt \
--database bolt://localhost:7687 \
--investigation-from "2026-03-15 00:00:00" \
-o findings.csv
# Neo4j (default database = neo4j; password from $NEO4J_PASSWORD or prompt)
NEO4J_PASSWORD='your-pass' masstin -a graph-hunt-neo4j \
--database bolt://localhost:7687 --user neo4j \
--investigation-from "2026-03-15 00:00:00" \
-o findings.csv
# Neo4j with a named database (useful when one server holds multiple cases)
masstin -a graph-hunt-neo4j \
--database bolt://localhost:7687 --user neo4j \
--db detection-test \
--investigation-from "2026-03-15 00:00:00" \
-o findings.csvThe output CSV is ranked by score and includes the Cypher snippet to inspect each finding in Neo4j Browser / Memgraph Lab.
The seven detectors run quickly on graphs up to a few hundred thousand edges with the default Neo4j heap (1 GB). For DFIR-scale corpora (millions of edges), the GDS in-memory projection needs more headroom — bump the heap via Neo4j Desktop:
- Stop the instance.
...menu → Settings (or openconf/neo4j.confin the instance folder).- Raise
server.memory.heap.max_size(e.g.6G) andserver.memory.pagecache.size(e.g.2G). - Start the instance again.
As a rule of thumb: 6 GB heap comfortably handles ~15 M edges + projection on a modern desktop; 2 GB heap is enough up to ~2-3 M edges.
If the analyst only wants specific signals, --only-detectors / --skip-detectors accept a comma-separated list of detector names: novel-edge, chain-motif, pagerank-spike, betweenness-spike, community-bridge, cred-rotation, rare-logon-type. The two options are mutually exclusive.
# Run only novelty + chain detectors
masstin -a graph-hunt-neo4j --database bolt://localhost:7687 --user neo4j \
--investigation-from "2026-03-15 00:00:00" \
--only-detectors novel-edge,chain-motif -o findings.csvIf you discover post-hoc that two :host nodes are the same physical machine (for example because the loader had no 4778/4779 evidence to unify them), use the merge-*-nodes actions to fuse them. They transfer every relationship from --old-node to --new-node, preserving relationship type and properties, and then delete the orphan node. No APOC or MAGE plugin required — masstin introspects the relationship types client-side and emits one transfer query per type.
# Neo4j
masstin -a merge-neo4j-nodes \
--database bolt://localhost:7687 --user neo4j \
--old-node "10.0.0.10" --new-node "WORKSTATION-A"
# Memgraph
masstin -a merge-memgraph-nodes \
--database localhost:7687 \
--old-node "10.0.0.10" --new-node "WORKSTATION-A"All actions produce a unified CSV with 14 columns:
| Column | Description |
|---|---|
time_created |
Event timestamp |
dst_computer |
Destination hostname |
event_type |
Event classification: SUCCESSFUL_LOGON, FAILED_LOGON, LOGOFF, CONNECT |
event_id |
Original Event ID (e.g., 4624, SSH_SUCCESS, SMB, RDP) |
logon_type |
Windows logon type (e.g., 2, 3, 10) |
target_user_name |
Target user account |
target_domain_name |
Target domain |
src_computer |
Source hostname |
src_ip |
Source IP address |
subject_user_name |
Subject user account |
subject_domain_name |
Subject domain |
logon_id |
Logon ID for session correlation |
detail |
Additional context: SubStatus, process name, SSH auth method, UAL role |
log_filename |
Source file (e.g., HRServer.e01:vss_0:Security.evtx) |
For the complete Event ID mapping, see CSV Format and Event Classification.
Masstin supports two graph databases. Both use the Cypher query language and the same queries work on both with minor differences.
| Step | Windows | Linux | macOS | Docker (all platforms) |
|---|---|---|---|---|
| Install | Download Neo4j Desktop and install | sudo apt install neo4j or download |
brew install neo4j or download |
docker run -p 7474:7474 -p 7687:7687 -e NEO4J_AUTH=neo4j/password neo4j |
| Start | Open Neo4j Desktop, create a database, click Start | sudo systemctl start neo4j |
neo4j start |
Runs automatically |
| Browser | http://localhost:7474 |
http://localhost:7474 |
http://localhost:7474 |
http://localhost:7474 |
| Load data | masstin.exe -a load-neo4j -f timeline.csv --database localhost:7687 --user neo4j |
masstin -a load-neo4j -f timeline.csv --database localhost:7687 --user neo4j |
Same as Linux | Same as Linux |
| Step | Windows | Linux | macOS | Docker (all platforms) |
|---|---|---|---|---|
| Install | Via Docker — requires WSL 2 + Docker Desktop (see below) | sudo apt install memgraph or download |
Use Docker (recommended) | docker compose with memgraph/memgraph-mage + memgraph/lab |
| Start | iwr https://windows.memgraph.com | iex (starts DB + Lab via docker compose) |
sudo systemctl start memgraph |
— | Runs automatically |
| Browser | http://localhost:3000 (Memgraph Lab) |
http://localhost:3000 |
http://localhost:3000 |
http://localhost:3000 |
| Load data | masstin.exe -a load-memgraph -f timeline.csv --database localhost:7687 |
masstin -a load-memgraph -f timeline.csv --database localhost:7687 |
Same as Linux | Same as Linux |
Note: Memgraph runs in-memory. Data is lost on restart unless snapshots are configured.
Graph style: A ready-to-use GSS style for Memgraph Lab is available at
memgraph-resources/style.gss. Copy its contents into the Graph Style editor in Memgraph Lab, click Apply, then click Save style with namemasstinand enable Default Graph Style to apply it automatically to all future queries.
Windows prerequisites for Memgraph (WSL 2 + Docker)
On Windows, Memgraph runs inside a Docker container, and Docker Desktop requires WSL 2. The dependency chain is: WSL 2 → Docker Desktop → Memgraph container.
1. Enable WSL 2 — Open PowerShell as Administrator:
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestartRestart your PC, then:
wsl --update
wsl --set-default-version 2
wsl --install2. Install Docker Desktop — Download from docker.com. Select "Use WSL 2 instead of Hyper-V" during installation. Restart if prompted.
3. Install and run Memgraph:
iwr https://windows.memgraph.com | iexThis downloads a docker-compose.yml and starts the database (memgraph/memgraph-mage) and the web interface (memgraph/lab). Open http://localhost:3000 — Memgraph Lab is ready.
After loading data, use Cypher queries to explore lateral movement.
Neo4j — filter by time range:
MATCH (h1:host)-[r]->(h2:host)
WHERE datetime(r.time) >= datetime("2024-08-12T00:00:00Z")
AND datetime(r.time) <= datetime("2024-08-13T00:00:00Z")
RETURN h1, r, h2Memgraph — view all lateral movement:
MATCH (h1:host)-[r]->(h2:host)
RETURN h1, r, h2Temporal path reconstruction (from 10_99_88_77 to SRV_BACKUP):
MATCH path = (start:host {name:'10_99_88_77'})-[*]->(end:host {name:'SRV_BACKUP'})
WHERE ALL(i IN range(0, size(relationships(path))-2)
WHERE localDateTime(relationships(path)[i].time) < localDateTime(relationships(path)[i+1].time))
RETURN path
ORDER BY length(path)
LIMIT 5For the full query catalog (10+ queries), see the Cypher Resources.
| Option | Description |
|---|---|
-a, --action |
parse-windows | parse-linux | parse-image | parse-massive | carve-image | parser-elastic | parse-cortex | parse-cortex-evtx-forensics | parse-custom | merge | load-neo4j | load-memgraph | merge-neo4j-nodes | merge-memgraph-nodes |
-d, --directory |
Directories to process — also accepts drive letters (D:) for mounted volumes (repeatable) |
-f, --file |
Individual files: EVTX, .mdb, E01, VMDK, dd/raw (repeatable) |
-o, --output |
Output file path |
--database |
Graph database URL (e.g., localhost:7687) |
-u, --user |
Database user (Neo4j) |
--db |
Target database name on the graph server (default neo4j for Neo4j actions, memgraph for Memgraph). Useful in multi-database Neo4j 5.x/2026.x setups. |
--cortex-url |
Cortex XDR API base URL |
--start-time |
Filter start: "YYYY-MM-DD HH:MM:SS" (Cortex actions, merge, load-neo4j / load-memgraph) |
--end-time |
Filter end: "YYYY-MM-DD HH:MM:SS" (same scope as --start-time) |
--ungrouped |
For load-neo4j / load-memgraph: emit one edge per CSV row instead of grouping |
--old-node |
For merge-neo4j-nodes / merge-memgraph-nodes: name of the :host node to remove (its edges are transferred to --new-node) |
--new-node |
For merge-neo4j-nodes / merge-memgraph-nodes: name of the :host node that survives the merge |
--filter-cortex-ip |
Filter by IP in Cortex queries |
--admin-ports |
parse-cortex: widen network port list to full admin set (22, 135, 139, 445, 1433, 3306, 3389, 5900, 5985, 5986). Default is RDP/SMB/SSH + WinRM. |
--cortex-event-ids |
parse-cortex-evtx-forensics: comma-separated override of the default Windows Event ID set |
--cortex-min-window-secs |
Auto-pagination floor for both Cortex actions when a time window saturates the API 1M cap (default 300) |
--cortex-max-passes |
Hard cap on auto-pagination passes for both Cortex actions (default 200) |
--all-volumes |
Scan all NTFS volumes on the system (parse-image, requires admin) |
--overwrite |
Overwrite output file if it exists |
--stdout |
Print output to stdout only |
--debug |
Print debug information |
--silent |
Suppress all output for automation (Velociraptor, SOAR) |
Masstin parses 32+ Windows Event IDs across 11 EVTX sources, plus Linux artifacts, UAL databases, Winlogbeat JSON, and Cortex XDR. For a full breakdown, see ARTIFACTS.md.
| Source | Event IDs | What it tracks | Article |
|---|---|---|---|
| Security.evtx | 4624, 4625, 4634, 4647, 4648, 4768, 4769, 4770, 4771, 4776, 4778, 4779, 5140 | Logons, logoffs, Kerberos, NTLM, RDP reconnect, share access | Read more → |
| TerminalServices-LocalSessionManager | 21, 22, 24, 25 | RDP session lifecycle | Read more → |
| TerminalServices-RDPClient | 1024, 1102 | Outgoing RDP connections | Read more → |
| TerminalServices-RemoteConnectionManager | 1149 | Incoming RDP accepted | Read more → |
| RdpCoreTS | 131 | RDP transport negotiation | Read more → |
| SMBServer/Security | 1009, 551 | SMB server connections and auth | Read more → |
| SMBClient/Security | 31001 | SMB client share access | Read more → |
| SMBClient/Connectivity | 30803-30808 | SMB connectivity and share events | Read more → |
| WinRM/Operational | 6 | PowerShell Remoting session init — destination host from connection field (source system) | Read more → |
| WMI-Activity/Operational | 5858 | Remote WMI execution — source machine from ClientMachine field (destination system) | Read more → |
| Scheduled Tasks XML | — | Remotely registered tasks detected via Author field (MACHINE\user) | Read more → |
| MountPoints2 (NTUSER.DAT) | — | Remote share connections from each user's registry (##SERVER#SHARE with LastWriteTime) | Read more → |
| Source | What it tracks | Article |
|---|---|---|
SystemIdentity.mdb |
Server hostname, role mappings | Read more → |
Current.mdb + {GUID}.mdb |
Username, source IP, role, access count, first/last seen (up to 3 years) | Read more → |
| Source | What it tracks | Article |
|---|---|---|
/var/log/auth.log (Debian/Ubuntu) |
SSH success, failure, PAM authentication | Read more → |
/var/log/secure (RHEL/CentOS) |
SSH success, failure, PAM authentication | Read more → |
/var/log/messages |
SSH events via syslog | Read more → |
/var/log/audit/audit.log |
USER_LOGIN / USER_AUTH from auditd — primary SSH signal on Ubuntu + SSSD |
Read more → |
/var/log/journal/<machine-id>/*.journal[~] |
systemd-journald binary logs — sshd Accepted/Failed events on modern SSSD / AD hosts |
Read more → |
utmp / wtmp / btmp / lastlog |
Login sessions, failed attempts | Read more → |
| Source | What it tracks | Article |
|---|---|---|
| Winlogbeat JSON | All Windows Event IDs in JSON format | Read more → |
| Cortex XDR Network | RDP, SMB, SSH connections via API | Read more → |
| Cortex XDR EVTX Forensics | Forensic event logs from agents | Read more → |
Full documentation at We Investigate Anything — bilingual DFIR knowledge base (English/Spanish).
| Topic | Article |
|---|---|
| Masstin main page | weinvestigateanything.com — masstin |
| CSV format and event classification | CSV Format |
| Forensic images and VSS recovery | VSS Recovery |
| User Access Logging (UAL) | UAL |
| vshadow-rs — pure Rust VSS parser | vshadow-rs |
| Neo4j and Cypher guide | Neo4j |
| Memgraph guide | Memgraph |
| Artifact | Article |
|---|---|
| Security.evtx (14 Event IDs) | Security.evtx |
| Terminal Services EVTX (RDP) | Terminal Services |
| SMB EVTX (Server + Client) | SMB Events |
| Linux forensic artifacts | Linux |
| Winlogbeat JSON | Winlogbeat |
| Cortex XDR | Cortex |
- VHD/VHDX image support
-
Event reconstruction from cleared logs (EVTX record carving)— done (Tier 1 + Tier 2 detection) -
MountPoints2 registry hive parsing for lateral movement traces— done -
Custom parser framework for VPN/firewall/proxy logs (YAML rules)— done (v1: csv/regex/keyvalue + sub-extract + strip_before) -
Initial community rule library— done (8 rules, 31 parsers: Palo Alto GP + TRAFFIC, Cisco AnyConnect + ASA, Fortinet SSL VPN + FortiGate, OpenVPN, Squid) - EVTX carving Tier 3: template matching for orphan records (reconstruct XML from records whose parent chunks are gone)
- Unallocated-only carving scan (
--carve-unalloc) — currently scans the whole image - Custom parsers v2: JSON extractor, conditional map, per-rule
--validatecommand - More community parser rules: Checkpoint, ZScaler, Cloudflare Access, Juniper, SonicWall
- EVTX header tampering detection — flag chunks whose record numbers, timestamps or CRCs have been edited (Event Log Edit / similar tooling)
-
systemd-journald binary log parsing — pure-Rust reader for— done/var/log/journal/*.journal[~](compact mode + zstd), essential on Ubuntu 22 / RHEL 8+ with SSSD + AD - Linux event recovery / carving — recover deleted entries from
auth.log,wtmp,btmp,journaldafter rotation or attacker cleanup - macOS support —
parse-mac(live/var/logand unified logs) andparse-image-mac(HFS+/APFS forensic images), bringing Mac to feature parity with Windows and Linux - Official Velociraptor plugin — package masstin so analysts can run it from a Velociraptor artifact and get a unified timeline back without leaving the platform
GNU Affero General Public License v3.0 — see LICENSE for details.
Toño Díaz (@jupyterj0nes) · LinkedIn · weinvestigateanything.com






