Detection/Find-PersistenceMechanisms.ps1
|
function Find-PersistenceMechanisms { <# .SYNOPSIS Detects common Windows persistence mechanisms used by malware .DESCRIPTION Scans for suspicious persistence mechanisms including Registry Run keys, Startup folder items, Scheduled Tasks, Services, WMI subscriptions, and more .PARAMETER IncludeKnownGood Include known legitimate persistence mechanisms in results .PARAMETER ExportPath Path to export detailed results .EXAMPLE Find-PersistenceMechanisms Find-PersistenceMechanisms -IncludeKnownGood -ExportPath "C:\Audits" .OUTPUTS PSCustomObject with detected persistence mechanisms #> [CmdletBinding()] param( [Parameter()] [switch]$IncludeKnownGood, [Parameter()] [string]$ExportPath ) begin { Write-Host "Scanning for persistence mechanisms..." -ForegroundColor Cyan $results = [PSCustomObject]@{ ScanDate = Get-Date -Format "yyyy-MM-dd HH:mm:ss" ComputerName = $env:COMPUTERNAME RegistryRunKeys = @() StartupFolder = @() ScheduledTasks = @() Services = @() WMISubscriptions = @() AppInitDLLs = @() LSAProviders = @() PrintMonitors = @() TotalFindings = 0 SuspiciousCount = 0 } # Known good paths/patterns to filter (if not including known good) $knownGoodPatterns = @( "Microsoft", "Windows Defender", "OneDrive", "Adobe", "Google\\Update", "Intel", "NVIDIA" ) } process { try { # 1. Registry Run Keys Write-Verbose "Checking Registry Run keys..." $runKeyPaths = @( "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\Microsoft\Windows\CurrentVersion\RunServices", "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce", "HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Run", "HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\RunOnce" ) foreach ($keyPath in $runKeyPaths) { if (Test-Path $keyPath) { $items = Get-ItemProperty -Path $keyPath -ErrorAction SilentlyContinue if ($items) { $items.PSObject.Properties | Where-Object { $_.Name -notmatch '^PS' } | ForEach-Object { $isSuspicious = $true if (-not $IncludeKnownGood) { foreach ($pattern in $knownGoodPatterns) { if ($_.Value -match $pattern) { $isSuspicious = $false break } } } if ($isSuspicious -or $IncludeKnownGood) { $results.RegistryRunKeys += [PSCustomObject]@{ Location = $keyPath Name = $_.Name Value = $_.Value Suspicious = $isSuspicious } if ($isSuspicious) { $results.SuspiciousCount++ } } } } } } # 2. Startup Folders Write-Verbose "Checking Startup folders..." $startupPaths = @( "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup", "$env:ProgramData\Microsoft\Windows\Start Menu\Programs\Startup" ) foreach ($path in $startupPaths) { if (Test-Path $path) { $items = Get-ChildItem -Path $path -File -ErrorAction SilentlyContinue foreach ($item in $items) { $isSuspicious = -not ($item.Name -match ($knownGoodPatterns -join '|')) if ($isSuspicious -or $IncludeKnownGood) { $results.StartupFolder += [PSCustomObject]@{ Location = $path Name = $item.Name FullPath = $item.FullName Target = if ($item.LinkType -eq 'SymbolicLink') { $item.Target } else { 'N/A' } CreationTime = $item.CreationTime Suspicious = $isSuspicious } if ($isSuspicious) { $results.SuspiciousCount++ } } } } } # 3. Scheduled Tasks Write-Verbose "Checking Scheduled Tasks..." $tasks = Get-ScheduledTask -ErrorAction SilentlyContinue | Where-Object { $null -ne $_ -and $_.PSObject.Properties['State'] -and $_.State -ne 'Disabled' } foreach ($task in $tasks) { try { $taskInfo = Get-ScheduledTaskInfo -TaskName $task.TaskName -ErrorAction SilentlyContinue $actions = $task.Actions | ForEach-Object { if ($_.Execute) { $_.Execute } } $isSuspicious = $false # Check for suspicious indicators if ($task.Principal -and $task.Principal.UserId -eq 'SYSTEM' -and $task.TaskPath -notmatch '\\Microsoft\\') { $isSuspicious = $true } if ($actions -match '(powershell|cmd|wscript|cscript|mshta)' -and $task.TaskPath -notmatch '\\Microsoft\\') { $isSuspicious = $true } if ($isSuspicious -or $IncludeKnownGood) { $results.ScheduledTasks += [PSCustomObject]@{ Name = $task.TaskName Path = $task.TaskPath State = $task.State Actions = if ($actions) { ($actions -join '; ') } else { 'N/A' } RunAsUser = if ($task.Principal) { $task.Principal.UserId } else { 'N/A' } LastRunTime = if ($taskInfo -and $taskInfo.LastRunTime) { $taskInfo.LastRunTime } else { 'Never' } NextRunTime = if ($taskInfo -and $taskInfo.NextRunTime) { $taskInfo.NextRunTime } else { 'Not scheduled' } Suspicious = $isSuspicious } if ($isSuspicious) { $results.SuspiciousCount++ } } } catch { Write-Verbose "Could not process task: $($task.TaskName)" } } # 4. Services Write-Verbose "Checking Services..." $services = Get-CimInstance -ClassName Win32_Service foreach ($service in $services) { $isSuspicious = $false # Check for suspicious indicators if ($service.PathName -match '(temp|appdata|programdata.*\\.\\)' -and $service.PathName -notmatch 'Microsoft') { $isSuspicious = $true } if ($service.StartMode -eq 'Auto' -and $service.PathName -notmatch '(System32|Program Files)') { $isSuspicious = $true } if ($isSuspicious -or $IncludeKnownGood) { $results.Services += [PSCustomObject]@{ Name = $service.Name DisplayName = $service.DisplayName PathName = $service.PathName StartMode = $service.StartMode State = $service.State StartName = $service.StartName Suspicious = $isSuspicious } if ($isSuspicious) { $results.SuspiciousCount++ } } } # 5. WMI Event Subscriptions Write-Verbose "Checking WMI Event Subscriptions..." try { $wmiFilters = Get-CimInstance -Namespace root\subscription -ClassName __EventFilter -ErrorAction SilentlyContinue $wmiConsumers = Get-CimInstance -Namespace root\subscription -ClassName __EventConsumer -ErrorAction SilentlyContinue $wmiBindings = Get-CimInstance -Namespace root\subscription -ClassName __FilterToConsumerBinding -ErrorAction SilentlyContinue foreach ($binding in $wmiBindings) { $results.WMISubscriptions += [PSCustomObject]@{ Filter = $binding.Filter Consumer = $binding.Consumer Suspicious = $true # All non-Microsoft WMI subscriptions are suspicious } $results.SuspiciousCount++ } } catch { Write-Warning "Could not check WMI subscriptions: $_" } # 6. AppInit DLLs Write-Verbose "Checking AppInit DLLs..." $appInitPath = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows" if (Test-Path $appInitPath) { $appInit = Get-ItemProperty -Path $appInitPath -ErrorAction SilentlyContinue if ($appInit -and $appInit.PSObject.Properties['AppInit_DLLs'] -and $appInit.AppInit_DLLs) { $results.AppInitDLLs += [PSCustomObject]@{ Value = $appInit.AppInit_DLLs LoadAppInit = if ($appInit.PSObject.Properties['LoadAppInit_DLLs']) { $appInit.LoadAppInit_DLLs } else { 'N/A' } Suspicious = $true } $results.SuspiciousCount++ } } # 7. LSA Authentication Packages Write-Verbose "Checking LSA providers..." $lsaPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa" if (Test-Path $lsaPath) { $lsa = Get-ItemProperty -Path $lsaPath -ErrorAction SilentlyContinue # Check Authentication Packages if ($lsa -and $lsa.PSObject.Properties['Authentication Packages'] -and $lsa.'Authentication Packages') { foreach ($pkg in $lsa.'Authentication Packages') { if ($pkg -notmatch '^msv1_0$') { $results.LSAProviders += [PSCustomObject]@{ Type = "Authentication Package" Value = $pkg Suspicious = $true } $results.SuspiciousCount++ } } } # Check Notification Packages if ($lsa -and $lsa.PSObject.Properties['Notification Packages'] -and $lsa.'Notification Packages') { foreach ($pkg in $lsa.'Notification Packages') { if ($pkg -notmatch '^(scecli|rassfm)$') { $results.LSAProviders += [PSCustomObject]@{ Type = "Notification Package" Value = $pkg Suspicious = $true } $results.SuspiciousCount++ } } } } # 8. Print Monitors Write-Verbose "Checking Print Monitors..." $monitorPath = "HKLM:\SYSTEM\CurrentControlSet\Control\Print\Monitors" if (Test-Path $monitorPath) { $monitors = Get-ChildItem -Path $monitorPath -ErrorAction SilentlyContinue foreach ($monitor in $monitors) { $monitorProps = Get-ItemProperty -Path $monitor.PSPath -ErrorAction SilentlyContinue $driverValue = if ($monitorProps -and $monitorProps.PSObject.Properties['Driver']) { $monitorProps.Driver } else { $null } if ($driverValue -and $driverValue -notmatch '(Microsoft|Windows)') { $results.PrintMonitors += [PSCustomObject]@{ Name = $monitor.PSChildName Driver = $driverValue Suspicious = $true } $results.SuspiciousCount++ } } } # Calculate total findings $results.TotalFindings = ($results.RegistryRunKeys.Count + $results.StartupFolder.Count + $results.ScheduledTasks.Count + $results.Services.Count + $results.WMISubscriptions.Count + $results.AppInitDLLs.Count + $results.LSAProviders.Count + $results.PrintMonitors.Count) } catch { Write-Error "Error during persistence scan: $_" throw } } end { Write-Host "`nScan complete!" -ForegroundColor Green Write-Host "Total findings: $($results.TotalFindings)" -ForegroundColor Yellow Write-Host "Suspicious items: $($results.SuspiciousCount)" -ForegroundColor $(if ($results.SuspiciousCount -gt 0) { 'Red' } else { 'Green' }) # Export if requested if ($ExportPath) { if (-not (Test-Path $ExportPath)) { New-Item -Path $ExportPath -ItemType Directory -Force | Out-Null } $exportFile = Join-Path -Path $ExportPath -ChildPath "PersistenceScan_$($env:COMPUTERNAME)_$(Get-Date -Format 'yyyyMMdd_HHmmss').json" $results | ConvertTo-Json -Depth 5 | Out-File -FilePath $exportFile -Encoding UTF8 Write-Host "Results exported to: $exportFile" -ForegroundColor Cyan } return $results } } |