@port139 Blog

基本的にはデジタル・フォレンジックの技術について取り扱っていますが、記載内容には高確率で誤りが含まれる可能性があります。

取得対象のダミーファイルを作成する:Test-ForensicCollection.ps1

※ご注意:この記事は生成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オフラインコレクターでの(現物ファイル)データ取得を前提としています。

  1. 監査 (Audit): .\Test-ForensicCollection.ps1 -l "list.txt" で作成予定パスを確認(CSV形式でプレビュー出力)。

  2. 生成 (Create): .\Test-ForensicCollection.ps1 -l "list.txt" -Create でダミーファイルを展開。

  3. 収集 (Collection): 作成されたダミーファイルを対象に、Velociraptorオフラインコレクターを実行。

  4. 検証 (Verify): .\Test-ForensicCollection.ps1 -l "list.txt" -Verify -j "uploads.json" で収集成功率を算出。


4. 利用上の重要事項(免責事項)

[!WARNING]

本スクリプトはあくまでサンプルです

本スクリプトは検証用のサンプルであり、特定の環境での動作を保証するものではありません。

Test-ForensicCollection.ps1
<#
.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 ---"