Files
2025-11-29 17:51:02 +08:00

13 KiB

Velociraptor Artifact Development Guide

Guide to creating custom VQL artifacts for specific investigation and threat hunting scenarios.

Table of Contents

Artifact Structure

Velociraptor artifacts are YAML files with a defined structure:

name: Category.Subcategory.ArtifactName
description: |
  Detailed description of what this artifact collects and why.
  Include use cases and expected output.

author: Your Name <email@domain.com>

type: CLIENT  # CLIENT, SERVER, or CLIENT_EVENT

parameters:
  - name: ParameterName
    default: "default_value"
    type: string
    description: Parameter description

precondition: |
  SELECT OS FROM info() WHERE OS = 'windows'

sources:
  - name: SourceName
    query: |
      SELECT * FROM plugin()
      WHERE condition

reports:
  - type: CLIENT
    template: |
      # Report Title
      {{ .Description }}

      {{ range .Rows }}
      - {{ .Column }}
      {{ end }}

Required Fields

  • name: Unique artifact identifier in dot notation
  • description: What the artifact does and when to use it
  • sources: At least one VQL query source

Optional Fields

  • author: Creator information
  • type: Artifact type (CLIENT, SERVER, CLIENT_EVENT)
  • parameters: User-configurable inputs
  • precondition: Check before running (OS, software presence)
  • reports: Output formatting templates
  • references: External documentation links

Parameter Types

String Parameters

parameters:
  - name: SearchPath
    default: "C:/Windows/System32/"
    type: string
    description: Directory path to search

Integer Parameters

parameters:
  - name: DaysBack
    default: 7
    type: int
    description: Number of days to look back

Boolean Parameters

parameters:
  - name: IncludeSystem
    default: Y
    type: bool
    description: Include system files

Regex Parameters

parameters:
  - name: ProcessPattern
    default: "(?i)(powershell|cmd)"
    type: regex
    description: Process name pattern to match

Choice Parameters

parameters:
  - name: LogLevel
    default: "INFO"
    type: choices
    choices:
      - DEBUG
      - INFO
      - WARNING
      - ERROR
    description: Logging verbosity

CSV Parameters

parameters:
  - name: IOCList
    default: |
      evil.com
      malicious.net
    type: csv
    description: List of IOC domains

Source Types

Query Sources

Standard VQL query that collects data:

sources:
  - name: ProcessCollection
    query: |
      SELECT Pid, Name, CommandLine, Username
      FROM pslist()
      WHERE Name =~ ProcessPattern

Event Sources

Continuous monitoring queries for CLIENT_EVENT artifacts:

sources:
  - name: ProcessCreation
    query: |
      SELECT * FROM watch_evtx(
        filename="C:/Windows/System32/winevt/Logs/Security.evtx"
      )
      WHERE System.EventID.Value = 4688

Multiple Sources

Artifacts can have multiple sources for different data collection:

sources:
  - name: Processes
    query: |
      SELECT * FROM pslist()

  - name: NetworkConnections
    query: |
      SELECT * FROM netstat()

  - name: LoadedDLLs
    query: |
      SELECT * FROM modules()

Best Practices

1. Use Preconditions

Prevent artifact execution on incompatible systems:

# Windows-only artifact
precondition: |
  SELECT OS FROM info() WHERE OS = 'windows'

# Requires specific tool
precondition: |
  SELECT * FROM stat(filename="C:/Tools/sysinternals/psexec.exe")

# Version check
precondition: |
  SELECT * FROM info() WHERE OS = 'windows' AND OSVersion =~ '10'

2. Parameterize Paths and Patterns

Make artifacts flexible and reusable:

parameters:
  - name: TargetPath
    default: "C:/Users/**/AppData/**"
    type: string

  - name: FilePattern
    default: "*.exe"
    type: string

sources:
  - query: |
      SELECT * FROM glob(globs=TargetPath + "/" + FilePattern)

3. Use LET for Query Composition

Break complex queries into manageable parts:

sources:
  - query: |
      -- Define reusable subqueries
      LET SuspiciousProcesses = SELECT Pid, Name, CommandLine
      FROM pslist()
      WHERE CommandLine =~ "(?i)(bypass|hidden)"

      LET NetworkConnections = SELECT Pid, Raddr.IP AS RemoteIP
      FROM netstat()
      WHERE Status = "ESTABLISHED"

      -- Join and correlate
      SELECT sp.Name,
             sp.CommandLine,
             nc.RemoteIP
      FROM SuspiciousProcesses sp
      JOIN NetworkConnections nc ON sp.Pid = nc.Pid

