From ed0c1983b35eed3e9b23a447da417e567aac7737 Mon Sep 17 00:00:00 2001 From: mnerv <24420859+mnerv@users.noreply.github.com> Date: Tue, 3 Feb 2026 22:39:35 +0100 Subject: [PATCH] Add startup and persistence analysis tools Add Get-AutoRun.ps1, Get-ScheduledTasks.ps1, and Get-Services.ps1 for analyzing auto-start programs and persistence mechanisms. Get-AutoRun: Run/RunOnce keys, Startup folders, startup tasks Get-ScheduledTasks: Detailed task analysis with filters Get-Services: Service enumeration using WMI/CIM Uses Get-CimInstance for reliable service enumeration, avoiding Get-Service permission issues. Multiple filters and output modes. Update TODO.md. --- TODO.md | 8 +- windows/Get-AutoRun.ps1 | 156 +++++++++++++++++++++++++++++++++ windows/Get-ScheduledTasks.ps1 | 138 +++++++++++++++++++++++++++++ windows/Get-Services.ps1 | 111 +++++++++++++++++++++++ 4 files changed, 409 insertions(+), 4 deletions(-) create mode 100644 windows/Get-AutoRun.ps1 create mode 100644 windows/Get-ScheduledTasks.ps1 create mode 100644 windows/Get-Services.ps1 diff --git a/TODO.md b/TODO.md index 7bf272c..32120ec 100644 --- a/TODO.md +++ b/TODO.md @@ -40,10 +40,10 @@ - [x] System uptime history (Get-Info.ps1, winfetch.ps1) ### Persistence Mechanisms -- [ ] Run/RunOnce keys - Programs that auto-start -- [ ] Scheduled tasks -- [ ] Services -- [ ] Startup folder contents +- [x] Run/RunOnce keys - Programs that auto-start (Get-AutoRun.ps1) +- [x] Scheduled tasks (Get-ScheduledTasks.ps1) +- [x] Services (Get-Services.ps1) +- [x] Startup folder contents (Get-AutoRun.ps1) ### Network Artifacts - [ ] DNS Cache diff --git a/windows/Get-AutoRun.ps1 b/windows/Get-AutoRun.ps1 new file mode 100644 index 0000000..bfddc27 --- /dev/null +++ b/windows/Get-AutoRun.ps1 @@ -0,0 +1,156 @@ +# Get-AutoRun.ps1 +# Lists programs that auto-start via Run/RunOnce keys and Startup folders + +param( + [switch]$IncludeDisabled +) + +Write-Host "=== Auto-Start Programs ===" -ForegroundColor Cyan +Write-Host "Programs configured to run at startup`n" + +$foundAny = $false + +# Registry Run keys to check +$runKeys = @( + @{Path="HKLM:\Software\Microsoft\Windows\CurrentVersion\Run"; Scope="System (All Users)"}, + @{Path="HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce"; Scope="System (All Users, Once)"}, + @{Path="HKCU:\Software\Microsoft\Windows\CurrentVersion\Run"; Scope="Current User"}, + @{Path="HKCU:\Software\Microsoft\Windows\CurrentVersion\RunOnce"; Scope="Current User (Once)"}, + @{Path="HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Run"; Scope="System (32-bit on 64-bit)"}, + @{Path="HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\RunOnce"; Scope="System (32-bit, Once)"} +) + +# Check Run/RunOnce registry keys +Write-Host "--- Registry Run Keys ---" -ForegroundColor Yellow + +foreach ($key in $runKeys) { + if (Test-Path $key.Path) { + $entries = Get-ItemProperty -Path $key.Path -ErrorAction SilentlyContinue + + if ($entries) { + $props = $entries.PSObject.Properties | Where-Object { $_.Name -notmatch "^PS" } + + if ($props) { + $foundAny = $true + Write-Host "`n$($key.Scope)" -ForegroundColor Green + Write-Host " Path: $($key.Path)" -ForegroundColor Gray + + foreach ($prop in $props) { + Write-Host " - $($prop.Name):" -ForegroundColor Cyan + Write-Host " $($prop.Value)" -ForegroundColor White + } + } + } + } +} + +# Startup folders +Write-Host "`n--- Startup Folders ---" -ForegroundColor Yellow + +$startupFolders = @( + @{Path="$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup"; Scope="Current User"}, + @{Path="$env:ProgramData\Microsoft\Windows\Start Menu\Programs\Startup"; Scope="All Users"} +) + +foreach ($folder in $startupFolders) { + if (Test-Path $folder.Path) { + $items = Get-ChildItem -Path $folder.Path -File -ErrorAction SilentlyContinue + + if ($items) { + $foundAny = $true + Write-Host "`n$($folder.Scope)" -ForegroundColor Green + Write-Host " Path: $($folder.Path)" -ForegroundColor Gray + + foreach ($item in $items) { + Write-Host " - $($item.Name)" -ForegroundColor Cyan + Write-Host " $($item.FullName)" -ForegroundColor White + + # If it's a shortcut, try to get target + if ($item.Extension -eq ".lnk") { + try { + $shell = New-Object -ComObject WScript.Shell + $shortcut = $shell.CreateShortcut($item.FullName) + if ($shortcut.TargetPath) { + Write-Host " Target: $($shortcut.TargetPath)" -ForegroundColor Gray + if ($shortcut.Arguments) { + Write-Host " Arguments: $($shortcut.Arguments)" -ForegroundColor Gray + } + } + [System.Runtime.Interopservices.Marshal]::ReleaseComObject($shell) | Out-Null + } catch { + # Silently fail if can't read shortcut + } + } + } + } + } +} + +# Task Scheduler startup tasks (basic check) +Write-Host "`n--- Task Scheduler (Run at Startup) ---" -ForegroundColor Yellow +try { + $startupTasks = Get-ScheduledTask -ErrorAction SilentlyContinue | + Where-Object { + $_.Triggers.CimClass.CimClassName -contains "MSFT_TaskBootTrigger" -or + $_.Triggers.CimClass.CimClassName -contains "MSFT_TaskLogonTrigger" + } | + Where-Object { $_.State -ne "Disabled" -or $IncludeDisabled } + + if ($startupTasks) { + $foundAny = $true + + foreach ($task in $startupTasks) { + $triggerType = if ($task.Triggers.CimClass.CimClassName -contains "MSFT_TaskBootTrigger") { + "At system startup" + } else { + "At user logon" + } + + $state = if ($task.State -eq "Disabled") { " (DISABLED)" } else { "" } + + Write-Host "`n - $($task.TaskName)$state" -ForegroundColor Cyan + Write-Host " Path: $($task.TaskPath)" -ForegroundColor Gray + Write-Host " Trigger: $triggerType" -ForegroundColor Gray + Write-Host " State: $($task.State)" -ForegroundColor Gray + + if ($task.Actions.Execute) { + Write-Host " Command: $($task.Actions.Execute)" -ForegroundColor White + if ($task.Actions.Arguments) { + Write-Host " Arguments: $($task.Actions.Arguments)" -ForegroundColor Gray + } + } + } + } else { + Write-Host " No startup tasks found" -ForegroundColor Gray + } +} catch { + Write-Host " Error accessing scheduled tasks: $_" -ForegroundColor Red +} + +# Windows Services set to Automatic +Write-Host "`n--- Services (Automatic Start) ---" -ForegroundColor Yellow +try { + # Try WMI method first (more reliable) + $autoServices = Get-CimInstance Win32_Service -ErrorAction Stop | + Where-Object { $_.StartMode -eq "Auto" } + + if ($autoServices) { + $foundAny = $true + $running = $autoServices | Where-Object { $_.State -eq "Running" } + $stopped = $autoServices | Where-Object { $_.State -ne "Running" } + + Write-Host "`nRunning: $($running.Count) | Stopped: $($stopped.Count) | Total: $($autoServices.Count)" -ForegroundColor Green + Write-Host "(Use Get-Services.ps1 for detailed service information)" -ForegroundColor Gray + } else { + Write-Host " No automatic services found" -ForegroundColor Gray + } +} catch { + Write-Host " Unable to enumerate services" -ForegroundColor Yellow +} + +if (-not $foundAny) { + Write-Host "`nNo auto-start programs found" -ForegroundColor Gray +} + +Write-Host "`nNote: Use -IncludeDisabled to show disabled scheduled tasks" -ForegroundColor Cyan +Write-Host "Forensic value: Shows persistence mechanisms and startup performance impacts" -ForegroundColor Cyan diff --git a/windows/Get-ScheduledTasks.ps1 b/windows/Get-ScheduledTasks.ps1 new file mode 100644 index 0000000..c739af6 --- /dev/null +++ b/windows/Get-ScheduledTasks.ps1 @@ -0,0 +1,138 @@ +# Get-ScheduledTasks.ps1 +# Lists scheduled tasks with details + +param( + [ValidateSet("All", "Running", "Ready", "Disabled", "Startup")] + [string]$Filter = "All", + [int]$MaxResults = 50, + [switch]$ShowAll +) + +Write-Host "=== Scheduled Tasks ===" -ForegroundColor Cyan + +if ($ShowAll) { + $MaxResults = [int]::MaxValue +} + +$filterDesc = switch ($Filter) { + "Running" { "Currently running tasks" } + "Ready" { "Tasks ready to run" } + "Disabled" { "Disabled tasks" } + "Startup" { "Tasks that run at startup/logon" } + default { "All scheduled tasks" } +} + +Write-Host "$filterDesc`n" -ForegroundColor Yellow + +try { + # Get all scheduled tasks + $tasks = Get-ScheduledTask -ErrorAction Stop + + # Apply filter + $filteredTasks = switch ($Filter) { + "Running" { $tasks | Where-Object { $_.State -eq "Running" } } + "Ready" { $tasks | Where-Object { $_.State -eq "Ready" } } + "Disabled" { $tasks | Where-Object { $_.State -eq "Disabled" } } + "Startup" { + $tasks | Where-Object { + $_.Triggers.CimClass.CimClassName -contains "MSFT_TaskBootTrigger" -or + $_.Triggers.CimClass.CimClassName -contains "MSFT_TaskLogonTrigger" + } + } + default { $tasks } + } + + if ($filteredTasks) { + # Sort by path and name + $sortedTasks = $filteredTasks | Sort-Object TaskPath, TaskName + + Write-Host "Found: $($filteredTasks.Count) tasks" -ForegroundColor Green + Write-Host "Showing: $(if ($ShowAll) { "All" } else { "Top $MaxResults" })`n" -ForegroundColor Gray + + $count = 0 + foreach ($task in ($sortedTasks | Select-Object -First $MaxResults)) { + $count++ + + # Color based on state + $nameColor = switch ($task.State) { + "Running" { "Green" } + "Ready" { "Cyan" } + "Disabled" { "Gray" } + default { "Yellow" } + } + + Write-Host "$count. $($task.TaskPath)$($task.TaskName)" -ForegroundColor $nameColor + Write-Host " State: $($task.State)" -ForegroundColor Gray + + # Show triggers + if ($task.Triggers) { + $triggerInfo = @() + foreach ($trigger in $task.Triggers) { + $triggerType = switch ($trigger.CimClass.CimClassName) { + "MSFT_TaskBootTrigger" { "At system startup" } + "MSFT_TaskLogonTrigger" { "At user logon" } + "MSFT_TaskDailyTrigger" { "Daily" } + "MSFT_TaskTimeTrigger" { "At specific time" } + "MSFT_TaskEventTrigger" { "On event" } + "MSFT_TaskIdleTrigger" { "On idle" } + default { $trigger.CimClass.CimClassName -replace "MSFT_Task", "" -replace "Trigger", "" } + } + $triggerInfo += $triggerType + } + Write-Host " Trigger: $($triggerInfo -join ', ')" -ForegroundColor Gray + } + + # Show action + if ($task.Actions) { + foreach ($action in $task.Actions) { + if ($action.Execute) { + Write-Host " Execute: $($action.Execute)" -ForegroundColor White + if ($action.Arguments) { + Write-Host " Arguments: $($action.Arguments)" -ForegroundColor Gray + } + if ($action.WorkingDirectory) { + Write-Host " WorkDir: $($action.WorkingDirectory)" -ForegroundColor Gray + } + } + } + } + + # Show last/next run time + $taskInfo = Get-ScheduledTaskInfo -TaskName $task.TaskName -TaskPath $task.TaskPath -ErrorAction SilentlyContinue + if ($taskInfo) { + if ($taskInfo.LastRunTime -and $taskInfo.LastRunTime.Year -gt 1) { + Write-Host " Last Run: $($taskInfo.LastRunTime.ToString('yyyy-MM-dd HH:mm:ss'))" -ForegroundColor Gray + } + if ($taskInfo.NextRunTime -and $taskInfo.NextRunTime.Year -gt 1) { + Write-Host " Next Run: $($taskInfo.NextRunTime.ToString('yyyy-MM-dd HH:mm:ss'))" -ForegroundColor Gray + } + if ($taskInfo.LastTaskResult -ne 0) { + Write-Host " Last Result: 0x$($taskInfo.LastTaskResult.ToString('X'))" -ForegroundColor $(if ($taskInfo.LastTaskResult -eq 0) { "Green" } else { "Red" }) + } + } + + Write-Host "" + } + + if ($filteredTasks.Count -gt $MaxResults -and -not $ShowAll) { + Write-Host "... and $($filteredTasks.Count - $MaxResults) more (use -ShowAll to see all)" -ForegroundColor Gray + } + + # Summary + Write-Host "`n=== Summary ===" -ForegroundColor Cyan + $states = $filteredTasks | Group-Object State + foreach ($state in $states) { + Write-Host "$($state.Name): $($state.Count)" -ForegroundColor Green + } + + } else { + Write-Host "No tasks found matching filter" -ForegroundColor Gray + } + +} catch { + Write-Host "Error: $_" -ForegroundColor Red + Write-Host "Try running as Administrator for full task access" -ForegroundColor Yellow +} + +Write-Host "`nFilters: -Filter All|Running|Ready|Disabled|Startup" -ForegroundColor Cyan +Write-Host "Use -ShowAll to see all tasks (default: top $MaxResults)" -ForegroundColor Cyan diff --git a/windows/Get-Services.ps1 b/windows/Get-Services.ps1 new file mode 100644 index 0000000..fd2249f --- /dev/null +++ b/windows/Get-Services.ps1 @@ -0,0 +1,111 @@ +# Get-Services.ps1 +# Lists Windows services with details + +param( + [ValidateSet("All", "Running", "Stopped", "Automatic", "Manual", "Disabled")] + [string]$Filter = "All", + [int]$MaxResults = 50, + [switch]$ShowAll +) + +Write-Host "=== Windows Services ===" -ForegroundColor Cyan + +if ($ShowAll) { + $MaxResults = [int]::MaxValue +} + +$filterDesc = switch ($Filter) { + "Running" { "Running services" } + "Stopped" { "Stopped services" } + "Automatic" { "Services set to start automatically" } + "Manual" { "Manual start services" } + "Disabled" { "Disabled services" } + default { "All services" } +} + +Write-Host "$filterDesc`n" -ForegroundColor Yellow + +try { + # Use WMI/CIM for reliable service enumeration + $services = Get-CimInstance Win32_Service -ErrorAction Stop + + # Apply filter + $filteredServices = switch ($Filter) { + "Running" { $services | Where-Object { $_.State -eq "Running" } } + "Stopped" { $services | Where-Object { $_.State -eq "Stopped" } } + "Automatic" { $services | Where-Object { $_.StartMode -eq "Auto" } } + "Manual" { $services | Where-Object { $_.StartMode -eq "Manual" } } + "Disabled" { $services | Where-Object { $_.StartMode -eq "Disabled" } } + default { $services } + } + + if ($filteredServices) { + # Sort by state then name + $sortedServices = $filteredServices | Sort-Object State, DisplayName + + Write-Host "Found: $($filteredServices.Count) services" -ForegroundColor Green + Write-Host "Showing: $(if ($ShowAll) { "All" } else { "Top $MaxResults" })`n" -ForegroundColor Gray + + $count = 0 + foreach ($service in ($sortedServices | Select-Object -First $MaxResults)) { + $count++ + + # Color based on status + $nameColor = switch ($service.State) { + "Running" { "Green" } + "Stopped" { "Gray" } + default { "Yellow" } + } + + $statusSymbol = if ($service.State -eq "Running") { "●" } else { "○" } + + Write-Host "$count. $statusSymbol $($service.DisplayName)" -ForegroundColor $nameColor + Write-Host " Name: $($service.Name)" -ForegroundColor Gray + Write-Host " Status: $($service.State) | Start Mode: $($service.StartMode)" -ForegroundColor Gray + + if ($service.PathName) { + Write-Host " Path: $($service.PathName)" -ForegroundColor White + } + if ($service.Description) { + Write-Host " Description: $($service.Description)" -ForegroundColor Gray + } + if ($service.StartName) { + Write-Host " Run As: $($service.StartName)" -ForegroundColor Gray + } + if ($service.ProcessId -and $service.ProcessId -ne 0) { + Write-Host " PID: $($service.ProcessId)" -ForegroundColor Gray + } + + Write-Host "" + } + + if ($filteredServices.Count -gt $MaxResults -and -not $ShowAll) { + Write-Host "... and $($filteredServices.Count - $MaxResults) more (use -ShowAll to see all)" -ForegroundColor Gray + } + + # Summary + Write-Host "`n=== Summary ===" -ForegroundColor Cyan + + $stateGroups = $filteredServices | Group-Object State + Write-Host "By Status:" -ForegroundColor Yellow + foreach ($group in $stateGroups) { + Write-Host " $($group.Name): $($group.Count)" -ForegroundColor Green + } + + $startModeGroups = $filteredServices | Group-Object StartMode + Write-Host "`nBy Start Mode:" -ForegroundColor Yellow + foreach ($group in $startModeGroups) { + Write-Host " $($group.Name): $($group.Count)" -ForegroundColor Green + } + + } else { + Write-Host "No services found matching filter" -ForegroundColor Gray + } + +} catch { + Write-Host "Error: $_" -ForegroundColor Red +} + +Write-Host "`nFilters: -Filter All|Running|Stopped|Automatic|Manual|Disabled" -ForegroundColor Cyan +Write-Host "Use -ShowAll to see all services (default: top $MaxResults)" -ForegroundColor Cyan +Write-Host "`nForensic note: Check for suspicious service names, paths, or 'Run As' accounts" -ForegroundColor Cyan