628 lines
13 KiB
Markdown
628 lines
13 KiB
Markdown
# Velociraptor Artifact Development Guide
|
|
|
|
Guide to creating custom VQL artifacts for specific investigation and threat hunting scenarios.
|
|
|
|
## Table of Contents
|
|
- [Artifact Structure](#artifact-structure)
|
|
- [Parameter Types](#parameter-types)
|
|
- [Source Types](#source-types)
|
|
- [Best Practices](#best-practices)
|
|
- [Common Patterns](#common-patterns)
|
|
- [Testing Artifacts](#testing-artifacts)
|
|
|
|
## Artifact Structure
|
|
|
|
Velociraptor artifacts are YAML files with a defined structure:
|
|
|
|
```yaml
|
|
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
|
|
|
|
```yaml
|
|
parameters:
|
|
- name: SearchPath
|
|
default: "C:/Windows/System32/"
|
|
type: string
|
|
description: Directory path to search
|
|
```
|
|
|
|
### Integer Parameters
|
|
|
|
```yaml
|
|
parameters:
|
|
- name: DaysBack
|
|
default: 7
|
|
type: int
|
|
description: Number of days to look back
|
|
```
|
|
|
|
### Boolean Parameters
|
|
|
|
```yaml
|
|
parameters:
|
|
- name: IncludeSystem
|
|
default: Y
|
|
type: bool
|
|
description: Include system files
|
|
```
|
|
|
|
### Regex Parameters
|
|
|
|
```yaml
|
|
parameters:
|
|
- name: ProcessPattern
|
|
default: "(?i)(powershell|cmd)"
|
|
type: regex
|
|
description: Process name pattern to match
|
|
```
|
|
|
|
### Choice Parameters
|
|
|
|
```yaml
|
|
parameters:
|
|
- name: LogLevel
|
|
default: "INFO"
|
|
type: choices
|
|
choices:
|
|
- DEBUG
|
|
- INFO
|
|
- WARNING
|
|
- ERROR
|
|
description: Logging verbosity
|
|
```
|
|
|
|
### CSV Parameters
|
|
|
|
```yaml
|
|
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:
|
|
|
|
```yaml
|
|
sources:
|
|
- name: ProcessCollection
|
|
query: |
|
|
SELECT Pid, Name, CommandLine, Username
|
|
FROM pslist()
|
|
WHERE Name =~ ProcessPattern
|
|
```
|
|
|
|
### Event Sources
|
|
|
|
Continuous monitoring queries for CLIENT_EVENT artifacts:
|
|
|
|
```yaml
|
|
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:
|
|
|
|
```yaml
|
|
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:
|
|
|
|
```yaml
|
|
# 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:
|
|
|
|
```yaml
|
|
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:
|
|
|
|
```yaml
|
|
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:
|
|
|
|
```yaml
|
|
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:
|
|
|
|
```yaml
|
|
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
|
|
|
|
```yaml
|
|
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
|
|
|
|
```yaml
|
|
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
|
|
|
|
```yaml
|
|
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
|
|
|
|
```yaml
|
|
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
|
|
|
|
```yaml
|
|
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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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:
|
|
|
|
```sql
|
|
-- 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
|
|
|
|
```yaml
|
|
# 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
|
|
|
|
```yaml
|
|
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
|
|
|
|
```yaml
|
|
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:
|
|
|
|
```yaml
|
|
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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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/
|