Created
June 10, 2026 18:48
-
-
Save affix/1d1eeacddda88a36de576e04ac24399d to your computer and use it in GitHub Desktop.
Sweeps queryable Windows persistence locations for references to a target executable
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <# | |
| .SYNOPSIS | |
| Sweeps queryable Windows persistence locations for references to a target executable. | |
| .DESCRIPTION | |
| Matches both senses: | |
| - PAYLOAD : your exe is referenced as the thing being launched (Run value, service | |
| ImagePath, scheduled-task action, IFEO Debugger, WMI consumer, etc.) | |
| - TARGET : your exe is the thing being hijacked (IFEO key / SilentProcessExit entry | |
| named after it). | |
| Read-only. Does not modify anything. Run elevated for full HKLM / LSA / all-users coverage. | |
| Iterates every loaded user hive under HKU, not just the current user. | |
| .PARAMETER ExeName | |
| Substring to match, case-insensitive. Pass a bare name ("evil.exe") to catch it regardless | |
| of path, or a full path fragment to scope tighter. | |
| .EXAMPLE | |
| .\Find-ExePersistence.ps1 -ExeName "beacon.exe" | |
| .NOTES | |
| Coverage is intentionally broad but not exhaustive. Catches: Run/RunOnce, Startup, | |
| scheduled tasks, services, WMI subscriptions, Winlogon, Active Setup, IFEO/GlobalFlag, | |
| SilentProcessExit, AppInit_DLLs, netsh helpers, LSA packages, print monitors, logon | |
| scripts, screensaver, PowerShell profiles, BITS. Does NOT catch fileless/in-memory or | |
| AD-side persistence (golden/silver tickets, skeleton key, DCSync rights, etc.). | |
| #> | |
| [CmdletBinding()] | |
| param( | |
| [Parameter(Mandatory)] | |
| [string]$ExeName | |
| ) | |
| $results = [System.Collections.Generic.List[object]]::new() | |
| function Add-Hit { | |
| param($Category, $Location, $Value, $Sense = 'PAYLOAD') | |
| $results.Add([pscustomobject]@{ | |
| Category = $Category | |
| Sense = $Sense | |
| Location = $Location | |
| Value = $Value | |
| }) | |
| } | |
| function Test-Match { param($s) $s -and ($s -match [regex]::Escape($ExeName)) } | |
| # Resolve every loaded user hive so HKCU-style keys are checked for all logged-on users | |
| $userHives = @('HKCU') | |
| try { | |
| Get-ChildItem 'Registry::HKEY_USERS' -ErrorAction Stop | | |
| Where-Object { $_.Name -notmatch '_Classes$' -and $_.PSChildName -match '^S-1-5-21' } | | |
| ForEach-Object { $userHives += "Registry::$($_.Name)" } | |
| } catch {} | |
| Write-Host "[*] Hunting for '$ExeName' across $($userHives.Count) user hive(s) + machine scope...`n" -ForegroundColor Cyan | |
| # --------------------------------------------------------------------------- | |
| # 1. Run / RunOnce (HKCU+all users, HKLM, Wow6432Node, Policies\Explorer\Run) | |
| # --------------------------------------------------------------------------- | |
| $runKeys = @() | |
| foreach ($h in $userHives) { | |
| $runKeys += "$h\Software\Microsoft\Windows\CurrentVersion\Run" | |
| $runKeys += "$h\Software\Microsoft\Windows\CurrentVersion\RunOnce" | |
| $runKeys += "$h\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run" | |
| } | |
| $runKeys += @( | |
| 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Run' | |
| 'HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce' | |
| 'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Run' | |
| 'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\RunOnce' | |
| 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run' | |
| ) | |
| foreach ($k in $runKeys) { | |
| try { | |
| $props = Get-ItemProperty -Path $k -ErrorAction Stop | |
| foreach ($p in $props.PSObject.Properties) { | |
| if ($p.Name -notmatch '^PS' -and (Test-Match $p.Value)) { | |
| Add-Hit 'Run/RunOnce' "$k\$($p.Name)" $p.Value | |
| } | |
| } | |
| } catch {} | |
| } | |
| # --------------------------------------------------------------------------- | |
| # 2. Startup folders | |
| # --------------------------------------------------------------------------- | |
| $startupDirs = @( | |
| [Environment]::GetFolderPath('CommonStartup') | |
| "$env:SystemDrive\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp" | |
| ) | |
| # Per-user Startup for EVERY profile, not just whoever is running this sweep | |
| Get-ChildItem 'C:\Users' -Directory -Force -ErrorAction SilentlyContinue | ForEach-Object { | |
| $startupDirs += "$($_.FullName)\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup" | |
| } | |
| $startupDirs = $startupDirs | Select-Object -Unique | |
| foreach ($d in $startupDirs) { | |
| if (Test-Path $d) { | |
| Get-ChildItem $d -Force -ErrorAction SilentlyContinue | ForEach-Object { | |
| $target = $_.FullName | |
| if ($_.Extension -eq '.lnk') { | |
| try { | |
| $sh = New-Object -ComObject WScript.Shell | |
| $target = ($sh.CreateShortcut($_.FullName)).TargetPath | |
| } catch {} | |
| } | |
| if (Test-Match $target) { Add-Hit 'StartupFolder' $_.FullName $target } | |
| } | |
| } | |
| } | |
| # --------------------------------------------------------------------------- | |
| # 3. Scheduled tasks | |
| # --------------------------------------------------------------------------- | |
| try { | |
| Get-ScheduledTask -ErrorAction Stop | ForEach-Object { | |
| $t = $_ | |
| foreach ($a in $t.Actions) { | |
| $line = "$($a.Execute) $($a.Arguments)" | |
| if (Test-Match $line) { | |
| Add-Hit 'ScheduledTask' "$($t.TaskPath)$($t.TaskName)" $line.Trim() | |
| } | |
| } | |
| } | |
| } catch {} | |
| # --------------------------------------------------------------------------- | |
| # 4. Services (ImagePath / binary + ServiceDll) | |
| # --------------------------------------------------------------------------- | |
| try { | |
| Get-CimInstance Win32_Service -ErrorAction Stop | ForEach-Object { | |
| if (Test-Match $_.PathName) { | |
| Add-Hit 'Service' "$($_.Name) ($($_.StartMode))" $_.PathName | |
| } | |
| } | |
| } catch {} | |
| # svchost-hosted ServiceDll | |
| try { | |
| Get-ChildItem 'HKLM:\SYSTEM\CurrentControlSet\Services' -ErrorAction Stop | ForEach-Object { | |
| $params = "HKLM:\SYSTEM\CurrentControlSet\Services\$($_.PSChildName)\Parameters" | |
| try { | |
| $dll = (Get-ItemProperty $params -ErrorAction Stop).ServiceDll | |
| if (Test-Match $dll) { Add-Hit 'Service(ServiceDll)' $_.PSChildName $dll } | |
| } catch {} | |
| } | |
| } catch {} | |
| # --------------------------------------------------------------------------- | |
| # 5. WMI permanent event subscriptions (root\subscription) | |
| # --------------------------------------------------------------------------- | |
| foreach ($cls in 'CommandLineEventConsumer','ActiveScriptEventConsumer') { | |
| try { | |
| Get-CimInstance -Namespace root\subscription -ClassName $cls -ErrorAction Stop | ForEach-Object { | |
| $payload = if ($cls -eq 'CommandLineEventConsumer') { | |
| "$($_.ExecutablePath) $($_.CommandLineTemplate)" | |
| } else { | |
| $_.ScriptText | |
| } | |
| if (Test-Match $payload) { | |
| Add-Hit 'WMI-Subscription' "$cls : $($_.Name)" $payload | |
| } | |
| } | |
| } catch {} | |
| } | |
| # --------------------------------------------------------------------------- | |
| # 6. Winlogon (Userinit, Shell, Taskman) - machine + per-user | |
| # --------------------------------------------------------------------------- | |
| $winlogonKeys = @('HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Winlogon') | |
| foreach ($h in $userHives) { | |
| $winlogonKeys += "$h\Software\Microsoft\Windows NT\CurrentVersion\Winlogon" | |
| } | |
| foreach ($wk in $winlogonKeys) { | |
| foreach ($v in 'Userinit','Shell','Taskman') { | |
| try { | |
| $val = (Get-ItemProperty $wk -Name $v -ErrorAction Stop).$v | |
| if (Test-Match $val) { Add-Hit 'Winlogon' "$wk\$v" $val } | |
| } catch {} | |
| } | |
| } | |
| # --------------------------------------------------------------------------- | |
| # 7. Active Setup (StubPath) | |
| # --------------------------------------------------------------------------- | |
| foreach ($as in 'HKLM:\Software\Microsoft\Active Setup\Installed Components', | |
| 'HKLM:\Software\Wow6432Node\Microsoft\Active Setup\Installed Components') { | |
| try { | |
| Get-ChildItem $as -ErrorAction Stop | ForEach-Object { | |
| try { | |
| $sp = (Get-ItemProperty $_.PSPath -Name StubPath -ErrorAction Stop).StubPath | |
| if (Test-Match $sp) { Add-Hit 'ActiveSetup' $_.PSChildName $sp } | |
| } catch {} | |
| } | |
| } catch {} | |
| } | |
| # --------------------------------------------------------------------------- | |
| # 8. IFEO Debugger / GlobalFlag + SilentProcessExit | |
| # --------------------------------------------------------------------------- | |
| foreach ($ifeo in 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options', | |
| 'HKLM:\Software\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Image File Execution Options') { | |
| try { | |
| Get-ChildItem $ifeo -ErrorAction Stop | ForEach-Object { | |
| $name = $_.PSChildName | |
| # TARGET sense: a key named after our exe means something hijacks it | |
| if (Test-Match $name) { Add-Hit 'IFEO-Key' "$ifeo\$name" '(exe is hijack target)' 'TARGET' } | |
| try { | |
| $dbg = (Get-ItemProperty $_.PSPath -Name Debugger -ErrorAction Stop).Debugger | |
| if (Test-Match $dbg) { Add-Hit 'IFEO-Debugger' "$ifeo\$name" $dbg } | |
| } catch {} | |
| } | |
| } catch {} | |
| } | |
| $spe = 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\SilentProcessExit' | |
| try { | |
| Get-ChildItem $spe -ErrorAction Stop | ForEach-Object { | |
| $name = $_.PSChildName | |
| if (Test-Match $name) { Add-Hit 'SilentProcessExit-Key' "$spe\$name" '(exe is monitored target)' 'TARGET' } | |
| try { | |
| $mon = (Get-ItemProperty $_.PSPath -Name MonitorProcess -ErrorAction Stop).MonitorProcess | |
| if (Test-Match $mon) { Add-Hit 'SilentProcessExit-Monitor' "$spe\$name" $mon } | |
| } catch {} | |
| } | |
| } catch {} | |
| # --------------------------------------------------------------------------- | |
| # 9. AppInit_DLLs / AppCertDlls | |
| # --------------------------------------------------------------------------- | |
| foreach ($wk in 'HKLM:\Software\Microsoft\Windows NT\CurrentVersion\Windows', | |
| 'HKLM:\Software\Wow6432Node\Microsoft\Windows NT\CurrentVersion\Windows') { | |
| try { | |
| $ai = (Get-ItemProperty $wk -Name AppInit_DLLs -ErrorAction Stop).AppInit_DLLs | |
| if (Test-Match $ai) { Add-Hit 'AppInit_DLLs' $wk $ai } | |
| } catch {} | |
| } | |
| try { | |
| $ac = (Get-ItemProperty 'HKLM:\System\CurrentControlSet\Control\Session Manager\AppCertDlls' -ErrorAction Stop) | |
| foreach ($p in $ac.PSObject.Properties) { | |
| if ($p.Name -notmatch '^PS' -and (Test-Match $p.Value)) { Add-Hit 'AppCertDlls' $p.Name $p.Value } | |
| } | |
| } catch {} | |
| # --------------------------------------------------------------------------- | |
| # 10. Netsh helper DLLs | |
| # --------------------------------------------------------------------------- | |
| try { | |
| $nh = Get-ItemProperty 'HKLM:\Software\Microsoft\Netsh' -ErrorAction Stop | |
| foreach ($p in $nh.PSObject.Properties) { | |
| if ($p.Name -notmatch '^PS' -and (Test-Match $p.Value)) { Add-Hit 'NetshHelper' $p.Name $p.Value } | |
| } | |
| } catch {} | |
| # --------------------------------------------------------------------------- | |
| # 11. LSA authentication / security / notification packages | |
| # --------------------------------------------------------------------------- | |
| try { | |
| $lsa = Get-ItemProperty 'HKLM:\System\CurrentControlSet\Control\Lsa' -ErrorAction Stop | |
| foreach ($v in 'Authentication Packages','Security Packages','Notification Packages') { | |
| if (Test-Match ($lsa.$v -join ' ')) { Add-Hit 'LSA-Packages' $v ($lsa.$v -join ', ') } | |
| } | |
| } catch {} | |
| # --------------------------------------------------------------------------- | |
| # 12. Print monitors / processors | |
| # --------------------------------------------------------------------------- | |
| try { | |
| Get-ChildItem 'HKLM:\System\CurrentControlSet\Control\Print\Monitors' -ErrorAction Stop | ForEach-Object { | |
| try { | |
| $drv = (Get-ItemProperty $_.PSPath -Name Driver -ErrorAction Stop).Driver | |
| if (Test-Match $drv) { Add-Hit 'PrintMonitor' $_.PSChildName $drv } | |
| } catch {} | |
| } | |
| } catch {} | |
| # --------------------------------------------------------------------------- | |
| # 13. Logon scripts + screensaver (per-user) | |
| # --------------------------------------------------------------------------- | |
| foreach ($h in $userHives) { | |
| try { | |
| $ls = (Get-ItemProperty "$h\Environment" -Name UserInitMprLogonScript -ErrorAction Stop).UserInitMprLogonScript | |
| if (Test-Match $ls) { Add-Hit 'LogonScript' "$h\Environment\UserInitMprLogonScript" $ls } | |
| } catch {} | |
| try { | |
| $ss = (Get-ItemProperty "$h\Control Panel\Desktop" -Name 'SCRNSAVE.EXE' -ErrorAction Stop).'SCRNSAVE.EXE' | |
| if (Test-Match $ss) { Add-Hit 'Screensaver' "$h\Control Panel\Desktop\SCRNSAVE.EXE" $ss } | |
| } catch {} | |
| } | |
| # --------------------------------------------------------------------------- | |
| # 14. PowerShell profiles (content grep) | |
| # --------------------------------------------------------------------------- | |
| $profilePaths = @( | |
| "$env:windir\System32\WindowsPowerShell\v1.0\profile.ps1" | |
| "$env:windir\System32\WindowsPowerShell\v1.0\Microsoft.PowerShell_profile.ps1" | |
| ) | |
| Get-ChildItem 'C:\Users' -Directory -ErrorAction SilentlyContinue | ForEach-Object { | |
| $profilePaths += "$($_.FullName)\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1" | |
| $profilePaths += "$($_.FullName)\Documents\PowerShell\Microsoft.PowerShell_profile.ps1" | |
| } | |
| foreach ($pf in ($profilePaths | Select-Object -Unique)) { | |
| if (Test-Path $pf) { | |
| try { | |
| $c = Get-Content $pf -Raw -ErrorAction Stop | |
| if (Test-Match $c) { Add-Hit 'PSProfile' $pf '(exe referenced in profile)' } | |
| } catch {} | |
| } | |
| } | |
| # --------------------------------------------------------------------------- | |
| # 15. BITS jobs | |
| # --------------------------------------------------------------------------- | |
| try { | |
| Get-BitsTransfer -AllUsers -ErrorAction Stop | ForEach-Object { | |
| $line = "$($_.NotifyCmdLine)" | |
| if (Test-Match $line) { Add-Hit 'BITS' $_.DisplayName $line } | |
| } | |
| } catch {} | |
| # --------------------------------------------------------------------------- | |
| # Output | |
| # --------------------------------------------------------------------------- | |
| if ($results.Count -eq 0) { | |
| Write-Host "[-] No queryable persistence references found for '$ExeName'." -ForegroundColor Yellow | |
| } else { | |
| Write-Host "[+] $($results.Count) reference(s) found:`n" -ForegroundColor Green | |
| $results | Sort-Object Category | Format-Table -AutoSize -Wrap | |
| } | |
| # Uncomment to export: | |
| # $results | Export-Csv ".\persistence_$($ExeName -replace '[^\w]','_').csv" -NoTypeInformation |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment