※ご注意:この記事は生成AIによって作成されています。
記載内容の正確性については細心の注意を払っておりますが、生成AIの特性上、ハルシネーション(事実に基づかない情報の生成)が含まれる可能性があります。提示されたスクリプトや手順を実務で利用される際は、必ず事前に十分な検証を行ってください。
※つまり、ほぼ全て生成AIに書いてもらった記事になるので、微妙な点もありますが「へぇ」くらいで読んでいただければ。
デジタルフォレンジックの現場において、Velociraptorオフラインコレクターは非常に強力なツールです。しかし、現場に投入する前に「自作した収集定義(YAML)やGlobパターンが、実際の環境で意図通りに機能するか」を確実に検証する必要があります。
本記事では、検証環境上にダミーデータを配置し、収集後の結果を自動で突き合わせることで、コレクターの「収集成功率」を可視化するPowerShellスクリプト「Test-ForensicCollection.ps1」について解説します。

1. 入力リスト(list.txt)の仕様と具体例
本スクリプトの動作を決定するのは、引数 -l で指定するテキストファイルです。ここには、Velociraptorの定義ファイル(YAML)などで使われるパスのパターンを記述します。
記述のルール
-
相対パスで記述: ドライブ文字(
C:\)を含まず、ルートディレクトリからの相対パスで記述します。 -
1行に1パターン: 1つの収集対象(Globパターン)につき1行を使用します。
具体的な変換例
スクリプトは、リスト内のワイルドカードを識別しやすい「証拠品マーカー」に置換してファイルを生成します。
| 入力リスト(list.txt)の記述例 | 生成されるダミーファイルのパス(例) |
case\999\*.exe |
C:\case\999\DummyEvidence_file.exe |
case\999\inetpub\logs\LogFiles\*.log |
C:\case\999\inetpub\logs\LogFiles\DummyEvidence_file.log |
case\999\inetpub\logs\**\*.log |
C:\case\999\...\DummyEvidence_Recursive01\DummyEvidence_Recursive02\DummyEvidence_file.log |
Users\*\NTUser.dat |
C:\Users\DummyEvidence_file\NTUser.dat |
実践的な活用案:標準定義のインポート
例えば、Velociraptorの標準的なトリアージ定義である Windows.Triage.Targets.yaml 内のファイルパス一覧を抽出し、このリストに流し込むといった利用方法があります。これにより、標準定義が自社の環境(特定のディレクトリ構造や権限)で確実に動作するかを事前にテストできます。
2. インシデントレスポンス視点の「安全設計」
検証環境での利用を前提としています
デジタルフォレンジックの検証用・学習用の環境での利用を想定しています。本番環境や重要なシステムでの実行は避けてください。また、システム領域への書き込みには管理者権限が必要です。
本スクリプトは、誤操作やシステム破壊を防ぐための多重のセーフティガードを実装しています。
-
既存ファイルの上書き防止: ファイル生成前に
Test-Pathで存在確認を行い、同名ファイルがある場合は書き込みを自動的にスキップします。 -
デフォルト安全モード(Read-Only):
-Createスイッチを指定しない限り、ファイルは一切作成されません。 -
絶対パスの禁止: リストに
C:\等の絶対パスが含まれる場合、安全装置が作動して処理を停止します。
3. 基本的なワークフロー
本スクリプトは、Velociraptorオフラインコレクターでの(現物ファイル)データ取得を前提としています。
-
監査 (Audit):
.\Test-ForensicCollection.ps1 -l "list.txt"で作成予定パスを確認(CSV形式でプレビュー出力)。 -
生成 (Create):
.\Test-ForensicCollection.ps1 -l "list.txt" -Createでダミーファイルを展開。 -
収集 (Collection): 作成されたダミーファイルを対象に、Velociraptorオフラインコレクターを実行。
-
検証 (Verify):
.\Test-ForensicCollection.ps1 -l "list.txt" -Verify -j "uploads.json"で収集成功率を算出。
4. 利用上の重要事項(免責事項)
[!WARNING]
本スクリプトはあくまでサンプルです
本スクリプトは検証用のサンプルであり、特定の環境での動作を保証するものではありません。
<#
.SYNOPSIS
Forensic Evidence Generation & Collection Verification Suite
.DESCRIPTION
A professional tool for validating forensic artifacts.
Defaults to CheckOnly mode for safety. Use -Create to write files.
.PARAMETER InputFile
Path to the text file containing patterns (Mandatory).
.PARAMETER Create
Enables actual file and directory creation on disk.
.EXAMPLE
.\Test-ForensicCollection.ps1 -l "list.txt"
.EXAMPLE
.\Test-ForensicCollection.ps1 -l "list.txt" -Create
.EXAMPLE
.\Test-ForensicCollection.ps1 -l "list.txt" -Verify -j "uploads.json"
#>
param (
[Parameter(Mandatory=$false)]
[Alias("l")]
[string]$InputFile,
[string]$RootDrive = "C:\",
[switch]$Create,
[switch]$CheckOnly,
[switch]$Verify,
[switch]$Force,
[Alias("j")]
[string]$JsonFile = "uploads.json"
)
# --- 1. Help Trigger ---
if ($PSBoundParameters.Count -eq 0) {
Get-Help $PSCommandPath
return
}
# --- 2. Dependency Check ---
if ([string]::IsNullOrWhiteSpace($InputFile)) {
Write-Host "[-] Error: Input file (-l) is required." -ForegroundColor Red
Write-Host "Usage: .\Test-ForensicCollection.ps1 -l <list_file> [-Create | -Verify]"
return
}
# --- 3. Mode Selection ---
if (-not ($Create -or $Verify)) {
$CheckOnly = $true
}
if (-not (Test-Path $InputFile)) {
Write-Host "[-] Error: File not found -> $InputFile" -ForegroundColor Red
return
}
$validEntries = @()
$errorReports = @()
$lineNum = 0
# --- 4. Simulation Engine ---
Get-Content $InputFile | ForEach-Object {
$lineNum++
$line = $_.Trim()
if (-not $line) { return }
if ($line -match "^[a-zA-Z]:") {
$errorReports += [PSCustomObject]@{ Line = $lineNum; Path = $line; Reason = "Absolute path detected" }
} else {
# Transformation Logic
$fixed = (Join-Path $RootDrive $line) `
-replace '\*\*', 'DummyEvidence_Recursive01\DummyEvidence_Recursive02\DummyEvidence_file' `
-replace '\*\.', 'DummyEvidence_file.' `
-replace '\*', 'DummyEvidence_file'
$validEntries += @{ "original" = $line; "fixed" = $fixed; "line" = $lineNum }
}
}
# --- 5. [Mode] Verification ---
if ($Verify) {
if (-not (Test-Path $JsonFile)) { Write-Host "[-] Error: $JsonFile not found." -ForegroundColor Red; return }
Write-Host "`n=== Collection Verification Phase ===" -ForegroundColor Cyan
$collectedPaths = New-Object 'System.Collections.Generic.HashSet[string]'
Get-Content $JsonFile | ForEach-Object {
try {
$data = $_ | ConvertFrom-Json
if ($data.vfs_path) {
$p = $data.vfs_path.ToLower().Replace("/", "\") -replace '\\+', '\'
[void]$collectedPaths.Add($p)
}
} catch { }
}
$results = @()
$successCount = 0
foreach ($entry in $validEntries) {
$normSim = $entry.fixed.ToLower().Replace("/", "\") -replace '\\+', '\'
$isFound = $false
foreach ($cp in $collectedPaths) {
if ($cp.EndsWith($normSim) -or $normSim.EndsWith($cp)) { $isFound = $true; break }
}
if ($isFound) { $successCount++ }
$results += [PSCustomObject]@{
Status = if ($isFound) { "OK" } else { "MISSING" };
Pattern = $entry.original;
ExpectedPath = $entry.fixed
}
}
$rate = if ($validEntries.Count -gt 0) { [math]::Round(($successCount / $validEntries.Count) * 100, 1) } else { 0 }
# Pre-calculate color to avoid parser errors
$rateColor = if ($rate -eq 100) { "Green" } else { "Yellow" }
Write-Host "Overall Success Rate: $rate % ($successCount / $($validEntries.Count))" -ForegroundColor $rateColor
$results | Format-Table -AutoSize
return
}
# --- 6. [Mode] CheckOnly ---
if ($CheckOnly) {
if ($errorReports.Count -gt 0) {
Write-Host "`n--- [Syntax Error Report] ---" -ForegroundColor Red
$errorReports | Format-Table -AutoSize
}
Write-Host "`n--- [Safe Mode: CSV Preview] ---" -ForegroundColor Cyan
Write-Output "DefinitionPattern,ExpectedDummyPath"
foreach ($entry in $validEntries) { Write-Output "$($entry.original),$($entry.fixed)" }
return
}
# --- 7. [Mode] Creation Phase ---
if ($errorReports.Count -gt 0 -and -not $Force) {
Write-Host "`n[STOP] Resolve syntax errors or use -Force." -ForegroundColor Red; exit
}
Write-Host "`n=== Creation Phase (Active Writing) ===" -ForegroundColor Yellow
foreach ($entry in $validEntries) {
$target = $entry.fixed
$parent = Split-Path -Path $target -Parent
try {
if (-not (Test-Path $target)) {
New-Item -ItemType Directory -Force -Path $parent | Out-Null
$content = "[DummyEvidence]`nOriginal: $($entry.original)`nGenerated: $(Get-Date)"
Set-Content -Path $target -Value $content -Encoding utf8
Write-Host "[+] Created: $target" -ForegroundColor Green
} else {
Write-Host "[!] Exists: $target" -ForegroundColor Gray
}
} catch { Write-Host "[X] Failed: $target" -ForegroundColor Red }
}
Write-Host "`n--- Operation Finished ---"