threat hunting

Fleet Hunting with Velociraptor

Deploy a Velociraptor server and agents and write VQL hunts across a fleet.

dfirdigital-forensicsendpoint-visibilityfleet-collectionincident-responsethreat-huntingvelociraptorvql
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

Authorized Use Only: Velociraptor agents provide deep endpoint visibility and remote collection. Deploy only on assets you own or are authorized to monitor, in accordance with your monitoring policy and applicable law.

Overview

Velociraptor is an open-source endpoint visibility and digital-forensics platform from Rapid7/Velocidex. A single Go binary acts as server, client (agent), and CLI depending on how it is invoked and configured. Its power comes from VQL (Velociraptor Query Language) — an SQL-like language whose plugins query the live state of an endpoint (processes, files, registry, event logs, WMI, network connections, prefetch, etc.). VQL queries are packaged into reusable Artifacts, and Artifacts are run at scale as Hunts that fan out across every connected client and stream results back to the server as structured rows.

This makes Velociraptor ideal for fleet-wide threat hunting: a hypothesis ("are any hosts running suspicious PowerShell?") becomes a VQL artifact, deployed as a hunt, with results aggregated centrally in minutes. It also supports offline collectors (standalone executables that collect and bundle artifacts on air-gapped or unmanaged hosts) and live forensic notebooks.

When to Use

  • Hunting for a TTP across hundreds or thousands of endpoints from a single console.
  • Collecting forensic artifacts on demand during incident response without re-imaging.
  • Continuously monitoring for an indicator using client-side event artifacts.
  • Generating standalone offline collectors for hosts you cannot enroll.

Prerequisites

  • A Linux or Windows host for the server (Linux recommended for production).
  • The Velociraptor binary from the official release page: https://github.com/Velocidex/velociraptor/releases
  • Outbound/inbound connectivity from clients to the server frontend port (default 8000) and admin GUI (default 8889).
  • Make the binary executable on Linux:
    chmod +x velociraptor-v0.*-linux-amd64
    sudo mv velociraptor-v0.*-linux-amd64 /usr/local/bin/velociraptor

Objectives

  • Generate server and client configurations.
  • Run the server frontend and admin GUI.
  • Enroll clients across the fleet.
  • Author and test VQL hunts.
  • Launch a fleet-wide hunt and collect results.
  • Produce an offline collector for unmanaged hosts.

MITRE ATT&CK Mapping

ID Official Technique Name Relevance to this skill
T1059 Command and Scripting Interpreter A primary hunt target — VQL artifacts surface anomalous interpreter execution (PowerShell, cmd, wscript) across the fleet for detection and triage.

Velociraptor is a defensive hunting platform; the mapping reflects the adversary behavior the hunts are designed to detect.

Workflow

1. Generate the server configuration

The interactive generator writes a server config (TLS, datastore paths, GUI users, frontend URL). Use config generate for a self-signed lab build or the interactive -i wizard for production.

# Non-interactive: dump a default server config
velociraptor config generate > server.config.yaml
 
# Interactive wizard (recommended for production deployments)
velociraptor config generate -i

2. Add a GUI admin user

Create at least one administrator to log into the console.

velociraptor --config server.config.yaml user add admin --role administrator

3. Start the server frontend and GUI

