ThreatFalcon is an experimental Windows endpoint telemetry sensor written in Rust.
The project is aimed at a transparent, explainable, open source sensor that collects host telemetry and normalizes it into structured JSONL events.
ThreatFalcon is early-stage software.
- Windows is the primary target platform
- ETW collection is implemented
- Sysmon subscription and parsing are implemented, but disabled by default
- Evasion-oriented signal collection is implemented for selected techniques
- Sensor health events are available for local runtime visibility
- Configuration is loaded from
threatfalcon.toml(TOML format), overridable via--config - Output supports file, stdout, and HTTP POST sinks
- Windows service mode is supported (SCM start/stop via
--serviceflag) - Process context enrichment provides stable process identity across PID reuse
- Local investigation CLI (
query,explain,bundle,stats,tail,tree,inspect,ioc,hunt,score,alert,replay) reads JSONL output directly - Real-time detection monitoring (
alert) with hunt rule evaluation, per-process scoring, deduplication, and webhook output - JSONL event replay (
replay) with speed control for offline testing and rule tuning - Hunt/score/alert rule parameters are configurable via
[rules]inthreatfalcon.toml - Optional SQLite index for fast lookups on large JSONL files (transparent fallback to full scan)
- The event schema and collector behavior may still change
- Keep the sensor small and auditable
- Use straightforward event models instead of opaque binaries
- Emit machine-readable events that are easy to inspect and test
- Prefer explicit collection and signal logic over black-box behavior
- Maintain a single normalized event model across collection sources
ThreatFalcon currently emits normalized telemetry for:
- process events
- file events
- network events
- registry events
- image load events
- DNS events
- script-related events (PowerShell ScriptBlock with path/ID correlation, AMSI scan with result classification)
- evasion-oriented signal events for selected techniques
- sensor health events (periodic heartbeat with uptime, throughput, and collector status)
It currently collects from:
- ETW
- Sysmon
- an in-process evasion-oriented signal collector
ThreatFalcon normalizes these inputs into a unified ThreatEvent model for downstream inspection and comparison.
The built-in default configuration currently does the following:
- writes events to
threatfalcon_events.jsonl - rotates output at
100 MB - enables ETW collection
- disables Sysmon collection by default
- enables evasion-oriented checks
- emits periodic health events every 60 seconds by default
- disk spool for the HTTP sink is disabled by default (opt-in via
spool_dir)
By default, ThreatFalcon writes events locally only and does not transmit telemetry to a remote service.
On Windows, when no config file is found, all data paths default under %ProgramData%\ThreatFalcon\ (typically C:\ProgramData\ThreatFalcon\):
| File | Default path (Windows) | Default path (dev / non-Windows) |
|---|---|---|
| Config file | %ProgramData%\ThreatFalcon\threatfalcon.toml |
./threatfalcon.toml |
| Agent state | %ProgramData%\ThreatFalcon\threatfalcon.state |
./threatfalcon.state |
| Event output | %ProgramData%\ThreatFalcon\threatfalcon_events.jsonl |
./threatfalcon_events.jsonl |
Config file lookup order (when --config is not specified):
./threatfalcon.toml(current working directory)%ProgramData%\ThreatFalcon\threatfalcon.toml(Windows only)- Built-in defaults if neither exists
Relative paths in the config file are resolved against the config file's directory, not the process working directory. This ensures consistent behavior between foreground mode and Windows service mode (where cwd is typically System32). Explicit absolute paths in the config are never rewritten.
When the HTTP sink is configured with spool_dir, failed batches are written to disk instead of dropped. Spooled batches are re-sent on the next successful POST or periodic health flush (every health_interval_secs), whichever comes first. This prevents event loss during network outages or server maintenance. Spool size is capped by spool_max_mb (default: 256 MB).
The default ETW provider set includes:
Microsoft-Windows-Kernel-ProcessMicrosoft-Windows-Kernel-FileMicrosoft-Windows-Kernel-NetworkMicrosoft-Windows-Kernel-RegistryMicrosoft-Windows-DNS-ClientMicrosoft-Windows-PowerShellMicrosoft-Antimalware-Scan-InterfaceMicrosoft-Windows-Threat-Intelligence
ThreatFalcon is split into a small number of clear components:
src/sensor.rs: collector lifecycle, event channel, shutdown handlingsrc/events.rs: unified event schema (ThreatEvent,ProcessContext)src/process_cache.rs: in-memory process context cache and enrichmentsrc/output.rs: sink abstraction (file, stdout, HTTP POST)src/collectors/etw.rs: ETW real-time session and event mappingsrc/collectors/sysmon.rs: Sysmon Event Log subscriptionsrc/collectors/sysmon_parser.rs: Sysmon XML parsing and mappingsrc/pe.rs: cross-platform PE header parser (sections, imports, exports)src/investigate.rs: local investigation CLI (query, explain, bundle)src/collectors/evasion.rs: evasion-oriented process inspection
Requirements:
- Rust stable
- Windows for real sensor execution
Build locally:
cargo build --releaseRun tests:
cargo testCross-check Windows compilation from another platform:
cargo check --target x86_64-pc-windows-msvcThreatFalcon is intended to run on Windows.
cargo run --releasethreatfalcon [OPTIONS] [COMMAND]
Commands:
query Query events from a JSONL telemetry file
explain Explain an event with its process context timeline
bundle Bundle an event and related context into a single JSON document
index Build or manage the SQLite index for fast event lookups
stats Show summary statistics for a JSONL telemetry file
tail Follow new events appended to a JSONL file (like tail -f)
tree Show process tree (parent-child relationships)
inspect Inspect a PE file: headers, sections, imports, and exports
ioc Extract indicators of compromise (IPs, domains, hashes)
hunt Run threat hunting rules against events
score Score processes by threat signals (detections, network, LOLBins, etc.)
alert Monitor events in real time and alert on threat detections
replay Replay saved JSONL events in chronological order with timing
Options:
--config <PATH> Path to config file (default: threatfalcon.toml)
--stdout Force output to stdout (overrides config file)
--output <PATH> Override output file path (overrides config file)
--validate-config Validate config and exit
--dump-default-config Dump default config as TOML and exit
--service Run as a Windows service (used by SCM)
--install-service Install as a Windows service and exit
--uninstall-service Uninstall the Windows service and exit
-h, --help Print help
-V, --version Print version
Examples:
# Run with a custom config file
cargo run --release -- --config /path/to/config.toml
# Output events to stdout for quick inspection
cargo run --release -- --stdout
# Write events to a specific file
cargo run --release -- --output /var/log/threatfalcon.jsonl
# Validate a config file without starting the sensor
cargo run --release -- --config myconfig.toml --validate-config
# Dump the built-in default config
cargo run --release -- --dump-default-config > threatfalcon.tomlLogging can be controlled with RUST_LOG:
RUST_LOG=info cargo run --releaseOn startup, the sensor initializes enabled collectors, writes newline-delimited JSON events, and stops on Ctrl-C.
ThreatFalcon can run as a Windows service. The --service flag connects to the Service Control Manager (SCM) so the sensor starts at boot and stops cleanly on service stop commands.
Recommended directory layout:
C:\ProgramData\ThreatFalcon\
threatfalcon.toml # config
threatfalcon.state # agent identity (auto-created)
threatfalcon_events.jsonl # event output (file sink)
Install the service:
# Copy the binary
Copy-Item threatfalcon.exe C:\ProgramData\ThreatFalcon\threatfalcon.exe
# Install (config is auto-discovered from ProgramData)
.\threatfalcon.exe --install-service
# Or with a custom config path baked into the service registration
.\threatfalcon.exe --install-service --config C:\ProgramData\ThreatFalcon\threatfalcon.toml--install-service registers ThreatFalcon with the Service Control Manager as an auto-start service running as LocalSystem. Equivalent to sc.exe create but with correct binary path and arguments.
Start / stop:
sc.exe start ThreatFalcon
sc.exe stop ThreatFalconRemove the service:
.\threatfalcon.exe --uninstall-service--uninstall-service stops the service if it is running, polls SCM until the service reaches Stopped state (up to 30 seconds), then removes it from SCM.
Service state transitions:
StartPending → Running → StopPending → Stopped
StartPending(wait_hint: 10s): config loading, logging init, sensor creationRunning: event loop active, acceptingStopcontrolStopPending(wait_hint: 15s): reported when SCM sends Stop, before sensor begins flushing sinks and writing final health eventStopped: exit code distinguishes success (0), config error (1), runtime error (2)
Notes:
- Service mode refuses the
stdoutsink (there is no console) — usefileorhttp --service,--install-service, and--uninstall-serviceare only available on Windows; on other platforms they exit with an error- Service exit codes match foreground mode: 0 = success, 1 = config error, 2 = runtime error
ThreatFalcon searches for threatfalcon.toml in the current directory first, then %ProgramData%\ThreatFalcon\ on Windows. If no file is found, built-in defaults are used. Use --config <path> to load from a different location, or --stdout / --output <path> to override the output destination from the command line.
Relative paths (state_path, output.path, output.spool_dir) are resolved against the config file's directory. When using built-in defaults (no config file), they are resolved against the executable's directory.
All sections are optional. Only the fields you want to override need to be specified.
# Override hostname (default: auto-detected from environment)
hostname = "WORKSTATION-01"
# Periodic health event interval in seconds (default: 60, 0 = periodic disabled)
# A final shutdown health event is always emitted regardless of this setting.
health_interval_secs = 60
# Path to persistent agent state file.
# A stable agent_id is generated on first run and reused across restarts.
# Default: "threatfalcon.state" (relative to config dir; on Windows defaults
# to %ProgramData%\ThreatFalcon\threatfalcon.state)
# state_path = "threatfalcon.state"
# Output sink type: "file" (default), "stdout", or "http"
[output]
# type = "file"
path = "threatfalcon_events.jsonl"
rotation_size_mb = 100
# type = "stdout"
# pretty = true
# type = "http"
# url = "https://example.com/api/events"
# batch_size = 100
# timeout_secs = 10
# bearer_token = "your-token-here"
# retry_count = 3
# retry_backoff_ms = 100
# gzip = false
# spool_dir = "spool"
# spool_max_mb = 256
# [output.headers]
# X-Sensor-Id = "sensor-001"
# X-Api-Key = "key-abc"
[collectors.etw]
enabled = true
# Custom provider list (replaces defaults when specified)
# providers = [
# { name = "Microsoft-Windows-Kernel-Process", guid = "22FB2CD6-0E7B-422B-A0C7-2FAD1FD0E716", level = 5, keywords = "0xFFFFFFFFFFFFFFFF" },
# ]
[collectors.sysmon]
enabled = false
[collectors.evasion]
enabled = true
scan_interval_ms = 5000
detect_etw_patching = true
detect_amsi_bypass = true
detect_unhooking = true
detect_direct_syscall = trueETW provider keywords can be specified as a hex string ("0xFFFFFFFFFFFFFFFF") or an integer.
Events are written as JSON Lines. Each event includes:
- unique event ID
- UTC timestamp
- hostname
- stable agent ID (persisted across restarts)
- sensor version
- source
- category
- severity
- typed event payload
- process context (when available — see below)
The sensor maintains an in-memory process context cache, populated from ProcessCreate events. Activity events (file, network, registry, DNS, image load, script, AMSI) are enriched with a process_context field that provides:
process_key: stable process identity derived from{pid}:{create_time}, resilient to PID reuseimage_path,command_line: the executable and its argumentsuser,integrity_level: when available from the sourceppid: parent process ID
ProcessCreate events receive a process_context containing only the process_key (the remaining fields are already in the event payload). ProcessTerminate events receive full context from the cache before the entry is evicted.
The cache is bounded (10,000 entries) and keyed by PID. When a PID is reused, the new ProcessCreate replaces the old entry. ProcessTerminate events verify create_time to avoid evicting a newer process that reused the same PID.
ETW events carry a precise OS-level creation timestamp (create_time), which produces PID-reuse-safe process keys. Sysmon events do not carry a comparable timestamp, so the cache applies source priority: an ETW-backed entry is never overwritten or evicted by a Sysmon event that lacks create_time. In Sysmon-only mode, process keys use a zero create_time ({pid}:0) and PID-reuse protection is best-effort.
Example ProcessCreate event:
{
"id": "2d8f7b0b-8f6b-4d77-b870-4e8c1a2f0f16",
"timestamp": "2026-03-11T00:00:00Z",
"hostname": "HOST01",
"agent_id": "550e8400-e29b-41d4-a716-446655440000",
"sensor_version": "0.3.0",
"source": {
"Etw": {
"provider": "Microsoft-Windows-Kernel-Process"
}
},
"category": "Process",
"severity": "Info",
"data": {
"type": "ProcessCreate",
"pid": 1234,
"ppid": 4321,
"image_path": "C:\\Windows\\System32\\cmd.exe",
"command_line": "cmd.exe /c whoami",
"user": "",
"integrity_level": "",
"hashes": null,
"create_time": 133579284000000000
},
"process_context": {
"process_key": "1234:133579284000000000"
}
}Example enriched NetworkConnect event:
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"timestamp": "2026-03-11T00:00:01Z",
"hostname": "HOST01",
"agent_id": "550e8400-e29b-41d4-a716-446655440000",
"sensor_version": "0.3.0",
"source": {
"Etw": {
"provider": "Microsoft-Windows-Kernel-Network"
}
},
"category": "Network",
"severity": "Info",
"data": {
"type": "NetworkConnect",
"pid": 1234,
"image_path": "",
"protocol": "TCP",
"src_addr": "10.0.0.5",
"src_port": 49152,
"dst_addr": "93.184.216.34",
"dst_port": 443,
"direction": "Outbound"
},
"process_context": {
"process_key": "1234:133579284000000000",
"image_path": "C:\\Windows\\System32\\cmd.exe",
"command_line": "cmd.exe /c whoami",
"ppid": 4321
}
}ThreatFalcon includes built-in investigation commands that read JSONL telemetry output directly — no external SIEM or database required.
Filter and search events from a JSONL file:
# All events for a specific PID
threatfalcon query --input events.jsonl --pid 1234
# Events by stable process identity
threatfalcon query --input events.jsonl --process-key "1234:133579284000000000"
# Network events only
threatfalcon query --input events.jsonl --category network
# Detection events by rule ID
threatfalcon query --input events.jsonl --rule-id TF-EVA-001
# Filter by source type (etw, sysmon, evasion) or ETW provider name
threatfalcon query --input events.jsonl --source etw
threatfalcon query --input events.jsonl --source DNS-Client
# Minimum severity filter (Info, Low, Medium, High, Critical)
threatfalcon query --input events.jsonl --severity high
# Case-insensitive text search across serialized event data
threatfalcon query --input events.jsonl --contains "malware.exe"
# Time range filtering (RFC 3339 timestamps)
threatfalcon query --input events.jsonl --from "2026-03-13T00:00:00Z"
threatfalcon query --input events.jsonl --from "2026-03-13T00:00:00Z" --to "2026-03-13T12:00:00Z"
# --since is a backward-compatible alias for --from
threatfalcon query --input events.jsonl --since "2026-03-13T00:00:00Z"
# Combine filters with a result limit
threatfalcon query --input events.jsonl --pid 1234 --category network --severity medium --limit 50Available query filters:
| Flag | Description |
|---|---|
--pid <N> |
Filter by process ID |
--process-key <KEY> |
Filter by stable process key (pid:create_time) |
--category <CAT> |
Filter by event category (case-insensitive) |
--rule-id <ID> |
Filter by detection rule ID |
--source <SRC> |
Filter by source type (etw, sysmon, evasion, sensor) or ETW provider name substring |
--severity <SEV> |
Minimum severity threshold (accepts info, low, med/medium, high, crit/critical) |
--contains <TEXT> |
Case-insensitive text search across the entire serialized event (Windows paths like C:\Temp\evil.exe are matched automatically) |
--from <TS> |
Only events after this RFC 3339 timestamp (alias: --since) |
--to <TS> |
Only events before this RFC 3339 timestamp |
--limit <N> |
Maximum number of results (default: 100) |
Output is JSONL (one event per line) for easy piping to jq, grep, or other tools. The match count is printed to stderr.
Show detailed context for a single event, including a process timeline of related activity within a time window:
# Full event detail + process timeline (±5 minutes by default)
threatfalcon explain --event <UUID> --input events.jsonl
# UUID prefix matching is supported
threatfalcon explain --event a1b2c3d4 --input events.jsonl
# Custom time window
threatfalcon explain --event <UUID> --input events.jsonl --window 10
# Output as structured JSON (useful for piping to jq)
threatfalcon explain --event <UUID> --input events.jsonl --jsonThe human-readable output includes:
- Target event details (ID, timestamp, category, severity, source, process context)
- Process timeline showing all events from the same
process_keywithin the window - Script / AMSI activity section (when the target is a ScriptBlock, AmsiScan, or AMSI bypass detection — shows correlated script executions and AMSI scan results for the same process)
- Detection rule details (if the event is a detection)
The --json flag outputs a single JSON object containing:
target_event: the full target eventwindow_minutes: the time window usedprocess_key: the target's process key (if available)timeline: array of related events from the same process within the windowscript_amsi_activity: correlated script/AMSI events (when applicable)rule: detection rule metadata (when the target is a detection)
Package an event and its related context into a single JSON document or zip archive for sharing or archiving:
# Bundle to stdout (JSON)
threatfalcon bundle --event <UUID> --input events.jsonl
# Bundle to a JSON file
threatfalcon bundle --event <UUID> --input events.jsonl --output bundle.json
# Bundle to a zip archive
threatfalcon bundle --event <UUID> --input events.jsonl --output bundle.zip
# Custom time window
threatfalcon bundle --event <UUID> --input events.jsonl --window 10 --output bundle.zipJSON output (default, or when the output path does not end in .zip):
bundle_version: schema version (currently 1)target_event: the event being investigatedrelated_events: all events sharing the sameprocess_keywithin the time windowevent_count: total number of events in the bundle- Metadata:
created_at,process_key,window_minutes
Zip output (when the output path ends in .zip):
The zip archive contains four files:
| File | Contents |
|---|---|
manifest.json |
Machine-readable metadata (format, version, event ID, process key, window, event count, time range, file list) |
target_event.json |
The target event (pretty-printed JSON) |
related_events.jsonl |
Related events (one JSON object per line) |
bundle.json |
Full combined bundle (identical to the standalone JSON output) |
The zip format is useful for attaching bundles to tickets, sharing with analysts, or archiving investigations. The manifest.json enables tooling to identify and process bundles programmatically.
Build a SQLite sidecar index for fast event lookups on large JSONL files. The JSONL file remains the source of truth — the index is an optional acceleration layer.
# Build or incrementally update the index
threatfalcon index --input events.jsonl
# Force a full rebuild
threatfalcon index --input events.jsonl --rebuild
# Show index health and coverage
threatfalcon index --input events.jsonl --statusThe index stores event_id, timestamp, pid, process_key, category, source, rule_id, severity, and byte offsets back into the JSONL file. This allows query, explain, and bundle to skip full file scans and seek directly to matching events.
Transparent behavior:
- If an index exists and is current,
query/explain/bundleuse it automatically - If an index exists but is behind (new events appended), it is incrementally updated before querying
- If no index exists, commands fall back to the current full JSONL scan (no index is created implicitly)
- If the index is corrupt, it is deleted and the command falls back to a full scan
- Use
--no-indexon any command to force a full scan (useful for debugging)
The index file is stored alongside the JSONL file as <filename>.idx.sqlite (e.g., events.jsonl.idx.sqlite).
Show summary statistics for a JSONL telemetry file — event counts by category, severity, source, top processes, and detection rule hits:
# Human-readable summary
threatfalcon stats --input events.jsonl
# Structured JSON output
threatfalcon stats --input events.jsonl --jsonExample output:
=== Event Statistics ===
Total events: 12847
Time range: 2026-03-13 08:00:00 UTC → 2026-03-13 18:30:00 UTC
Duration: 10h 30m
--- By Category ---
Network 5230
Process 3412
File 2108
Registry 980
Evasion 117
--- By Severity ---
Critical 3
High 42
Medium 117
Info 12685
--- By Source ---
ETW/Microsoft-Windows-Kernel-Network 5230
ETW/Microsoft-Windows-Kernel-Process 3412
EvasionDetector 117
--- Top Processes (by event count) ---
PID 892 3210 events C:\Windows\System32\svchost.exe
PID 4120 1845 events C:\Program Files\app.exe
--- Detection Rules ---
TF-EVA-001 42
TF-EVA-004 3
Follow new events appended to a JSONL file in real time (like tail -f). Existing content is skipped — only newly appended events are shown:
# Follow all new events
threatfalcon tail --input events.jsonl
# Follow only high-severity events
threatfalcon tail --input events.jsonl --severity high
# Follow events from a specific PID
threatfalcon tail --input events.jsonl --pid 1234
# Follow with text search
threatfalcon tail --input events.jsonl --contains "malware.exe"
# JSONL output for piping to other tools
threatfalcon tail --input events.jsonl --json | jq '.severity'The human-readable format shows timestamp, severity, category, PID, and a one-line event summary. Press Ctrl+C to stop.
Reconstruct and display process trees from ProcessCreate events. Show the descendant tree (children, grandchildren, ...) or the ancestor chain (parent, grandparent, ...) of a given process:
# Show descendants of PID 1234
threatfalcon tree --input events.jsonl --pid 1234
# Show ancestor chain leading to PID 5678
threatfalcon tree --input events.jsonl --pid 5678 --ancestors
# Disambiguate PID reuse with --process-key
threatfalcon tree --input events.jsonl --pid 1234 --process-key "1234:133579284000000000"
# JSON output for programmatic use
threatfalcon tree --input events.jsonl --pid 1234 --jsonExample output (descendants):
=== Process Tree for PID 892 (3 descendants) ===
svchost.exe [PID 892, PPID 600] NT AUTHORITY\SYSTEM [892:133579284000000000]
├─ taskhostw.exe [PID 3120, PPID 892] DESKTOP\user [3120:133579285000000000]
└─ RuntimeBroker.exe [PID 4200, PPID 892] DESKTOP\user [4200:133579286000000000]
└─ cmd.exe [PID 5100, PPID 4200] DESKTOP\user [5100:133579287000000000]
Example output (ancestors):
=== Ancestor Chain for PID 5100 (4 levels) ===
wininit.exe [PID 600, PPID 0] NT AUTHORITY\SYSTEM [600:133579280000000000]
└─ svchost.exe [PID 892, PPID 600] NT AUTHORITY\SYSTEM [892:133579284000000000]
└─ RuntimeBroker.exe [PID 4200, PPID 892] DESKTOP\user [4200:133579286000000000]
└─ cmd.exe [PID 5100, PPID 4200] DESKTOP\user [5100:133579287000000000]
When a PID appears in multiple ProcessCreate events (PID reuse), the tree selects the most recent instance by default. Use --process-key to select a specific instance. Parent-child relationships are resolved using temporal ordering: a child is assigned to the parent instance whose creation time is closest before the child's.
The --json flag outputs a nested JSON tree structure where each node contains pid, ppid, image_path, command_line, user, timestamp, process_key, and children.
Analyze a PE (Portable Executable) file on disk — show headers, sections, imports with suspicious API classification, and exports:
# Inspect a binary found in events
threatfalcon inspect --file C:\Windows\System32\cmd.exe
# JSON output for programmatic analysis
threatfalcon inspect --file suspicious.exe --jsonExample output:
=== PE Inspection: suspicious.exe ===
[Basic Info]
Architecture: PE32+ (AMD64)
Entry Point: 0x00001000
Image Base: 0x0000000140000000
Image Size: 0x00020000
Subsystem: Windows CUI
[Sections] (3)
Name VirtSize RawSize Flags
.text 0x0000A000 0x0000A000 R-X
.rdata 0x00003000 0x00003000 R--
.data 0x00001000 0x00000800 RW-
[Imports] (2 DLLs, 12 functions)
KERNEL32.dll (8 functions)
VirtualAllocEx [!] Process Injection, Memory Manipulation
WriteProcessMemory [!] Process Injection
CreateRemoteThread [!] Process Injection
GetProcAddress [!] Hooking / Dynamic Resolution
...4 more
NTDLL.dll (4 functions)
NtUnmapViewOfSection [!] Defense Evasion
...3 more
[Suspicious API Summary]
Process Injection 3 API(s)
Memory Manipulation 1 API(s)
Hooking / Dynamic Resolution 1 API(s)
Defense Evasion 1 API(s)
[Exports] (0)
(none)
[Warnings]
[!] Section .rwx is writable + executable (RWX)
Suspicious API categories:
| Category | Example APIs |
|---|---|
| Process Injection | VirtualAllocEx, WriteProcessMemory, CreateRemoteThread, NtCreateThreadEx |
| Code Execution | CreateProcessW, ShellExecuteW, WinExec |
| Memory Manipulation | VirtualProtect, VirtualAlloc, NtAllocateVirtualMemory |
| Hooking / Dynamic Resolution | GetProcAddress, LoadLibraryW, SetWindowsHookExW |
| Credential Access | CredReadW, LsaRetrievePrivateData, CryptUnprotectData |
| Defense Evasion | NtUnmapViewOfSection, NtSetContextThread, EtwEventWrite |
Sections with both writable and executable characteristics are flagged as suspicious (common in packed or self-modifying code).
The inspect command works cross-platform — PE files can be analyzed on macOS or Linux (e.g., for post-collection forensic analysis).
Extract indicators of compromise (IPs, domains, file hashes) from telemetry events:
# Extract all IOCs from an event file
threatfalcon ioc --input events.jsonl
# Show only external IPs
threatfalcon ioc --input events.jsonl --type ip
# Show only domains
threatfalcon ioc --input events.jsonl --type domain
# JSON output for integration with other tools
threatfalcon ioc --input events.jsonl --jsonExample output:
=== IOC Extraction (1523 events scanned) ===
[External IPs] (4)
8x 93.184.216.34 (explorer.exe)
3x 8.8.8.8 (svchost.exe)
2x 185.199.108.133 (DNS/github.com)
1x 104.16.132.229 (chrome.exe)
[Domains] (3)
5x evil.example.com
2x github.com
1x api.example.org
[File Hashes] (2)
1x SHA256=abcdef1234567890... (malware.exe)
1x MD5=d41d8cd98f00b204... (rundll32.exe)
The IOC extraction covers:
| IOC Type | Source Events | Notes |
|---|---|---|
| External IPs | NetworkConnect |
Private, loopback, and link-local addresses excluded |
| External IPs | DnsQuery response |
IPs resolved from DNS answers |
| Domains | DnsQuery |
Queried domain names |
| File Hashes | ProcessCreate, ImageLoad |
SHA256=..., MD5=... format preserved |
Results are deduplicated and sorted by frequency (descending). Use --limit to control the number of results per type (default: 50).
Run threat hunting rules against telemetry events to surface suspicious patterns:
# Run all hunting rules
threatfalcon hunt --input events.jsonl
# Run a specific rule
threatfalcon hunt --input events.jsonl --rule suspicious-parent
# JSON output for integration
threatfalcon hunt --input events.jsonl --jsonExample output:
=== Threat Hunt (2841 events scanned, 3 findings) ===
[High] suspicious-parent (T1204.002)
PID: 4521
Process: C:\Windows\System32\cmd.exe
Detail: winword.exe spawned cmd.exe (cmd.exe /c whoami)
Time: 2026-03-13T10:05:12+00:00
[Medium] lolbin (T1218)
PID: 5102
Process: C:\Windows\System32\certutil.exe
Detail: LOLBin execution: certutil.exe (certutil.exe -urlcache -f ...)
Time: 2026-03-13T10:07:33+00:00
[Medium] beaconing (T1071.001)
PID: 3200
Process: C:\Users\user\malware.exe
Detail: 42 connections to 185.100.87.174 from malware.exe
Time: 2026-03-13T09:00:00+00:00
Available hunting rules:
| Rule | Severity | MITRE | Description |
|---|---|---|---|
suspicious-parent |
High | T1204.002 | Office/PDF apps spawning shells or scripting engines |
lolbin |
Medium | T1218 | Execution of living-off-the-land binaries (certutil, mshta, regsvr32, etc.) |
unsigned-dll |
Low | T1574.001 | Unsigned DLL loaded into a process |
beaconing |
Medium | T1071.001 | Repeated connections to the same external IP (threshold: 10+) |
Results are sorted by severity (descending), then by timestamp. Use --limit to control the maximum number of findings (default: 50).
Rank processes by aggregated threat signals to prioritize investigation:
# Score all processes
threatfalcon score --input events.jsonl
# JSON output with full breakdown
threatfalcon score --input events.jsonl --json
# Show only top 5
threatfalcon score --input events.jsonl --limit 5Example output:
=== Process Threat Scores (4821 events scanned) ===
[Score: 62] PID 4521 C:\Users\user\malware.exe
key: 4521:133579284000000000
1x detection (+40), LOLBin (+20), 1 external IP(s) (+2)
[Score: 27] PID 5102 C:\Windows\System32\cmd.exe
key: 5102:133579285000000000
suspicious parent (+20), 3 external IP(s) (+6), 1 DNS query(s) (+1)
[Score: 10] PID 3200 C:\app\helper.exe
key: 3200:133579282000000000
2x unsigned DLL (+10)
Scoring weights:
| Signal | Points | Source |
|---|---|---|
| Evasion detection (with rule) | +40 per detection | EvasionDetected events with RuleMetadata |
| Suspicious parent-child | +20 | Office/PDF app spawning shell (same as hunt) |
| LOLBin execution | +20 | Living-off-the-land binary (same as hunt) |
| Unsigned DLL load | +5 per DLL | ImageLoad with signed: false |
| External IP connection | +2 per unique IP | NetworkConnect to public IPs |
| DNS query | +1 per unique domain | DnsQuery events |
Processes with a score of 0 are excluded. Use --limit to control the number of results (default: 20).
Hunt, score, and alert subcommands accept --config to load rule settings from a TOML file. When --config is not specified, threatfalcon.toml is auto-discovered using the same lookup path as the sensor. See Rules Configuration for details.
Monitor events in real time and alert on threat detections. Applies hunt rules and per-process scoring to each new event as it arrives:
# Follow a live event file and alert on threats
threatfalcon alert --input events.jsonl
# JSON output for structured processing
threatfalcon alert --input events.jsonl --json
# Read from stdin (e.g., piped from replay)
threatfalcon alert --input - --json
# Custom score threshold and cooldown
threatfalcon alert --input events.jsonl --threshold 60 --cooldown 120
# POST alerts to a webhook endpoint
threatfalcon alert --input events.jsonl --json \
--webhook https://hooks.example.com/alerts \
--webhook-token secret123
# Load rule settings from a config file
threatfalcon alert --input events.jsonl --config threatfalcon.toml --jsonAlert types:
| Type | Trigger | Severity |
|---|---|---|
detection |
Evasion detection event with rule metadata | From event |
hunt / lolbin |
LOLBin process execution | Medium |
hunt / suspicious-parent |
Office/PDF app spawning shell | High |
hunt / unsigned-dll |
Unsigned DLL loaded | Low |
score / score-threshold |
Process score reaches threshold | High |
Key features:
- File follow mode: seeks to end of file, watches for new events, handles file rotation
- Stdin mode (
--input -): reads from stdin, exits cleanly on EOF — enables piping fromreplayor other tools - Deduplication: same
process_key+rulecombination is suppressed for--cooldownseconds (default: 300) - Suspicious-parent: works regardless of event order (parent before child or child before parent), with timestamp-based validation to prevent PID-reuse false positives
- Webhook output:
--webhook <URL>POSTs each alert as JSON to an HTTP endpoint. Delivery runs in a background thread so the alert loop is never blocked. Failures are logged to stderr but do not interrupt processing. Optional--webhook-tokenadds Bearer authentication.
Replay saved JSONL events in chronological order with configurable speed control. Events are sorted by timestamp regardless of file order:
# Real-time playback
threatfalcon replay --input captured_events.jsonl
# 10x speed
threatfalcon replay --input captured_events.jsonl --speed 10
# Instant replay (no delays)
threatfalcon replay --input captured_events.jsonl --speed 0
# Replay through alert for offline detection testing
threatfalcon replay --input captured.jsonl --speed 0 | \
threatfalcon alert --input - --json
# Full pipeline: replay + alert + webhook
threatfalcon replay --input captured.jsonl --speed 10 | \
threatfalcon alert --input - --json \
--config threatfalcon.toml \
--webhook https://hooks.example.com/alertsOptions:
| Flag | Description |
|---|---|
--speed <N> |
Playback speed multiplier (default: 1.0 = real time, 0 = instant) |
Output is JSONL to stdout. Progress messages (event count, completion) are printed to stderr. This design enables clean piping to alert, jq, or other tools.
Hunt rules, score weights, and alert defaults can be configured via the [rules] section in threatfalcon.toml. All values are optional — omitted fields use built-in defaults.
[rules.hunt]
# Parent images that should not normally spawn shells
suspicious_parents = [
"winword.exe", "excel.exe", "powerpnt.exe", "outlook.exe",
"msaccess.exe", "mspub.exe", "visio.exe", "onenote.exe",
"acrobat.exe", "acrord32.exe",
]
# Children that are suspicious when spawned from a suspicious parent
suspicious_children = [
"cmd.exe", "powershell.exe", "pwsh.exe", "wscript.exe",
"cscript.exe", "mshta.exe", "regsvr32.exe", "rundll32.exe",
"certutil.exe", "bitsadmin.exe",
]
# LOLBins — legitimate binaries commonly abused
lolbins = [
"certutil.exe", "mshta.exe", "regsvr32.exe", "rundll32.exe",
"wscript.exe", "cscript.exe", "msiexec.exe", "bitsadmin.exe",
"wmic.exe", "msbuild.exe", "installutil.exe", "regasm.exe",
"regsvcs.exe", "cmstp.exe", "esentutl.exe", "expand.exe",
"extrac32.exe", "hh.exe", "ieexec.exe", "makecab.exe",
"replace.exe",
]
# Minimum connection count before flagging as beaconing
beaconing_threshold = 10
[rules.score]
detection = 40
suspicious_parent = 20
lolbin = 20
unsigned_dll = 5
external_ip = 2
dns_query = 1
[rules.alert]
threshold = 40 # Score threshold for score-based alerts
cooldown = 300 # Seconds to suppress duplicate alertsRule string values (process names) are normalized to lowercase on load, so entries like WinWord.exe or CertUtil.exe match correctly.
The hunt, score, and alert subcommands accept --config <path> to load rule settings from a specific file. When --config is not specified, threatfalcon.toml is auto-discovered using the same lookup path as the sensor (current directory, then %ProgramData%\ThreatFalcon\ on Windows). If no config file is found, built-in defaults are used.
CLI flags (--threshold, --cooldown) override config values when specified.
The evasion collector periodically scans running processes for EDR evasion techniques. Each detection emits a structured RuleMetadata with evidence.
| Rule ID | Name | Technique | MITRE | Confidence |
|---|---|---|---|---|
| TF-EVA-001 | ETW Event Write Patching | ntdll!EtwEventWrite patched to ret |
T1562.006 | High |
| TF-EVA-002 | AMSI Scan Buffer Bypass | amsi!AmsiScanBuffer patched to return clean |
T1562.001 | High/Medium |
| TF-EVA-003 | ntdll User-Mode Hook Removal | ntdll .text replaced with clean on-disk copy |
T1562.001 | Medium |
| TF-EVA-004 | Suspicious Direct Syscall Stub | syscall/int 0x2e stubs in non-system module |
T1562.001 | Medium |
Tools like SysWhispers embed syscall stubs directly in malware modules to bypass ntdll user-mode hooks. TF-EVA-004 detects the canonical stub pattern:
mov r10, rcx ; 4C 8B D1 or 49 89 CA
mov eax, <SSN> ; B8 xx xx xx xx
[optional gap] ; up to 12 bytes (Wow64 compat check)
syscall / int 0x2e ; 0F 05 or CD 2E
The scanner enumerates loaded modules per process, skips system DLLs that legitimately contain syscall stubs (ntdll.dll, win32u.dll — verified by full path to System32/SysWOW64, not just basename), parses each module's PE headers to locate the .text section by RVA and characteristics, and matches the stub pattern. Evidence includes the module name, stub offset, SSN value, and raw bytes.
Function locations for ETW patching (EtwEventWrite) and AMSI bypass (AmsiScanBuffer) detection are resolved from the on-disk PE export table at scanner startup, eliminating per-process GetProcAddress calls and enabling AMSI detection even when the sensor process does not have amsi.dll loaded.
TF-EVA-002 detects multiple patterns of AmsiScanBuffer patching:
| Pattern | Bytes | Meaning | Confidence |
|---|---|---|---|
mov eax, 0x80070057; ret |
B8 57 00 07 80 C3 |
Force E_INVALIDARG return (classic) | High |
mov eax, 1; ret |
B8 01 00 00 00 C3 |
Force S_FALSE return | High |
mov eax, <imm32>; ret |
B8 xx xx xx xx C3 |
Force arbitrary return value | High |
xor eax, eax; ret |
31 C0 C3 / 33 C0 C3 |
Force S_OK (scan always clean) | High |
ret |
C3 |
Immediate return, function body skipped | Medium |
nop; ret |
90 C3 |
Function body replaced with no-op | Medium |
Evidence includes the decoded instruction, raw bytes, amsi.dll base address in the target process, and the function RVA from the export table.
When using explain on a ScriptBlock, AmsiScan, or AMSI bypass detection event, ThreatFalcon shows a dedicated "Script / AMSI Activity" section. This section lists all PowerShell script block executions and AMSI scan results from the same process, making the script execution → AMSI scan → bypass chain visible and explainable.
Note: TF-EVA-004 detects the presence of syscall stubs in non-system modules. It does not confirm that the stubs were executed or that ntdll hooks were actively bypassed. Confidence is Medium to reflect this distinction.
- No installer or packaging yet (service registration is manual via
sc.exe) - Some ETW payload parsing is best-effort and should be validated on real Windows hosts
- Sysmon support depends on Sysmon being installed and configured
- Evasion-oriented checks are heuristic and should be treated as signal, not ground truth
MIT