Velociraptor

Velociraptor DFIR and hunting platform. VQL queries, built-in artifacts, hunts, collections, server/client deployment, and custom artifact creation.

#Overview

#What is Velociraptor

Velociraptor is an open-source DFIR and endpoint visibility tool developed by Rapid7. It uses the Velociraptor Query Language (VQL) to query endpoint state in real time - processes, files, registry, network, event logs, and more.

  • Purpose: Incident response, threat hunting, forensic collection, continuous monitoring
  • Core idea: Write a VQL query, push it to thousands of endpoints, get structured results back in seconds
  • Runs on: Windows, Linux, macOS
  • License: AGPL v3

#Architecture

┌──────────────────────────────────────┐
│          Velociraptor Server          │
│  - Web UI (port 8889)                │
│  - gRPC frontend (port 8000)         │
│  - Datastore (file-based / EFS)      │
│  - Hunt dispatcher                   │
│  - Artifact repository               │
└──────────────┬───────────────────────┘
               │  gRPC + TLS (mutual auth)
    ┌──────────┼──────────┐
    ▼          ▼          ▼
 ┌──────┐  ┌──────┐  ┌──────┐
 │Client│  │Client│  │Client│
 │(agent)│  │(agent)│  │(agent)│
 └──────┘  └──────┘  └──────┘
  • Server: Stores configs, artifacts, hunt results. Hosts the web GUI and API.
  • Client: Lightweight agent (~15 MB). Polls server for work. Executes VQL queries locally and streams results back.
  • Communication: gRPC with mutual TLS. Clients authenticate with embedded certificates.

#Server Installation

The single binary acts as both server and client. Generate a server config, then deploy.

# Download latest release
wget https://github.com/Velocidex/velociraptor/releases/latest/download/velociraptor-v0.73.3-linux-amd64
chmod +x velociraptor-v0.73.3-linux-amd64
mv velociraptor-v0.73.3-linux-amd64 /usr/local/bin/velociraptor

# Interactive config generation (creates server.config.yaml + client.config.yaml)
velociraptor config generate -i

# Start server as a service
velociraptor --config server.config.yaml frontend -v

# Or install as a systemd service
velociraptor --config server.config.yaml service install --user velociraptor
systemctl enable --now velociraptor_server

#Client Deployment

The client config is generated during server setup. It contains the server URL and TLS certs. Package and deploy it to endpoints.

# Repack the binary with embedded client config
velociraptor --config server.config.yaml config repack \
  --exe velociraptor-v0.73.3-linux-amd64 \
  client.config.yaml velociraptor-client-repacked

# Deploy on Windows (as a service)
velociraptor-client-repacked.exe service install

# Deploy on Linux
sudo cp velociraptor-client-repacked /usr/local/bin/velociraptor-client
velociraptor-client --config client.config.yaml service install
systemctl enable --now velociraptor_client

# Generate MSI installer for mass deployment
velociraptor --config server.config.yaml config repack \
  --msi velociraptor-v0.73.3-amd64.msi \
  client.config.yaml velociraptor-client.msi

# Verify client connectivity (server-side)
velociraptor --config server.config.yaml query \
  "SELECT * FROM clients() LIMIT 5"

#Adding Users

# Add a GUI admin user
velociraptor --config server.config.yaml \
  user add admin --role administrator

# Roles: administrator, reader, analyst, investigator, api
velociraptor --config server.config.yaml \
  user add analyst1 --role analyst

#VQL Essentials

#VQL Syntax Basics

VQL (Velociraptor Query Language) looks like SQL but queries live endpoint state. Each plugin acts as a virtual "table" that pulls data from the system in real time - processes, files, network, registry, etc.

-- Basic structure
SELECT Column1, Column2
FROM plugin(arg1=value1, arg2=value2)
WHERE condition
ORDER BY Column1
LIMIT 100

-- Every VQL query has a FROM clause with a plugin
-- Plugins generate rows, like SQL tables but dynamic

-- Example: list processes
SELECT Pid, Name, Exe, CommandLine
FROM pslist()

-- Filter with WHERE
SELECT Pid, Name, Exe
FROM pslist()
WHERE Name =~ "powershell|cmd"

-- Subqueries (foreach)
SELECT * FROM foreach(
  row={SELECT Pid, Name FROM pslist()},
  query={SELECT Pid, Name, FamilyName
         FROM netstat() WHERE Pid = Pid}
)

#Key Operators and Functions

Operator Purpose Example
=~ Regex match Name =~ "svc.*host"
= Exact match Pid = 4
<> Not equal Status <> "Running"
IN Membership Name IN ("cmd.exe", "powershell.exe")
AND / OR Logic Pid > 100 AND Name =~ "svc"
NOT Negation NOT Name =~ "system"
format() String formatting format(format="%s:%d", args=[IP, Port])
timestamp() Parse timestamps timestamp(epoch=CreateTime)
basename() Filename from path basename(path=Exe)
dirname() Directory from path dirname(path=Exe)
hash() File hash hash(path=Exe, hashselect="SHA256")
upload() Upload file to server upload(file=Exe)
if() Conditional if(condition=Pid=0, then="Kernel", else=Name)
count() Aggregate count SELECT Name, count() FROM pslist() GROUP BY Name
len() Length len(list=CommandLine)

#Core VQL Plugins

Plugins are the data sources in VQL. Each one queries a specific aspect of the endpoint.

-- Process listing
SELECT * FROM pslist()

-- Network connections
SELECT * FROM netstat()

-- File search (glob patterns)
SELECT * FROM glob(globs="C:/Users/*/Downloads/*.exe")

-- Registry key reading
SELECT * FROM read_reg_key(globs="HKLM/SOFTWARE/Microsoft/Windows/CurrentVersion/Run/*")