The frontend accepts client connections; the GUI is served per the config (default https://127.0.0.1:8889).

velociraptor --config server.config.yaml frontend -v

For a quick all-in-one local lab (server + frontend + a local client in one process):

velociraptor gui

4. Generate the client configuration and deploy agents

Derive the client config from the server config and run it as the client on each endpoint.

# Produce the client config (embeds server URL + CA)
velociraptor --config server.config.yaml config client > client.config.yaml
 
# On a Linux endpoint, run as a client (or install as a service)
velociraptor --config client.config.yaml client -v

On Windows, build an MSI/service installer from the GUI ("Server Artifacts" > deployment) or run:

velociraptor.exe --config client.config.yaml service install

5. Test VQL interactively before hunting

Validate a query locally with query (-q) before deploying it fleet-wide. VQL is SQL-like: SELECT ... FROM plugin(...) WHERE ....

# List running processes with their command lines
velociraptor query "SELECT Pid, Name, CommandLine FROM pslist()"
 
# Hunt for suspicious PowerShell command lines
velociraptor query "
SELECT Pid, Name, CommandLine
FROM pslist()
WHERE Name =~ 'powershell'
  AND CommandLine =~ '(?i)(-enc|frombase64string|downloadstring|-w hidden|iex)'
"

6. List and run a built-in artifact

Artifacts wrap VQL into reusable, parameterized collections.

# Show available artifacts
velociraptor artifacts list
 
# Collect a built-in artifact and write results to a directory
velociraptor artifacts collect Windows.System.Pslist --output results.zip

7. Launch a fleet-wide hunt (GUI workflow)

In the GUI: Hunt Manager > New Hunt > select the artifact (e.g. Windows.Detection.Powershell or a custom one) and parameters > Launch. The hunt fans out to every matching client; results stream into the hunt's results table and can be exported as CSV/JSON. Equivalent server-side VQL:

-- Create a hunt programmatically via a server VQL notebook
SELECT hunt(
    description="Suspicious PowerShell fleet sweep",
    artifacts="Windows.Detection.Powershell"
) FROM scope()

8. Build a custom artifact

Custom artifacts are YAML documents containing parameters and VQL sources. Save in the GUI's Artifact editor or import via artifacts:

name: Custom.Hunt.SuspiciousPowershell
description: Find encoded / download-cradle PowerShell across the fleet.
parameters:
  - name: regex
    default: "(?i)(-enc|frombase64string|downloadstring|-w hidden|iex)"
sources:
  - query: |
      SELECT Pid, Name, CommandLine, timestamp(epoch=now()) AS Collected
      FROM pslist()
      WHERE Name =~ "powershell" AND CommandLine =~ regex

9. Generate an offline collector

For unmanaged/air-gapped hosts, build a standalone collector from the GUI ("Server Artifacts" > Server.Utils.CreateCollector) or via VQL; it produces a single executable that collects chosen artifacts into a ZIP for later import.

Tools and Resources

Resource Purpose Link
Velociraptor releases Official binaries https://github.com/Velocidex/velociraptor/releases
Documentation Deployment, VQL, artifacts https://docs.velociraptor.app/
VQL reference Plugin/function reference https://docs.velociraptor.app/vql_reference/
Artifact Exchange Community artifacts https://docs.velociraptor.app/exchange/
Source GitHub repository https://github.com/Velocidex/velociraptor

Key Commands

Command Purpose
config generate [-i] Create server config (interactive optional)
config client Derive client config from server config
user add <name> --role administrator Add a GUI admin
frontend -v Start server frontend (client comms + GUI)
gui All-in-one local lab instance
client -v Run as an endpoint agent
service install Install the agent as a service
query "<VQL>" Run VQL ad hoc
artifacts list List available artifacts
artifacts collect <name> --output <zip> Collect an artifact locally

Validation Criteria

  • Server config generated and GUI admin created
  • Frontend running and GUI reachable over TLS
  • Client config generated and at least one agent enrolled
  • VQL query validated locally with query
  • Built-in artifact collected successfully
  • Fleet-wide hunt launched and results aggregated
  • Custom VQL artifact authored and tested
  • Offline collector produced for unmanaged hosts where needed
Source materials

References and resources

Everything below is rendered for inspection. Script files are read-only and never run.

References 2

api-reference.md2.5 KB

Velociraptor Command and VQL Reference

The same velociraptor binary is server, client, and CLI. Behavior depends on the subcommand and --config.

Core subcommands

Command Description
velociraptor config generate Print a default server config to stdout
velociraptor config generate -i Interactive config wizard
velociraptor --config server.config.yaml config client Derive client config
velociraptor --config server.config.yaml user add <name> --role administrator Create GUI admin
velociraptor --config server.config.yaml frontend -v Start server frontend + GUI
velociraptor gui All-in-one local lab (server + frontend + local client)
velociraptor --config client.config.yaml client -v Run as agent
velociraptor --config client.config.yaml service install Install agent service (Windows)
velociraptor query "<VQL>" Run an ad-hoc VQL query
velociraptor artifacts list List artifacts
velociraptor artifacts collect <Name> --output results.zip Collect an artifact locally
velociraptor artifacts show <Name> Show an artifact definition

Useful global flags

Flag Purpose
--config <file> Path to config YAML
-v / --verbose Verbose logging
-q Alias usage with query
--format json Output query results as JSON

Common VQL plugins (data sources)

Plugin Returns
pslist() Running processes (Pid, Name, CommandLine, ...)
glob(globs=...) Files matching glob patterns
parse_evtx(filename=...) Windows event log records
registry(...) / read_reg_key() Registry keys/values
netstat() Network connections
wmi(query=...) WMI query results
info() Host/system information
execve(argv=...) Run an external command
artifact_definitions() Enumerate loaded artifacts
hunt(description=..., artifacts=...) Create a server-side hunt

VQL query shape

SELECT <columns>
FROM <plugin>(<args>)
WHERE <condition>          -- supports =~ for regex, AND/OR
ORDER BY <column>
LIMIT <n>

Custom artifact YAML structure

name: Custom.Category.Name
description: What it does.
parameters:
  - name: param1
    default: value
sources:
  - query: |
      SELECT * FROM plugin() WHERE col =~ param1

Default ports

Service Port
Frontend (client comms) 8000
Admin GUI 8889
standards.md1.0 KB

Standards and Framework Mapping — Fleet Hunting with Velociraptor

NIST Cybersecurity Framework 2.0

ID Name Rationale
DE.CM-01 Networks and network services are monitored to find potentially adverse events Velociraptor provides continuous endpoint visibility and on-demand fleet hunts that surface adverse events (anomalous execution, persistence, lateral movement) across monitored assets.

MITRE ATT&CK

ID Name Rationale
T1059 Command and Scripting Interpreter VQL hunts commonly target abuse of interpreters (PowerShell, cmd, WScript) — a frequent adversary technique that Velociraptor detects fleet-wide.

Supporting References

Scripts 1

agent.py5.3 KB
Display-only source. This catalog never executes bundled scripts.
#!/usr/bin/env python3
"""
Velociraptor fleet-hunting helper.

Wraps the velociraptor binary to: run ad-hoc VQL queries, list/collect
artifacts, validate custom artifact YAML, and scaffold a custom hunt artifact.
All commands are real velociraptor subcommands.

Reference: https://docs.velociraptor.app/
"""
import argparse
import json
import shutil
import subprocess
import sys
from pathlib import Path

try:
    import yaml  # PyYAML, used only for --validate
except ImportError:
    yaml = None


def find_binary(explicit):
    """Locate the velociraptor binary."""
    if explicit:
        return explicit
    for name in ("velociraptor", "velociraptor.exe"):
        path = shutil.which(name)
        if path:
            return path
    return None


def run_query(binary, vql, fmt="json", dry_run=False):
    """Run a VQL query via `velociraptor query`."""
    cmd = [binary, "query", vql]
    if fmt:
        cmd += ["--format", fmt]
    return _exec(cmd, dry_run, capture=True)


def list_artifacts(binary, dry_run=False):
    return _exec([binary, "artifacts", "list"], dry_run, capture=True)


def collect_artifact(binary, name, output, dry_run=False):
    cmd = [binary, "artifacts", "collect", name, "--output", output]
    return _exec(cmd, dry_run, capture=False)


def validate_artifact(path):
    """Validate a custom artifact YAML has the required structure."""
    if yaml is None:
        print("[!] PyYAML not installed; run: pip install pyyaml", file=sys.stderr)
        return 2
    try:
        doc = yaml.safe_load(Path(path).read_text(encoding="utf-8"))
    except Exception as e:
        print(f"[!] YAML parse error: {e}", file=sys.stderr)
        return 1
    errors = []
    if not isinstance(doc, dict):
        errors.append("top-level document must be a mapping")
        doc = {}
    if not doc.get("name"):
        errors.append("missing required 'name'")
    sources = doc.get("sources")
    if not sources or not isinstance(sources, list):
        errors.append("missing 'sources' list")
    else:
        for i, s in enumerate(sources):
            if not isinstance(s, dict) or "query" not in s:
                errors.append(f"sources[{i}] missing 'query'")
    if errors:
        for e in errors:
            print(f"[!] {e}", file=sys.stderr)
        return 1
    print(f"[+] Artifact '{doc['name']}' is structurally valid "
          f"({len(sources)} source(s)).")
    return 0


def scaffold(name, regex):
    """Emit a custom hunt artifact YAML to stdout."""
    doc = {
        "name": name,
        "description": "Auto-generated fleet hunt artifact.",
        "parameters": [{"name": "regex", "default": regex}],
        "sources": [{
            "query": (
                "SELECT Pid, Name, CommandLine, "
                "timestamp(epoch=now()) AS Collected\n"
                "FROM pslist()\n"
                "WHERE Name =~ \"powershell\" AND CommandLine =~ regex\n"
            )
        }],
    }
    if yaml:
        print(yaml.safe_dump(doc, sort_keys=False))
    else:
        print(json.dumps(doc, indent=2))
    return 0


def _exec(cmd, dry_run, capture):
    print("[*] " + " ".join(cmd), file=sys.stderr)
    if dry_run:
        return 0
    try:
        if capture:
            proc = subprocess.run(cmd, check=False, text=True,
                                  capture_output=True)
            if proc.stdout:
                print(proc.stdout)
            if proc.returncode != 0 and proc.stderr:
                print(proc.stderr, file=sys.stderr)
            return proc.returncode
        return subprocess.run(cmd, check=False).returncode
    except FileNotFoundError:
        print(f"[!] binary not found: {cmd[0]}", file=sys.stderr)
        return 127


def main():
    p = argparse.ArgumentParser(description="Velociraptor fleet-hunting helper")
    p.add_argument("--binary", help="Path to velociraptor binary")
    p.add_argument("--dry-run", action="store_true")
    sub = p.add_subparsers(dest="cmd", required=True)

    q = sub.add_parser("query", help="Run a VQL query")
    q.add_argument("vql")
    q.add_argument("--format", default="json")

    sub.add_parser("artifacts", help="List artifacts")

    c = sub.add_parser("collect", help="Collect an artifact")
    c.add_argument("name")
    c.add_argument("--output", default="results.zip")

    v = sub.add_parser("validate", help="Validate a custom artifact YAML")
    v.add_argument("path")

    s = sub.add_parser("scaffold", help="Emit a custom hunt artifact")
    s.add_argument("--name", default="Custom.Hunt.SuspiciousPowershell")
    s.add_argument("--regex",
                   default="(?i)(-enc|frombase64string|downloadstring|-w hidden|iex)")

    args = p.parse_args()

    if args.cmd == "validate":
        return validate_artifact(args.path)
    if args.cmd == "scaffold":
        return scaffold(args.name, args.regex)

    binary = find_binary(args.binary)
    if not binary:
        print("[!] velociraptor binary not found (use --binary)", file=sys.stderr)
        return 127

    if args.cmd == "query":
        return run_query(binary, args.vql, args.format, args.dry_run)
    if args.cmd == "artifacts":
        return list_artifacts(binary, args.dry_run)
    if args.cmd == "collect":
        return collect_artifact(binary, args.name, args.output, args.dry_run)
    return 2


if __name__ == "__main__":
    sys.exit(main())
Keep exploring