Skip to content

Instantly share code, notes, and snippets.

@affix
Created June 10, 2026 18:48
Show Gist options
  • Select an option

  • Save affix/1d1eeacddda88a36de576e04ac24399d to your computer and use it in GitHub Desktop.

Select an option

Save affix/1d1eeacddda88a36de576e04ac24399d to your computer and use it in GitHub Desktop.
Sweeps queryable Windows persistence locations for references to a target executable
<#
.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