-- WMI queries
SELECT * FROM wmi(query="SELECT * FROM Win32_Service", namespace="ROOT/CIMV2")

-- Execute command and capture output
SELECT * FROM execve(argv=["cmd.exe", "/c", "ipconfig", "/all"])

-- Parse PE headers
SELECT * FROM parse_pe(file="C:/Windows/System32/cmd.exe")

-- Read file content
SELECT * FROM read_file(filenames="C:/Windows/System32/drivers/etc/hosts")

-- Parse event logs
SELECT * FROM parse_evtx(filename="C:/Windows/System32/winevt/Logs/Security.evtx")

-- Yara scanning
SELECT * FROM yara(rules=MyYaraRule, files="C:/Users/*/Downloads/*.exe")

#LET Bindings and Variables

VQL supports variable declarations with LET - useful for reusing subqueries, defining constants, and building complex pipelines.

-- Define a constant
LET suspicious_names = ("mimikatz", "rubeus", "seatbelt", "sharphound")

-- Store a subquery for reuse
LET running_procs = SELECT Pid, Name, Exe, CommandLine
  FROM pslist()
  WHERE Exe

-- Use it in another query
SELECT * FROM running_procs
WHERE basename(path=Exe) IN suspicious_names

-- Parameterized lambda (lazy evaluation)
LET hash_file(path) = SELECT
  hash(path=path, hashselect="SHA256") AS SHA256
  FROM scope()

-- Chain queries
LET executables = SELECT FullPath
  FROM glob(globs="C:/Temp/**/*.exe")

SELECT FullPath,
       hash(path=FullPath, hashselect="SHA256") AS SHA256
FROM executables

#Process Hunting

#List All Processes with Details

The pslist() plugin retrieves all running processes with metadata. This is the starting point for most investigations - get a process listing, then drill down into suspicious entries.

-- Full process listing
SELECT Pid, Ppid, Name, Exe,
       CommandLine, Username,
       CreateTime, TokenIsElevated
FROM pslist()

-- With hash of the binary
SELECT Pid, Name, Exe, CommandLine,
       hash(path=Exe, hashselect="SHA256") AS SHA256
FROM pslist()
WHERE Exe

-- Process with loaded DLLs (Windows)
SELECT * FROM modules(pid=1234)

-- Process memory map
SELECT * FROM vad(pid=1234)

#Find Processes by Name, Path, or Cmdline

Search for specific processes matching patterns. Useful when you know the tool name, a suspicious path, or a command-line argument associated with malware or attacker tools.

-- Find by name (regex)
SELECT Pid, Name, Exe, CommandLine
FROM pslist()
WHERE Name =~ "(?i)powershell|pwsh|cmd"

-- Find by suspicious paths (not in expected locations)
SELECT Pid, Name, Exe, CommandLine
FROM pslist()
WHERE Exe AND NOT Exe =~ "(?i)^C:\\\\(Windows|Program Files)"

-- Find by command-line keywords
SELECT Pid, Name, CommandLine
FROM pslist()
WHERE CommandLine =~ "(?i)(encoded|bypass|hidden|invoke-|iex|downloadstring)"

-- Unsigned binaries running
SELECT Pid, Name, Exe,
       authenticode(filename=Exe).Trusted AS Signed
FROM pslist()
WHERE Exe
  AND NOT authenticode(filename=Exe).Trusted = "trusted"

#Process Tree Reconstruction

Reconstruct parent-child relationships to understand how a process was spawned. Critical for identifying LOLBin abuse, lateral movement artifacts, and initial access vectors.

-- Build process tree
SELECT Pid, Ppid, Name, Exe, CommandLine,
       {SELECT Name FROM pslist() WHERE Pid = Ppid} AS ParentName
FROM pslist()

-- Children of a specific process
SELECT Pid, Name, Exe, CommandLine
FROM pslist()
WHERE Ppid = 1234

-- Full ancestry chain (recursive)
LET get_parent(pid) = SELECT Pid, Ppid, Name, Exe
  FROM pslist() WHERE Pid = pid

SELECT * FROM foreach(
  row={SELECT Pid, Ppid, Name FROM pslist() WHERE Pid = 5678},
  query={SELECT * FROM chain(
    a={SELECT * FROM get_parent(pid=Ppid)},
    b={SELECT * FROM get_parent(pid=Ppid)},
    c={SELECT * FROM get_parent(pid=Ppid)}
  )}
)

#Detect Suspicious Parent-Child Relationships

Flag process relationships that deviate from normal Windows behavior. For example, svchost.exe should only be spawned by services.exe, and cmd.exe from explorer.exe via a user-initiated action.

-- Suspicious parent-child pairs
LET suspicious_combos = SELECT * FROM parse_csv(accessor="data", filename='''
ParentPattern,ChildPattern
winword|excel|powerpoint,cmd\.exe|powershell|wscript|cscript|mshta
svchost,cmd\.exe|powershell
wmiprvse,cmd\.exe|powershell
services\.exe,cmd\.exe|powershell
''')

LET procs = SELECT Pid, Ppid, Name, Exe, CommandLine,
  {SELECT Name FROM pslist() WHERE Pid = Ppid} AS ParentName
FROM pslist()

SELECT Pid, Name, ParentName, CommandLine
FROM procs
WHERE ParentName AND
  filter(list=suspicious_combos,
    condition= ParentName[0] =~ ParentPattern
      AND Name =~ ChildPattern)

-- svchost.exe not spawned by services.exe
SELECT Pid, Ppid, Name, Exe, CommandLine,
       {SELECT Name FROM pslist() WHERE Pid = Ppid} AS ParentName
FROM pslist()
WHERE Name =~ "svchost"
  AND NOT {SELECT Name FROM pslist() WHERE Pid = Ppid}[0] = "services.exe"

