My Windows DFIR Checklist for Initial Triage

4 December 2022 | justruss.tech

This is the triage checklist I actually use at the start of a Windows incident or serious CTF challenge. It is not exhaustive — deep-dive analysis follows once scope is understood. The goal of this phase is to answer three questions: what
happened, roughly when, and how widely has it spread?

Phase 0 — Before touching anything

If the system is live and you have time:

# Note the current system time and verify it against a trusted source
w32tm /query /status
# Check "Stratum" and "Last Successful Sync Time" - large skew = potential tampering

# Confirm your investigation user has the right privileges
whoami /priv | findstr /i "SeDebugPrivilege\|SeBackupPrivilege"
# SeDebugPrivilege is needed for memory access and some NTFS operations

Document the system state before any commands run. Take a screenshot of Task Manager. Note the system time. These baselines matter if the case goes to legal proceedings.

Phase 1 — Volatile data collection (live system only)

# Running processes with full paths and command lines
Get-WmiObject Win32_Process | Select-Object ProcessId, ParentProcessId, Name, ExecutablePath, CommandLine | Export-Csv C:\ir\processes.csv

# Network connections with owning process
Get-NetTCPConnection | Select-Object LocalAddress, LocalPort, RemoteAddress, RemotePort, State, OwningProcess | Export-Csv C:\ir\connections.csv

# Cross-reference connection PIDs with process list
$procs = Get-Process -IncludeUserName
Get-NetTCPConnection -State Established | ForEach-Object {
    $proc = $procs | Where-Object {$_.Id -eq $_.OwningProcess}
    [PSCustomObject]@{
        Remote = "$($_.RemoteAddress):$($_.RemotePort)"
        PID = $_.OwningProcess
        Process = $proc.Name
        Path = $proc.Path
        User = $proc.UserName
    }
} | Export-Csv C:\ir\active_connections.csv

# Scheduled tasks - full details including actions and triggers
schtasks /query /fo CSV /v > C:\ir\schtasks.csv

# Services - include binary path and start type
Get-WmiObject Win32_Service | Select-Object Name, State, StartMode, PathName, StartName | Export-Csv C:\ir\services.csv

# Loaded drivers
Get-WmiObject Win32_SystemDriver | Select-Object Name, State, PathName | Export-Csv C:\ir\drivers.csv

# DNS cache (can reveal C2 domains contacted)
Get-DnsClientCache | Export-Csv C:\ir\dns_cache.csv

Phase 2 — Event log triage

Extract targeted events from the last 48 hours rather than pulling entire log files:

# Account logon events - identify unusual accounts or times
Get-WinEvent -FilterHashtable @{
    LogName = "Security"
    Id = 4624
    StartTime = (Get-Date).AddHours(-48)
} | Where-Object {
    $_.Properties[8].Value -ne 5  # Exclude service logons
} | Select-Object TimeCreated,
    @{N="Account";E={$_.Properties[5].Value}},
    @{N="LogonType";E={$_.Properties[8].Value}},
    @{N="SourceIP";E={$_.Properties[18].Value}}

# New local accounts created
Get-WinEvent -FilterHashtable @{LogName="Security"; Id=4720; StartTime=(Get-Date).AddHours(-48)}

# Services installed (common persistence)
Get-WinEvent -FilterHashtable @{LogName="System"; Id=7045; StartTime=(Get-Date).AddHours(-48)} |
    ForEach-Object {
        [xml]$xml = $_.ToXml()
        [PSCustomObject]@{
            Time = $_.TimeCreated
            ServiceName = $xml.Event.EventData.Data[0]."#text"
            ImagePath = $xml.Event.EventData.Data[2]."#text"
        }
    }

# PowerShell script block logging - look for suspicious content
Get-WinEvent -FilterHashtable @{
    LogName = "Microsoft-Windows-PowerShell/Operational"
    Id = 4104
    Level = 3  # Warning level = flagged by PowerShell as suspicious
    StartTime = (Get-Date).AddHours(-48)
} | Select-Object TimeCreated, Message | Format-List

Phase 3 — Persistence locations

# Registry Run keys - all users
$runkeys = @(
    "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run",
    "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce",
    "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run",
    "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce",
    "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run"
)
foreach ($key in $runkeys) {
    if (Test-Path $key) {
        Get-ItemProperty $key | Select-Object * -ExcludeProperty PS*
    }
}

# WMI event subscriptions - commonly missed in IR checklists
Get-WMIObject -Namespace root\subscription -Class __EventFilter
Get-WMIObject -Namespace root\subscription -Class __EventConsumer
Get-WMIObject -Namespace root\subscription -Class __FilterToConsumerBinding

# AppInit_DLLs - DLL injection via registry
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows" -Name AppInit_DLLs
Get-ItemProperty "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows NT\CurrentVersion\Windows" -Name AppInit_DLLs

# COM object hijacking candidates
$userCOM = "HKCU:\SOFTWARE\Classes\CLSID"
if (Test-Path $userCOM) {
    Get-ChildItem $userCOM | Select-Object Name, LastWriteTime
}

# Startup folders
Get-ChildItem "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup" -ErrorAction SilentlyContinue
Get-ChildItem "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup" -ErrorAction SilentlyContinue

Phase 4 — Execution artefacts

# Prefetch - execution history
if (Test-Path C:\Windows\Prefetch) {
    Get-ChildItem C:\Windows\Prefetch\*.pf |
        Select-Object Name, LastWriteTime, CreationTime |
        Sort-Object LastWriteTime -Descending |
        Where-Object { $_.LastWriteTime -gt (Get-Date).AddHours(-48) }
}

# Amcache - file execution with hashes (run PECmd/AmcacheParser offline)
if (Test-Path C:\Windows\AppCompat\Programs\Amcache.hve) {
    Write-Output "Amcache.hve present - copy for offline analysis"
    Copy-Item C:\Windows\AppCompat\Programs\Amcache.hve C:\ir\
}

# Recently accessed files (Shellbags, LNK files)
Get-ChildItem "$env:APPDATA\Microsoft\Windows\Recent" -ErrorAction SilentlyContinue |
    Sort-Object LastWriteTime -Descending | Select-Object Name, LastWriteTime | head -20

The most common mistake

Spending the first 90 minutes going deep on a single suspicious process before completing broad triage. The suspicious process is often a distraction — a secondary tool run after the real persistence mechanism was already established. Complete
the full checklist first. The most important finding is usually not the most obvious one.