4. Add Error Handling

Handle missing data gracefully:

sources:
  - query: |
      SELECT * FROM foreach(
        row={
          SELECT FullPath FROM glob(globs=SearchPath)
        },
        query={
          SELECT FullPath,
                 hash(path=FullPath, accessor="file").SHA256 AS SHA256
          FROM scope()
          WHERE log(message="Processing: " + FullPath)
        },
        workers=5
      )
      WHERE SHA256  -- Filter out hash failures

5. Include Documentation

Add inline comments and comprehensive descriptions:

description: |
  ## Overview
  This artifact hunts for suspicious scheduled tasks.

  ## Use Cases
  - Persistence mechanism detection
  - Lateral movement artifact collection
  - Threat hunting campaigns

  ## Output
  Returns task name, actions, triggers, and creation time.

  ## References
  - MITRE ATT&CK T1053.005 (Scheduled Task/Job)

Common Patterns

Pattern: File Collection with Hashing

name: Custom.Windows.FileCollection
description: Collect files matching patterns with hashes

parameters:
  - name: GlobPatterns
    default: |
      C:/Users/**/AppData/**/*.exe
      C:/Windows/Temp/**/*.dll
    type: csv

sources:
  - query: |
      SELECT FullPath,
             Size,
             timestamp(epoch=Mtime) AS Modified,
             timestamp(epoch=Btime) AS Created,
             hash(path=FullPath, accessor="file") AS Hashes
      FROM foreach(
        row={
          SELECT * FROM parse_csv(filename=GlobPatterns, accessor="data")
        },
        query={
          SELECT * FROM glob(globs=_value)
        }
      )
      WHERE NOT IsDir

Pattern: Event Log Analysis

name: Custom.Windows.EventLogHunt
description: Hunt for specific event IDs with context

parameters:
  - name: LogFile
    default: "C:/Windows/System32/winevt/Logs/Security.evtx"
    type: string

  - name: EventIDs
    default: "4624,4625,4672"
    type: csv

sources:
  - query: |
      LET EventIDList = SELECT parse_string_with_regex(
        string=EventIDs,
        regex="(\\d+)"
      ).g1 AS EventID FROM scope()

      SELECT timestamp(epoch=System.TimeCreated.SystemTime) AS EventTime,
             System.EventID.Value AS EventID,
             System.Computer AS Computer,
             EventData
      FROM parse_evtx(filename=LogFile)
      WHERE str(str=System.EventID.Value) IN EventIDList.EventID
      ORDER BY EventTime DESC

Pattern: Process Tree Analysis

name: Custom.Windows.ProcessTree
description: Build process tree from a starting PID

parameters:
  - name: RootPID
    default: 0
    type: int
    description: Starting process PID (0 for all)

sources:
  - query: |
      LET ProcessList = SELECT Pid, Ppid, Name, CommandLine, Username, CreateTime
      FROM pslist()

      LET RECURSIVE GetChildren(ParentPID) = SELECT *
      FROM ProcessList
      WHERE Ppid = ParentPID

      LET RECURSIVE BuildTree(Level, ParentPID) = SELECT
        Level,
        Pid,
        Ppid,
        Name,
        CommandLine,
        Username,
        CreateTime
      FROM GetChildren(ParentPID=ParentPID)
      UNION ALL
      SELECT * FROM BuildTree(Level=Level+1, ParentPID=Pid)

      SELECT * FROM if(
        condition=RootPID > 0,
        then={
          SELECT * FROM BuildTree(Level=0, ParentPID=RootPID)
        },
        else={
          SELECT 0 AS Level, * FROM ProcessList
        }
      )
      ORDER BY CreateTime

Pattern: Network IOC Matching

name: Custom.Windows.NetworkIOCMatch
description: Match network connections against IOC list

parameters:
  - name: IOCList
    default: |
      IP,Description
      192.0.2.1,C2 Server
      198.51.100.50,Malicious Host
    type: csv

sources:
  - query: |
      LET IOCs = SELECT IP, Description
      FROM parse_csv(filename=IOCList, accessor="data")

      LET Connections = SELECT
        Raddr.IP AS RemoteIP,
        Raddr.Port AS RemotePort,
        Pid,
        process_tracker_get(id=Pid).Name AS ProcessName,
        process_tracker_get(id=Pid).CommandLine AS CommandLine
      FROM netstat()
      WHERE Status = "ESTABLISHED"

      SELECT c.RemoteIP,
             c.RemotePort,
             c.ProcessName,
             c.CommandLine,
             i.Description AS IOCMatch
      FROM Connections c
      JOIN IOCs i ON c.RemoteIP = i.IP