#Process Memory Analysis

Examine process memory for injected code, suspicious memory regions, or strings. Use VAD (Virtual Address Descriptor) analysis to find executable memory regions that aren't backed by files on disk - a common indicator of process injection.

-- Find RWX (read-write-execute) memory regions - injection indicator
SELECT Pid, Name, Address, Size, Protection, FileName
FROM vad(pid=1234)
WHERE Protection =~ "xrw"

-- Scan process memory with Yara
LET yara_rule = '''
rule Mimikatz {
  strings:
    $s1 = "mimikatz" ascii wide
    $s2 = "sekurlsa" ascii wide
  condition:
    any of them
}
'''

SELECT * FROM proc_yara(rules=yara_rule, pid=1234)

-- Dump suspicious process memory
SELECT upload(file=format(format="/proc/%d/mem", args=[Pid]),
              accessor="sparse",
              name=format(format="%s_%d.dmp", args=[Name, Pid]))
FROM pslist()
WHERE Pid = 1234

#Network Analysis

#Active Connections (Enriched)

The netstat() plugin shows active network connections enriched with process info. Use this to identify beaconing behavior, lateral movement, and data exfiltration channels.

-- All connections with process info
SELECT Pid, Name, FamilyName AS Proto,
       Laddr.IP AS LocalIP, Laddr.Port AS LocalPort,
       Raddr.IP AS RemoteIP, Raddr.Port AS RemotePort,
       Status, Timestamp
FROM netstat()

-- Only established outbound connections
SELECT Pid, Name,
       Raddr.IP AS RemoteIP, Raddr.Port AS RemotePort
FROM netstat()
WHERE Status = "ESTABLISHED"
  AND NOT Raddr.IP =~ "^(127\.|10\.|172\.(1[6-9]|2[0-9]|3[01])\.|192\.168\.)"

-- Top talkers (most connections per process)
SELECT Name, Pid, count() AS ConnCount
FROM netstat()
WHERE Status = "ESTABLISHED"
GROUP BY Name, Pid
ORDER BY ConnCount DESC
LIMIT 20

#DNS Cache Queries

Query the local DNS resolver cache to identify recently resolved domains. Useful for detecting C2 domains, DGA patterns, and data exfiltration over DNS.

-- Windows DNS cache
SELECT * FROM dns_cache()

-- Look for suspicious TLDs or long domain names (DGA detection)
SELECT Name, Record, TTL
FROM dns_cache()
WHERE Name =~ "\.(xyz|top|buzz|tk|ml|ga|cf)$"
   OR len(list=split(string=Name, sep=".")[0]) > 20

-- Search for specific domains
SELECT Name, Record
FROM dns_cache()
WHERE Name =~ "(?i)(pastebin|ngrok|duckdns|no-ip|dyndns)"

#Listening Ports

Identify all services listening for incoming connections. Compare against known baselines to find backdoors, reverse shells, or unauthorized services.

-- All listening ports
SELECT Pid, Name,
       Laddr.IP AS ListenIP,
       Laddr.Port AS ListenPort,
       FamilyName AS Proto
FROM netstat()
WHERE Status = "LISTEN"
ORDER BY ListenPort

-- Listening on non-standard ports (not well-known services)
SELECT Pid, Name, Laddr.Port AS Port, Exe
FROM netstat()
WHERE Status = "LISTEN"
  AND NOT Laddr.Port IN (22, 53, 80, 135, 139, 443, 445, 3389, 5985, 5986)
ORDER BY Port

-- High ports with no recognizable service
SELECT Pid, Name, Exe, CommandLine,
       Laddr.Port AS Port
FROM netstat()
WHERE Status = "LISTEN"
  AND Laddr.Port > 49000

#Connection to Known-Bad IPs

Cross-reference active connections against a threat intel list. Replace the IP list with your own IOC feed or a local file of indicators.

-- Check against a list of IOC IPs
LET bad_ips = ("203.0.113.50", "198.51.100.23", "192.0.2.100")

SELECT Pid, Name, Exe,
       Raddr.IP AS RemoteIP,
       Raddr.Port AS RemotePort
FROM netstat()
WHERE Raddr.IP IN bad_ips

-- Check against IOC file (one IP per line)
LET ioc_ips = SELECT Content
  FROM read_file(filenames="C:/iocs/bad_ips.txt")

SELECT Pid, Name, Raddr.IP AS RemoteIP
FROM netstat()
WHERE Raddr.IP IN ioc_ips.Content

-- GeoIP-based anomaly detection (if MaxMind DB available)
SELECT Pid, Name, Raddr.IP AS RemoteIP, Raddr.Port AS Port,
       geoip(ip=Raddr.IP) AS Geo
FROM netstat()
WHERE Status = "ESTABLISHED"
  AND geoip(ip=Raddr.IP).Country.ISOCode
    IN ("RU", "CN", "KP", "IR")

#Network Timeline

Build a timeline of network activity by correlating event logs and connection data. Helpful for establishing when C2 communication started or when lateral movement occurred.

-- Firewall log events (Windows)
SELECT timestamp(epoch=TimeCreated) AS Time,
       EventData.SourceAddress AS SrcIP,
       EventData.SourcePort AS SrcPort,
       EventData.DestAddress AS DstIP,
       EventData.DestPort AS DstPort,
       EventData.Application AS App,
       EventData.Direction AS Direction
FROM parse_evtx(
  filename="C:/Windows/System32/winevt/Logs/Microsoft-Windows-Windows Firewall With Advanced Security%4Firewall.evtx")
ORDER BY Time DESC
LIMIT 500

