Hayabusa (隼, Japanese for "peregrine falcon") is a Sigma-based threat-hunting and fast-forensics timeline generator for Windows event logs, developed by Yamato Security in Rust. It parses .evtx files (offline or via live analysis of a local host), applies a large built-in library of Sigma detection rules plus Hayabusa-specific rules, and produces a single, readable, chronological timeline of high-signal events with severity levels, MITRE ATT&CK tactics, and rule references. This collapses thousands of raw event-log records into a prioritized incident timeline that an analyst can review quickly.
Hayabusa is purpose-built for DFIR triage. Instead of loading EVTX into a SIEM, an investigator runs a single binary against a directory of collected logs and gets a CSV or JSON timeline plus metrics (events per computer, per Event ID, per channel). Because detections are Sigma-based, coverage tracks the open detection-engineering community, and rules can be updated on demand with update-rules. The tool's output integrates with downstream analysis: CSV opens in Timeline Explorer, JSONL feeds into jq, and timesketch-* profiles export directly into Timesketch.
A frequent finding in Hayabusa timelines is malicious PowerShell — MITRE ATT&CK T1059.001 (Command and Scripting Interpreter: PowerShell) — surfaced via Sigma rules over Event ID 4104 (script-block logging), 4103, and Sysmon process creation. This skill maps to NIST CSF RS.AN-03 (analysis is performed to establish what has taken place during an incident).
When to Use
During incident-response triage, to turn a pile of collected .evtx files into a prioritized timeline.
When you need fast, SIEM-free detection over Windows event logs with community Sigma coverage.
To enumerate suspicious activity (PowerShell, account changes, lateral movement) across many hosts' logs.
To produce metrics (events per computer/Event ID/channel) and pivot keywords for deeper hunting.
To export an incident timeline into Timesketch or Timeline Explorer for collaborative analysis.
Prerequisites
Hayabusa binary. Download a pre-compiled release (Windows/Linux/macOS) from GitHub:
# Linux examplecurl -LO https://github.com/Yamato-Security/hayabusa/releases/latest/download/hayabusa-3.0.0-lin-x64-gnu.zipunzip hayabusa-*.zip && cd hayabusa-*./hayabusa-3.0.0-lin-x64-gnu --version
Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder
Rules over registry-modification events flag persistence
T1053.005
Scheduled Task/Job: Scheduled Task
Event ID 4698/106 rules surface task creation
T1003
OS Credential Dumping
Rules flag LSASS access and credential-dumping patterns
Workflow
1. Update the rule set
Pull the latest Sigma and Hayabusa rules before every investigation.
./hayabusa update-rules
2. Build a CSV timeline from collected logs
Point Hayabusa at a directory of .evtx files and write a CSV timeline. -w skips the interactive wizard for scripted runs.
./hayabusa csv-timeline -d ./collected_evtx -o timeline.csv -w# UTC timestamps for cross-host correlation./hayabusa csv-timeline -d ./collected_evtx -o timeline_utc.csv -U -w
3. Choose an output profile
Profiles control detail. Use verbose to include MITRE ATT&CK tactics, tags, and the source rule/EVTX file; all-field-info to retain every original field.
Yamato Security: Hayabusa documentation and AnalysisWithJQ guide
Timesketch integration via timesketch-minimal / timesketch-verbose profiles
Scripts 1
agent.py4.5 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3"""Hayabusa timeline helper.Drives the Hayabusa binary to update rules and generate a CSV (and optionallyJSONL) forensic timeline from a directory of .evtx files, then summarizes thedetections by severity and top rule titles for fast triage."""import argparseimport csvimport jsonimport osimport shutilimport subprocessimport sysfrom collections import Counterfrom datetime import datetime, timezonedef find_binary(explicit): """Locate the hayabusa binary.""" if explicit: if os.path.isfile(explicit): return explicit sys.exit(f"[!] Binary not found: {explicit}") for name in ("hayabusa", "hayabusa.exe"): path = shutil.which(name) if path: return path sys.exit("[!] hayabusa not found on PATH; pass --bin /path/to/hayabusa")def run(cmd): """Run a subprocess and stream a short status line.""" print(f"[*] {' '.join(cmd)}") proc = subprocess.run(cmd, capture_output=True, text=True) if proc.returncode != 0: print(proc.stderr[-1000:], file=sys.stderr) return procdef update_rules(binary): run([binary, "update-rules"])def csv_timeline(binary, src, out, profile, min_level, live): cmd = [binary, "csv-timeline", "-o", out, "-p", profile, "-m", min_level, "-w", "-U"] cmd += ["-l"] if live else ["-d", src] proc = run(cmd) if not os.path.isfile(out): sys.exit(f"[!] Timeline not produced. {proc.stderr[-500:]}") return outdef json_timeline(binary, src, out, profile, min_level, live): cmd = [binary, "json-timeline", "-L", "-o", out, "-p", profile, "-m", min_level, "-w"] cmd += ["-l"] if live else ["-d", src] run(cmd) return outdef summarize_csv(path): """Summarize a Hayabusa CSV timeline by level and rule title.""" levels, titles = Counter(), Counter() total = 0 with open(path, newline="", encoding="utf-8", errors="replace") as f: reader = csv.DictReader(f) # Hayabusa columns include "Level" and "RuleTitle" lvl_key = next((k for k in (reader.fieldnames or []) if k.lower() == "level"), None) title_key = next((k for k in (reader.fieldnames or []) if k.lower().replace(" ", "") == "ruletitle"), None) for row in reader: total += 1 if lvl_key: levels[row.get(lvl_key, "")] += 1 if title_key: titles[row.get(title_key, "")] += 1 return {"total_detections": total, "by_level": dict(levels), "top_rules": titles.most_common(15)}def main(): p = argparse.ArgumentParser(description="Hayabusa timeline helper") p.add_argument("-d", "--directory", help="Directory of .evtx files") p.add_argument("--live", action="store_true", help="Live-analyze local logs") p.add_argument("--bin", help="Path to hayabusa binary") p.add_argument("-o", "--output", default="timeline.csv", help="CSV output path") p.add_argument("--jsonl", help="Also write a JSONL timeline to this path") p.add_argument("-p", "--profile", default="verbose", help="Output profile (minimal/standard/verbose/...)") p.add_argument("-m", "--min-level", default="medium", choices=["informational", "low", "medium", "high", "critical"]) p.add_argument("--no-update", action="store_true", help="Skip update-rules") p.add_argument("--report", help="Write JSON summary to this path") args = p.parse_args() if not args.live and not args.directory: p.error("provide -d/--directory or --live") binary = find_binary(args.bin) if not args.no_update: update_rules(binary) csv_path = csv_timeline(binary, args.directory, args.output, args.profile, args.min_level, args.live) if args.jsonl: json_timeline(binary, args.directory, args.jsonl, args.profile, args.min_level, args.live) summary = summarize_csv(csv_path) summary["generated"] = datetime.now(timezone.utc).isoformat() summary["timeline"] = csv_path print(f"\n[+] {summary['total_detections']} detections") print(f"[+] By level: {summary['by_level']}") print("[+] Top rules:") for title, count in summary["top_rules"]: print(f" {count:5d} {title}") if args.report: with open(args.report, "w") as f: json.dump(summary, f, indent=2) print(f"[+] Summary written to {args.report}")if __name__ == "__main__": main()