Pattern: Registry Timeline

name: Custom.Windows.RegistryTimeline
description: Timeline registry modifications in specific keys

parameters:
  - name: RegistryPaths
    default: |
      HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/Run/**
      HKEY_CURRENT_USER/SOFTWARE/Microsoft/Windows/CurrentVersion/Run/**
    type: csv

  - name: DaysBack
    default: 7
    type: int

sources:
  - query: |
      LET StartTime = timestamp(epoch=now() - DaysBack * 86400)

      SELECT timestamp(epoch=Key.Mtime) AS Modified,
             Key.FullPath AS RegistryPath,
             ValueName,
             ValueData.value AS Value
      FROM foreach(
        row={
          SELECT * FROM parse_csv(filename=RegistryPaths, accessor="data")
        },
        query={
          SELECT * FROM read_reg_key(globs=_value)
        }
      )
      WHERE Key.Mtime > StartTime
      ORDER BY Modified DESC

Testing Artifacts

1. Local Testing with GUI

# Start Velociraptor in GUI mode
velociraptor gui

# Navigate to: View Artifacts → Add Artifact
# Paste your artifact YAML and click Save
# Run artifact via Collected Artifacts → New Collection

2. Command Line Testing

# Test artifact syntax
velociraptor artifacts show Custom.Artifact.Name

# Run artifact locally
velociraptor artifacts collect Custom.Artifact.Name \
  --args ParameterName=value \
  --format json

# Run with output file
velociraptor artifacts collect Custom.Artifact.Name \
  --output results.json

3. Notebook Testing

Use VQL notebooks for interactive development:

-- Test query components in isolation
SELECT * FROM pslist() WHERE Name =~ "powershell" LIMIT 10

-- Test parameter substitution
LET ProcessPattern = "(?i)(powershell|cmd)"
SELECT * FROM pslist() WHERE Name =~ ProcessPattern

-- Test full artifact query
/* Paste your artifact query here */

4. Validation Checklist

Before deploying artifacts:

  • Artifact name follows convention: Category.Subcategory.Name
  • Description includes use cases and expected output
  • Parameters have sensible defaults
  • Precondition prevents incompatible execution
  • Query tested in notebook mode
  • Error handling for missing data
  • Performance acceptable on test system
  • Output format is useful and parseable
  • Documentation includes MITRE ATT&CK mapping if applicable

Performance Considerations

Limit Scope

# BAD: Scans entire filesystem
SELECT * FROM glob(globs="C:/**/*.exe")

# GOOD: Targeted scope
SELECT * FROM glob(globs=[
  "C:/Users/**/AppData/**/*.exe",
  "C:/Windows/Temp/**/*.exe"
])

Use Workers for Parallel Processing

sources:
  - query: |
      SELECT * FROM foreach(
        row={SELECT * FROM glob(globs=SearchPath)},
        query={
          SELECT FullPath,
                 hash(path=FullPath, accessor="file").SHA256 AS SHA256
          FROM scope()
        },
        workers=10  -- Process 10 files concurrently
      )

Rate Limiting

sources:
  - query: |
      SELECT * FROM foreach(
        row={SELECT * FROM glob(globs="C:/**")},
        query={
          SELECT * FROM scope()
          WHERE rate(query_name="my_query", ops_per_sec=100)
        }
      )

MITRE ATT&CK Mapping

Map artifacts to MITRE ATT&CK techniques:

name: Custom.Windows.PersistenceHunt
description: |
  Hunt for persistence mechanisms.

  MITRE ATT&CK Techniques:
  - T1547.001: Registry Run Keys / Startup Folder
  - T1053.005: Scheduled Task/Job
  - T1543.003: Windows Service
  - T1546.003: Windows Management Instrumentation Event Subscription

references:
  - https://attack.mitre.org/techniques/T1547/001/
  - https://attack.mitre.org/techniques/T1053/005/

Artifact Distribution

Export Artifacts

# Export single artifact
velociraptor artifacts show Custom.Artifact.Name > artifact.yaml

# Export all custom artifacts
velociraptor artifacts list --filter Custom > all_artifacts.yaml

Import Artifacts

# Via command line
velociraptor --config server.config.yaml artifacts import artifact.yaml

# Via GUI
# Navigate to: View Artifacts → Upload Artifact Pack

Share via Artifact Exchange

Contribute artifacts to the community:

  1. Test thoroughly across different systems
  2. Document clearly with examples
  3. Add MITRE ATT&CK mappings
  4. Submit to: https://docs.velociraptor.app/exchange/