-- Sysmon network connections (Event ID 3)
SELECT timestamp(epoch=TimeCreated) AS Time,
       EventData.Image AS Process,
       EventData.DestinationIp AS DstIP,
       EventData.DestinationPort AS DstPort,
       EventData.User AS User
FROM parse_evtx(
  filename="C:/Windows/System32/winevt/Logs/Microsoft-Windows-Sysmon%4Operational.evtx")
WHERE System.EventID.Value = 3
ORDER BY Time DESC

#File System

#File Search by Name, Hash, or Time

Use glob() to recursively search the file system. This is one of the most frequently used operations - finding malware droppers, tools, or modified system files.

-- Search by filename pattern
SELECT FullPath, Size, Mtime, Atime
FROM glob(globs="C:/Users/**/mimikatz*")

-- Search by extension in temp directories
SELECT FullPath, Size, Mtime
FROM glob(globs="C:/{Temp,Users/*/AppData/Local/Temp}/**/*.{exe,dll,ps1,bat,vbs}")

-- Find files matching a known-bad SHA256
LET target_hash = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"

SELECT FullPath, Size, Mtime,
       hash(path=FullPath, hashselect="SHA256") AS SHA256
FROM glob(globs="C:/Users/**/*.exe")
WHERE hash(path=FullPath, hashselect="SHA256").SHA256 = target_hash

-- Files modified in the last 24 hours
SELECT FullPath, Size, Mtime, Atime, Ctime
FROM glob(globs="C:/Windows/System32/**")
WHERE Mtime > now() - 86400
ORDER BY Mtime DESC

#Recent File Modifications

Identify recently created or modified files that may indicate attacker activity. Focus on sensitive directories and executable file types.

-- Recently modified executables across common attack paths
SELECT FullPath, Size,
       timestamp(epoch=Mtime) AS Modified,
       timestamp(epoch=Ctime) AS Created,
       hash(path=FullPath, hashselect="SHA256") AS SHA256
FROM glob(globs=[
  "C:/Windows/Temp/**/*.{exe,dll,ps1,bat,vbs}",
  "C:/Users/*/AppData/**/*.{exe,dll,ps1,bat,vbs}",
  "C:/ProgramData/**/*.{exe,dll,ps1,bat,vbs}"
])
WHERE Mtime > now() - 86400 * 7
ORDER BY Mtime DESC

-- New executables in System32 (should rarely change)
SELECT FullPath, Size,
       timestamp(epoch=Ctime) AS Created
FROM glob(globs="C:/Windows/System32/*.{exe,dll}")
WHERE Ctime > now() - 86400 * 7
ORDER BY Ctime DESC

#Alternate Data Streams (ADS)

NTFS Alternate Data Streams can hide data within files. Attackers use ADS to stash payloads or tools that won't appear in normal directory listings.

-- Find files with alternate data streams
SELECT FullPath, Name, Size, Data
FROM glob(globs="C:/Users/**",
          accessor="ntfs")
WHERE Data.type =~ "ADS"

-- Scan for ADS in common attacker directories
SELECT *
FROM Artifact.Windows.NTFS.ADS(
  SearchPath="C:/Users/",
  FileRegex=".")

-- Zone.Identifier streams (Mark of the Web)
SELECT FullPath, Size, Mtime,
       read_file(filename=FullPath + ":Zone.Identifier") AS ZoneInfo
FROM glob(globs="C:/Users/*/Downloads/*")
WHERE IsDir = false

#Prefetch Analysis

Windows Prefetch files record evidence of program execution - even after the binary is deleted. Each .pf file contains the executable name, run count, timestamps, and loaded files.

-- Parse all prefetch files
SELECT Name, Executable, FileSize,
       RunCount, LastRunTimes,
       FilesAccessed
FROM Artifact.Windows.Forensics.Prefetch()
ORDER BY LastRunTimes DESC

-- Search prefetch for specific tool execution
SELECT Executable, RunCount,
       LastRunTimes, FilesAccessed
FROM Artifact.Windows.Forensics.Prefetch()
WHERE Executable =~ "(?i)(mimikatz|psexec|wmic|certutil|bitsadmin)"

-- Recently executed programs (last 7 days)
SELECT Executable, RunCount, LastRunTimes
FROM Artifact.Windows.Forensics.Prefetch()
WHERE LastRunTimes[-1] > now() - 86400 * 7
ORDER BY LastRunTimes DESC

#MFT Parsing

The Master File Table (MFT) is the NTFS index of every file and directory. Parsing the MFT provides a complete file system timeline - including deleted files that haven't been overwritten.

-- Parse MFT for file timeline
SELECT FullPath, FileName, FileSize,
       Created0x10 AS Created,
       LastModified0x10 AS Modified,
       InUse, IsDir
FROM parse_mft(accessor="ntfs", filename="\\\\.\\C:")
WHERE FullPath =~ "Users"
ORDER BY Created DESC
LIMIT 1000

-- Find timestomped files (SI vs FN timestamp mismatch)
SELECT FullPath, FileName,
       Created0x10 AS SI_Created,
       Created0x30 AS FN_Created,
       LastModified0x10 AS SI_Modified,
       LastModified0x30 AS FN_Modified
FROM parse_mft(accessor="ntfs", filename="\\\\.\\C:")
WHERE Created0x10 <> Created0x30
  AND InUse = true
  AND NOT IsDir

-- Deleted files recovery (MFT records not in use)
SELECT FullPath, FileName, FileSize,
       Created0x10, LastModified0x10
FROM parse_mft(accessor="ntfs", filename="\\\\.\\C:")
WHERE InUse = false
  AND FileName =~ "(?i)\.(exe|dll|ps1|bat|zip|rar)$"
ORDER BY LastModified0x10 DESC
LIMIT 500

#File System Timeline Creation

Combine multiple file system data sources into a unified timeline. This helps correlate attacker actions across different evidence types during an investigation.

