A passive, read-only Windows host threat-hunting tool for blue teams, incident responders, and security-conscious power users.
ThreatHunter sweeps your machine for the kinds of artifacts attackers leave behind — persistence entries, hidden processes, suspicious drivers, high-entropy / packed binaries, AMSI / YARA matches, beaconing network connections, Defender-tampering events, and more — and reports them as structured findings to the console, a rotating log file, JSONL, syslog, or a webhook.
It does not modify your system. It does not quarantine or kill processes. It does not open ports or beacon out. It is purely an enumeration / detection tool — the human reading the output decides what to do with what it finds.
- Persistence sweep — Run keys, Startup folders, IFEO, Winlogon, BHOs, Scheduled Tasks, Services, WMI subscriptions, screensaver hijacks, keyboard hooks
- Hidden process detection — cross-checks the kernel process list
(
NtQuerySystemInformation) againstpsutil,EnumProcesses, andToolhelp32to surface rootkit-hidden PIDs - Driver inspection with Authenticode signature verification
- File entropy / packer detection plus optional AMSI scanning, YARA rule matching, and VirusTotal reputation lookups
- Real-time process monitor — watches new process creations and flags LOLBins, suspicious parent/child chains, and risky command lines
- Network beaconing detection — flags low-jitter periodic connections (classic C2 pattern) using coefficient-of-variation on inter-arrival times
- ETW monitoring — subscribes to Sysmon and Defender event channels (uses
pywintraceif installed, falls back towevtutilpolling) - Defender posture check — alerts if real-time protection, tamper protection, or cloud delivery are disabled
- Multiple output sinks — colorised console, rotating log file, JSONL, syslog (RFC 5424), and webhook (JSON POST)
- OS: Windows 10 / 11 / Server 2019+ (64-bit). The tool uses many
Windows-only APIs (
ctypesagainstntdll,psapi,winreg, WMI, ETW) and will not run on Linux or macOS. - Python: 3.10 or newer. (3.11+ recommended;
tomllibis then in stdlib.) - Privileges: Best run from an elevated (Administrator) terminal for full visibility into kernel structures, system-owned processes, drivers, and ETW channels. It will run unprivileged but with reduced coverage and will warn you on startup.
# 1. Clone
git clone https://github.com/Barnacules/ThreatHunter.git
cd ThreatHunter
# 2. (Recommended) create a virtualenv
python -m venv .venv
.\.venv\Scripts\Activate.ps1
# 3. Install dependencies
pip install -r requirements.txtrequirements.txt pulls in:
| Package | Purpose |
|---|---|
psutil |
Process / network enumeration |
colorama |
Coloured console output |
requests |
Webhook output sink + VT fallback |
WMI |
WMI subscription / service enumeration |
pywin32 |
Win32 API access (event log, registry, security) |
yara-python |
YARA rule matching against scanned files |
vt-py |
VirusTotal API client (file reputation lookups) |
tomli |
TOML parser (only needed on Python < 3.11) |
pywintrace |
Optional native ETW backend (falls back to wevtutil) |
If a single package fails to install (e.g. you don't have a C toolchain for
yara-python), ThreatHunter degrades gracefully — the corresponding detector
will simply be skipped and a startup warning will tell you which capability
is missing.
yara-pythonships pre-built wheels for most Python/Windows combinations; if pip falls back to source, you'll need a C compiler.pywintraceis optional — without it, ETW events are pulled from Windows Event Log viawevtutilpolling, which is slower but works on a stock system.
This is intentionally blank in the published repo. You must add your own key for file reputation lookups to work.
The repo ships a template called config.example.toml. Your real config
lives in config.toml, which is .gitignored so your API key can never be
committed by accident.
-
Sign up for a free account at https://www.virustotal.com/ and grab your API key from your account profile.
-
Copy the template:
copy config.example.toml config.toml
-
Open
config.tomland paste your key under[virustotal]:[virustotal] api_key = "PASTE_YOUR_KEY_HERE"
-
Save. ThreatHunter will print a startup warning if the key is empty so you know VT lookups are disabled. (If
config.tomldoesn't exist, the tool automatically falls back toconfig.example.tomlso it still runs — just without VT.)
The free VT public API is rate-limited to 4 requests/min and 500/day. The tool's built-in throttle (
max_per_minin config) defaults to 4 to stay inside that.
# Default: quiet console — only WARN / HIGH / CRITICAL findings + a 60s
# heartbeat. Everything (including INFO/DEBUG) goes to threathunter.log
# and threathunter.jsonl.
python threathunter.py
# Show ALL activity on the console (everything except DEBUG)
python threathunter.py --verbose
# Verbose + DEBUG-level diagnostics (firehose — useful when troubleshooting)
python threathunter.py --debug
# Custom log file path
python threathunter.py --log-file C:\logs\hunter.log
# Heartbeat every 30 seconds instead of 60
python threathunter.py --stats-interval 30
# Disable the heartbeat entirely
python threathunter.py --no-stats
# Use a different config file
python threathunter.py --config C:\configs\hunter.toml
# List the detectors that will run
python threathunter.py --list-detectors
# All options
python threathunter.py --help| Mode | Console shows | Log file gets |
|---|---|---|
| (default) | WARN, HIGH, ALERT, CRITICAL + heartbeat lines | Everything |
-v |
All severity levels except DEBUG | Everything |
-d |
Everything including DEBUG | Everything |
The default mode is intentionally calm: if the tool is healthy and your machine is clean, you'll see only the heartbeat ticking by once a minute. Anything noisier than that is something you should look at.
Every minute (configurable) the tool prints a one-line status update:
[2026-05-15 01:46:13.290] HEARTBEAT: heartbeat — uptime=60s files=128 procs=412 conns=87 detectors=13 findings=2 (HIGH=2)
That tells you it's still alive and how much work it has done since startup — files scanned, processes inspected, network connections checked, detectors run, and findings broken down by severity.
Press ESC in the console window, or Ctrl-C.
config.toml controls scan intervals, paths to scan, allowlists, output
sinks, and which detectors are enabled. The defaults are sensible for most
desktops/workstations — read through the file's comments to tune it for your
environment.
Output sinks live under [output]:
console = true/falsejsonl_path = "threathunter.jsonl"syslog_host,syslog_port,syslog_protowebhook_url
Periodic-connection ("beaconing") detectors are notorious for false positives — browsers, OneDrive, push services, telemetry, and Windows itself all beacon. ThreatHunter's beacon detector uses a layered approach to keep the signal high:
A list of process names that are expected to beacon (svchost, browsers, OneDrive, Teams, MsMpEng, etc.). Detected beacons from these processes are demoted to LOW severity — invisible in quiet console mode, but still written to the log file and JSONL so you can audit what was suppressed.
Bundles well-known cloud / vendor IP ranges (Microsoft, Cloudflare, Akamai, Apple, AWS, Google). Beacons whose remote IP falls in these ranges are demoted the same way.
beaconing_min_samples = 20— require ≥ 20 connections before evaluatingbeaconing_min_duration_s = 300— require ≥ 5 min of continuous beaconingbeaconing_interval_threshold = 0.05— coefficient-of-variation threshold
This kills the burst-then-quiet patterns of legitimate apps.
When a connection survives the allowlists and triggers, it is scored on six independent signals (weights configurable):
| Signal | Default weight | Source |
|---|---|---|
unsigned_binary |
30 | Authenticode signature fails |
suspicious_path |
25 | exe under %AppData%/%Temp%/%ProgramData% |
unknown_asn |
20 | no PTR record AND not in cidr_allowlist |
nonstandard_port |
15 | raddr port not in benign_ports |
very_low_jitter |
15 | CV < 0.02 (extremely rigid timing) |
long_sustained |
10 | sustained > 1 hour with consistent timing |
The total score (0–100) maps to severity via [beaconing.severity]:
| Score range | Severity | Visible in quiet mode? |
|---|---|---|
0 – 29 |
LOW | no (logged only) |
30 – 59 |
MEDIUM | yes |
60 – 79 |
HIGH | yes |
80 – 100 |
CRITICAL | yes |
ASN lookups are optional — they require pip install ipwhois. Without
ipwhois, the unknown_asn signal still triggers based on reverse-DNS alone
(which is always available via the stdlib). All lookups are cached and only
executed when an alert is otherwise about to fire, so this stays cheap.
Each beacon finding now includes the breakdown of which signals contributed to its score, so you can tell at a glance why it triggered.
- "Running without admin" — re-launch from an elevated PowerShell / cmd.exe to enable kernel-level checks and ETW.
- "YARA not available" —
pip install yara-python. On Windows, the prebuilt wheel usually works; if you hit a build error, install the MSVC C++ Build Tools or use a Python version with a published wheel. - "VirusTotal API key not set" — see the VT section above.
- Lots of HIGH findings on first run — that's expected. ThreatHunter is
surfacing everything notable; many entries will be legitimate software you
installed (vendor autorun entries, signed drivers, etc.). Triage once, then
add benign items to the allowlists in
config.toml.
Issues and PRs welcome. Please:
- Keep the tool read-only — no write/modify/kill primitives.
- Add detectors as their own class deriving from
Detectorand register them inrun_initial_scan()(or as a backgroundMonitorthread). - New severity levels go through
Detector.emit()so dedup, JSONL, syslog, webhook, and the stats counter stay consistent.
MIT — see LICENSE (add your preferred license file if you fork).
ThreatHunter is a detection tool. It surfaces suspicious artifacts; it does not decide whether they are malicious or remediate them. Use the output to inform investigation by a qualified responder. The authors are not responsible for actions taken (or not taken) based on its output.