-- USN Journal timeline (tracks file system changes)
SELECT
  timestamp(epoch=Timestamp) AS Time,
  FullPath,
  Reason,
  FileAttributes
FROM parse_usn(device="\\\\.\\C:")
WHERE Timestamp > now() - 86400 * 3
ORDER BY Timestamp DESC
LIMIT 5000

-- Combined timeline: MFT + Prefetch + Event Logs
-- Use the built-in super timeline artifact
SELECT * FROM Artifact.Windows.Forensics.Timeline()
WHERE Time > "2026-03-28"
ORDER BY Time

#Registry & Persistence

#Run Keys

Run and RunOnce registry keys execute programs at user logon. One of the oldest and most common persistence mechanisms, easy to set and frequently abused by malware.

-- All Run key entries (HKLM + HKCU)
SELECT Key.FullPath, Name, Data.value AS Value
FROM read_reg_key(
  globs=[
    "HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/Run/*",
    "HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/RunOnce/*",
    "HKEY_USERS/*/SOFTWARE/Microsoft/Windows/CurrentVersion/Run/*",
    "HKEY_USERS/*/SOFTWARE/Microsoft/Windows/CurrentVersion/RunOnce/*"
  ])

-- Run keys with unsigned binaries
SELECT Key.FullPath, Name, Data.value AS Value,
       authenticode(filename=expand(path=Data.value)).Trusted AS Signed
FROM read_reg_key(
  globs="HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/Run/*")
WHERE NOT authenticode(filename=expand(path=Data.value)).Trusted = "trusted"

#Services

Windows services run as SYSTEM by default and survive reboots. Attackers create or hijack services for persistent, elevated access.

-- All registered services
SELECT Name, DisplayName, PathName, StartMode,
       State, StartName AS RunAs
FROM wmi(query="SELECT * FROM Win32_Service",
         namespace="ROOT/CIMV2")

-- Services running from unusual paths
SELECT Name, DisplayName, PathName, State
FROM wmi(query="SELECT * FROM Win32_Service",
         namespace="ROOT/CIMV2")
WHERE NOT PathName =~ "(?i)^(C:\\\\Windows|C:\\\\Program Files)"
  AND PathName

-- Recently created services (registry)
SELECT Key.FullPath, Key.Mtime AS Modified,
       {SELECT Data.value FROM read_reg_key(
         globs=Key.FullPath + "/ImagePath")} AS ImagePath
FROM read_reg_key(
  globs="HKEY_LOCAL_MACHINE/SYSTEM/CurrentControlSet/Services/*")
WHERE Key.Mtime > now() - 86400 * 7
ORDER BY Key.Mtime DESC

#Scheduled Tasks

Scheduled tasks are a high-value persistence mechanism. They can run as any user, trigger on events, times, or startup, and are often abused for lateral movement via schtasks /create /s.

-- All scheduled tasks
SELECT * FROM Artifact.Windows.System.TaskScheduler()

-- Tasks executing from suspicious locations
SELECT Name, Path, Command, Arguments, UserId,
       LastRunTime, NextRunTime
FROM Artifact.Windows.System.TaskScheduler()
WHERE NOT Command =~ "(?i)^(C:\\\\Windows|C:\\\\Program Files)"

-- Tasks created recently
SELECT Name, Path, Command, Arguments,
       RegistrationDate
FROM Artifact.Windows.System.TaskScheduler()
WHERE RegistrationDate > now() - 86400 * 7

-- Raw XML parsing of task files
SELECT FullPath,
       parse_xml(file=FullPath) AS TaskXML
FROM glob(globs="C:/Windows/System32/Tasks/**")
WHERE NOT IsDir

#Startup Folders

Startup folders execute anything placed inside them at user logon. Simple but effective - dropping an LNK or script here is a low-effort persistence method.

-- Check all startup folder entries
SELECT FullPath, Name, Size,
       timestamp(epoch=Mtime) AS Modified
FROM glob(globs=[
  "C:/ProgramData/Microsoft/Windows/Start Menu/Programs/Startup/*",
  "C:/Users/*/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Startup/*"
])
WHERE NOT IsDir

-- Analyze LNK files in startup
SELECT FullPath,
       parse_lnk(filename=FullPath) AS LnkTarget
FROM glob(globs="C:/Users/*/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Startup/*.lnk")

#COM Object Hijacking

COM hijacking replaces legitimate COM class references with attacker-controlled DLLs. Stealthy because it leverages normal Windows functionality. User-level CLSID entries override machine-level ones.

-- HKCU COM overrides (user-level hijacks)
SELECT Key.FullPath,
       Name, Data.value AS DLLPath
FROM read_reg_key(
  globs="HKEY_USERS/*/SOFTWARE/Classes/CLSID/*/InProcServer32/*")
WHERE Data.value
  AND NOT Data.value =~ "(?i)^C:\\\\Windows"

-- Compare HKCU vs HKLM (hijack detection)
LET hklm_com = SELECT Key.FullPath,
  regex_replace(source=Key.FullPath,
    re="HKEY_LOCAL_MACHINE/SOFTWARE/Classes/",
    replace="") AS CLSID,
  Data.value AS MachineValue
FROM read_reg_key(
  globs="HKEY_LOCAL_MACHINE/SOFTWARE/Classes/CLSID/*/InProcServer32/*")

LET hkcu_com = SELECT Key.FullPath,
  regex_replace(source=Key.FullPath,
    re="HKEY_USERS/.+?/SOFTWARE/Classes/",
    replace="") AS CLSID,
  Data.value AS UserValue
FROM read_reg_key(
  globs="HKEY_USERS/*/SOFTWARE/Classes/CLSID/*/InProcServer32/*")

SELECT * FROM hkcu_com
WHERE CLSID IN hklm_com.CLSID

#WMI Event Subscriptions

WMI event subscriptions are a fileless persistence technique. An event filter triggers on a condition (e.g., system startup), and a consumer executes a payload. Hard to detect without specific tooling.

-- WMI Event Consumers (the action)
SELECT * FROM wmi(
  query="SELECT * FROM __EventConsumer",
  namespace="ROOT/Subscription")

-- WMI Event Filters (the trigger)
SELECT * FROM wmi(
  query="SELECT * FROM __EventFilter",
  namespace="ROOT/Subscription")

-- WMI Filter-to-Consumer Bindings (links trigger to action)
SELECT * FROM wmi(
  query="SELECT * FROM __FilterToConsumerBinding",
  namespace="ROOT/Subscription")

-- Full WMI persistence chain in one query
SELECT
  {SELECT Name, Query FROM wmi(
    query="SELECT * FROM __EventFilter",
    namespace="ROOT/Subscription")} AS Filters,
  {SELECT Name, CommandLineTemplate, ScriptText
   FROM wmi(
    query="SELECT * FROM __EventConsumer",
    namespace="ROOT/Subscription")} AS Consumers,
  {SELECT * FROM wmi(
    query="SELECT * FROM __FilterToConsumerBinding",
    namespace="ROOT/Subscription")} AS Bindings
FROM scope()

#Built-in Artifacts

#Windows.System.*

System-level artifacts for process, service, and driver enumeration.

Artifact Description
Windows.System.Pslist Running processes with hashes
Windows.System.Services All Windows services
Windows.System.TaskScheduler Scheduled tasks
Windows.System.Drivers Loaded kernel drivers
Windows.System.DLLs Loaded DLLs per process
Windows.System.CmdShell Run arbitrary shell cmd
Windows.System.PowerShell Run PowerShell script
Windows.System.Users Local user accounts
Windows.System.Interfaces Network interfaces
Windows.System.Hotfixes Installed KB patches

#Windows.Network.*

Network configuration and connection data.

Artifact Description
Windows.Network.Netstat Active TCP/UDP connections
Windows.Network.NetstatEnriched Connections + process + geo
Windows.Network.InterfaceAddresses NIC addresses and MACs
Windows.Network.ArpCache ARP table entries
Windows.Network.DNSCache DNS resolver cache
Windows.Network.ListeningPorts Open listening ports

#Windows.Forensics.*

Disk and artifact forensics for deep-dive investigations.

Artifact Description
Windows.Forensics.Prefetch Program execution evidence
Windows.Forensics.USN USN journal (file changes)
Windows.Forensics.MFT Master File Table parsing
Windows.Forensics.Timeline Super timeline creation
Windows.Forensics.SRUM System Resource Usage Monitor
Windows.Forensics.SAM SAM database (local accounts)
Windows.Forensics.Lnk LNK (shortcut) file parser
Windows.Forensics.RecycleBin Deleted file recovery
Windows.Forensics.Shellbags Folder access history
Windows.Forensics.Amcache Application compatibility cache

#Windows.Registry.*

Registry-based persistence and configuration.

Artifact Description
Windows.Registry.Run Run/RunOnce keys
Windows.Registry.Services Service registry entries
Windows.Registry.Autoruns All autostart locations
Windows.Registry.NTUser Per-user NTUser.dat parse
Windows.Registry.AppCompatCache Shimcache entries
Windows.Registry.RecentDocs Recently opened documents

#Windows.EventLogs.*

Event log parsing and analysis.

Artifact Description
Windows.EventLogs.Evtx Generic EVTX parser
Windows.EventLogs.PowerShellScriptBlock PS script block logging
Windows.EventLogs.RDPAuth RDP authentication events
Windows.EventLogs.Logon Logon/logoff events (4624/4634)
Windows.EventLogs.ServiceCreation New service installs (7045)
Windows.EventLogs.ProcessCreation Process creation (4688)
Windows.EventLogs.ScheduledTasks Task creation/modification
Windows.EventLogs.Kerbroasting Kerberos ticket requests

#Windows.Detection.*

Detection-focused artifacts for known attack patterns.

Artifact Description
Windows.Detection.Yara.Process Yara scan process memory
Windows.Detection.Yara.NTFS Yara scan disk files
Windows.Detection.ProcessInjection Detect injected code
Windows.Detection.BinaryRename Renamed system binaries
Windows.Detection.Amsi AMSI bypass detection
Windows.Detection.ForwardedImports Import forwarding abuse
Windows.Detection.MaliciousPowershell Suspicious PS patterns

#Linux Artifacts

Artifact Description
Linux.Sys.Pslist Process listing
Linux.Sys.Maps Process memory maps
Linux.Sys.Crontab Cron jobs (all users)
Linux.Sys.Services Systemd services
Linux.Sys.Users User accounts
Linux.Network.Netstat Network connections
Linux.Forensics.Journal Systemd journal logs
Linux.Detection.Yara.Process Yara scan processes
Linux.Sys.BashHistory Bash history files
Linux.Sys.SSHAuthorizedKeys SSH authorized keys
Linux.Sys.LastLog Last login records

#Server Artifacts

Server-side artifacts run on the Velociraptor server itself, not on clients.

Artifact Description
Server.Hunts.List List all hunts
Server.Hunts.Results Get hunt results
Server.Clients.List List all clients
Server.Internal.Ping Check client connectivity
Server.Utils.CreateCollector Build offline collector
Server.Import.ArtifactExchange Import community artifacts
Server.Monitor.Health Server health status
Server.Enrichment.Virustotal VT hash lookup

#Using Built-in Artifacts in VQL

You can call any artifact directly from VQL using the Artifact. prefix. This lets you combine multiple artifacts into a single query.

-- Collect an artifact
SELECT * FROM Artifact.Windows.System.Pslist()

-- Artifact with parameters
SELECT * FROM Artifact.Windows.Detection.Yara.NTFS(
  PathGlob="C:/Users/**/*.exe",
  YaraRule="rule test { strings: $a = \"mimikatz\" condition: $a }")

-- Chain artifacts
LET services = SELECT Name, PathName
  FROM Artifact.Windows.System.Services()
  WHERE StartMode = "Auto"

SELECT Name, PathName,
       hash(path=PathName, hashselect="SHA256") AS SHA256
FROM services
WHERE PathName

#Custom Artifacts

#Artifact YAML Structure

Custom artifacts are defined in YAML. They contain metadata, parameters, sources (VQL queries), and optional preconditions. This is the standard structure every artifact follows.

name: Custom.Windows.Detection.SuspiciousService
description: |
  Detects services running from non-standard paths.
  Flags unsigned binaries and recently created services.

author: Your Name
type: CLIENT  # CLIENT, SERVER, CLIENT_EVENT, SERVER_EVENT

parameters:
  - name: SuspiciousPathRegex
    type: string
    default: "^(?i)C:\\\\(Windows|Program Files)"
    description: Regex for legitimate service paths (negated in query)
  - name: MaxAge
    type: int
    default: 7
    description: Number of days to look back for new services

precondition:
  SELECT OS FROM info() WHERE OS = "windows"

sources:
  - name: SuspiciousServices
    query: |
      SELECT Name, DisplayName, PathName, State,
             StartMode, StartName AS RunAs,
             timestamp(epoch=Key.Mtime) AS RegistryModified,
             authenticode(filename=expand(path=PathName)).Trusted AS Signed
      FROM Artifact.Windows.System.Services()
      WHERE PathName
        AND NOT PathName =~ SuspiciousPathRegex
      ORDER BY RegistryModified DESC

#Complete Custom Artifact Example

This artifact hunts for potential C2 beaconing by analyzing network connection patterns. It looks for processes making repeated connections to the same external IP at regular intervals.

name: Custom.Windows.Detection.C2Beaconing
description: |
  Detects potential C2 beaconing by looking for processes
  with repeated outbound connections to the same remote
  IP on the same port. Filters out private IP ranges.

author: Security Team
type: CLIENT
reference:
  - https://attack.mitre.org/techniques/T1071/

parameters:
  - name: MinConnections
    type: int
    default: 3
    description: Minimum connections to flag
  - name: ExcludeIPs
    type: string
    default: "^(127\\.|10\\.|172\\.(1[6-9]|2|3[01])\\.|192\\.168\\.)"
    description: Regex for IPs to exclude

precondition:
  SELECT OS FROM info() WHERE OS = "windows"

sources:
  - name: BeaconCandidates
    query: |
      LET connections = SELECT
        Pid, Name, Exe,
        Raddr.IP AS RemoteIP,
        Raddr.Port AS RemotePort,
        count() AS ConnCount
      FROM netstat()
      WHERE Status = "ESTABLISHED"
        AND NOT Raddr.IP =~ ExcludeIPs
        AND Raddr.IP
      GROUP BY Pid, Name, RemoteIP, RemotePort

      SELECT Pid, Name, Exe,
             RemoteIP, RemotePort, ConnCount,
             hash(path=Exe, hashselect="SHA256") AS SHA256
      FROM connections
      WHERE ConnCount >= MinConnections
      ORDER BY ConnCount DESC

  - name: ProcessDetails
    query: |
      LET beacon_pids = SELECT Pid
        FROM source(source="BeaconCandidates")

      SELECT Pid, Name, Exe, CommandLine,
             Username, TokenIsElevated,
             {SELECT Name FROM pslist()
              WHERE Pid = Ppid} AS ParentName
      FROM pslist()
      WHERE Pid IN beacon_pids.Pid

#Parameters and Preconditions

Parameters make artifacts configurable at collection time. Preconditions control whether the artifact runs (e.g., only on Windows). These make artifacts portable and reusable.

# Parameter types
parameters:
  - name: SearchGlob
    type: string
    default: "C:/Users/**/*.exe"
    description: File glob pattern to search

  - name: MaxResults
    type: int
    default: 500

  - name: HashFiles
    type: bool
    default: true

  - name: TargetHashes
    type: csv
    default: |
      Hash
      e3b0c44298fc1c149afbf4c8996fb924

  - name: DateAfter
    type: timestamp
    description: Only include files modified after this date

# Preconditions - OS checks
precondition:
  SELECT OS FROM info() WHERE OS = "windows"

# Multi-OS precondition
precondition: |
  SELECT OS FROM info()
  WHERE OS = "windows" OR OS = "linux"

#Artifact Exchange and Packaging

The Velociraptor Artifact Exchange is a community repository of shared artifacts. You can import them into your server or create your own and share them.

# Import artifacts from the exchange (server-side)
velociraptor --config server.config.yaml \
  artifacts import /path/to/artifact.yaml

# List available artifacts
velociraptor --config server.config.yaml \
  artifacts list | grep -i "detection"

# Show artifact definition
velociraptor --config server.config.yaml \
  artifacts show Windows.Detection.Yara.Process
-- Import artifact exchange via VQL (in notebook)
SELECT * FROM Artifact.Server.Import.ArtifactExchange(
  ExchangeURL="https://docs.velociraptor.app/exchange/")

-- List all custom artifacts
SELECT name, description, type
FROM artifact_definitions()
WHERE name =~ "^Custom\\."

-- Pack artifacts into a zip for sharing
SELECT * FROM collect(
  artifacts="Custom.Windows.Detection.C2Beaconing",
  output="/tmp/c2_beacon_artifact.zip")

#Hunts & Collections

#Creating a Hunt

A hunt pushes a VQL artifact to multiple clients simultaneously. Use hunts to sweep your entire fleet for indicators - compromised credentials, malware, persistence, lateral movement, etc.

-- Create a hunt (via VQL in server notebook)
SELECT * FROM hunt(
  description="Hunt for Mimikatz across all Windows endpoints",
  artifacts="Windows.Detection.Yara.NTFS",
  spec=dict(`Windows.Detection.Yara.NTFS`=dict(
    PathGlob="C:/Users/**/*.{exe,dll}",
    YaraRule='rule Mimikatz { strings: $a = "mimikatz" wide ascii condition: $a }'
  )),
  os_condition="windows",
  expires=now() + 86400 * 7
)

-- Hunt with label targeting
SELECT * FROM hunt(
  description="Collect autoruns from Domain Controllers",
  artifacts="Windows.Registry.Autoruns",
  label="Domain Controller",
  os_condition="windows"
)
# CLI hunt creation
velociraptor --config server.config.yaml \
  hunt create \
  --artifacts Windows.System.Pslist \
  --description "Process listing across fleet" \
  --os windows

#Scheduling Collections

Schedule recurring collections to continuously monitor endpoints. Useful for baseline comparisons and detecting changes over time (new services, processes, autoruns).

-- Schedule a client monitoring artifact (event query)
SELECT * FROM collect(
  client_id="C.1234567890abcdef",
  artifacts="Windows.Events.ProcessCreation",
  timeout=0  -- run indefinitely
)

-- Collect from a specific client
SELECT * FROM collect(
  client_id="C.1234567890abcdef",
  artifacts=[
    "Windows.System.Pslist",
    "Windows.Network.NetstatEnriched",
    "Windows.Registry.Autoruns"
  ],
  timeout=600
)

-- Schedule via server event monitoring
SELECT * FROM clock(period=3600)
WHERE log(message="Hourly collection trigger")
  AND collect(
    client_id="C.1234567890abcdef",
    artifacts="Windows.System.Pslist")

#Offline Collector

The offline collector is a standalone binary that runs artifact collections without needing a server connection. Ideal for air-gapped systems, initial triage, or when you can't install an agent.

# Build an offline collector (interactive)
velociraptor --config server.config.yaml \
  collector create \
  --artifacts Windows.System.Pslist,Windows.Registry.Autoruns,Windows.Forensics.Prefetch \
  --output offline_collector.exe \
  --format json

# Build via GUI:
# Server Artifacts -> Server.Utils.CreateCollector
# Select artifacts, configure parameters, download binary
-- Create offline collector via VQL
SELECT * FROM Artifact.Server.Utils.CreateCollector(
  OS="Windows",
  artifacts=[
    "Windows.System.Pslist",
    "Windows.Network.NetstatEnriched",
    "Windows.Registry.Autoruns",
    "Windows.Forensics.Prefetch",
    "Windows.EventLogs.PowerShellScriptBlock"
  ],
  parameters=dict(
    `Windows.EventLogs.PowerShellScriptBlock`=dict(
      DateAfter="2026-03-22"
    )
  ),
  output="/tmp/triage_collector.exe"
)

#Exporting Results

Export hunt and collection results for reporting, SIEM ingestion, or further analysis in external tools. Velociraptor supports JSON, CSV, and direct SIEM forwarding.

-- Export hunt results to CSV
SELECT * FROM hunt_results(
  hunt_id="H.1234567890",
  artifact="Windows.System.Pslist")
WHERE true
EXPORT(filename="/tmp/hunt_results.csv")

-- Export to JSON lines (for SIEM)
SELECT * FROM hunt_results(
  hunt_id="H.1234567890",
  artifact="Windows.Registry.Autoruns")

-- Inspect a specific collection
SELECT * FROM flows(client_id="C.1234567890abcdef")
ORDER BY create_time DESC LIMIT 10

-- Download collection results
SELECT * FROM flow_results(
  client_id="C.1234567890abcdef",
  flow_id="F.1234567890")
# CLI export
velociraptor --config server.config.yaml \
  query "SELECT * FROM hunt_results(hunt_id='H.1234567890', artifact='Windows.System.Pslist')" \
  --format json > results.json

velociraptor --config server.config.yaml \
  query "SELECT * FROM hunt_results(hunt_id='H.1234567890', artifact='Windows.System.Pslist')" \
  --format csv > results.csv

#CLI Usage Reference

The Velociraptor CLI is the primary admin interface for automation, scripting, and headless operation. Every GUI action has a CLI equivalent.

# Run a VQL query against the server
velociraptor --config server.config.yaml query \
  "SELECT * FROM clients() WHERE os_info.system = 'windows' LIMIT 10"

# Collect an artifact from a specific client
velociraptor --config server.config.yaml collect \
  --client C.1234567890abcdef \
  --artifacts Windows.System.Pslist

# List all hunts
velociraptor --config server.config.yaml query \
  "SELECT hunt_id, description, state, stats FROM hunts()"

# List all connected clients
velociraptor --config server.config.yaml query \
  "SELECT client_id, os_info.hostname AS Host, os_info.system AS OS, last_seen_at FROM clients()"

# Run VQL interactively
velociraptor --config server.config.yaml query --interactive

# Generate API config for external tool integration
velociraptor --config server.config.yaml \
  config api_client --name automation_user \
  --role reader,analyst \
  api_client.yaml

# Run a VQL file
velociraptor --config server.config.yaml query \
  --file my_investigation.vql

# Server health check
velociraptor --config server.config.yaml query \
  "SELECT * FROM Artifact.Server.Monitor.Health()"

#Also See

#Cyber Aurelien Guidi