Autoruns and OS persistence hunting¶
Systematically enumerating all persistence mechanisms on Windows and Linux endpoints to identify implanted entries.
Windows: Sysinternals Autoruns¶
Autoruns is the most comprehensive Windows autorun enumerator. It covers scheduled tasks, services, registry run keys, startup folders, browser extensions, drivers, codecs, and more.
# run as administrator for full visibility
# from command line (headless, outputs to CSV)
autorunsc.exe -accepteula -a * -c -h -s > autoruns-output.csv
# flags:
# -a * all entry types
# -c CSV output
# -h show file hashes (SHA-256)
# -s check VirusTotal (requires internet)
Review the output for:
Entries with no publisher signature (
(not verified))Entries pointing to unusual paths (temp directories, user profiles, AppData)
Entries with base64-encoded command lines
Entries whose hash appears on VirusTotal
Compare against a known-good baseline from a clean system image. New entries since the last known-good state are the highest priority.
# PowerShell: compare two Autoruns CSV exports
$baseline = Import-Csv baseline-autoruns.csv
$current = Import-Csv current-autoruns.csv
# entries in current that were not in the baseline
$current | Where-Object {
$entry = $_
-not ($baseline | Where-Object { $_.ImagePath -eq $entry.ImagePath })
} | Select-Object 'Entry Location', 'Entry', 'Image Path', 'Launch String'
Windows: scheduled tasks¶
# all enabled tasks with their execute actions and authors
Get-ScheduledTask | Where-Object { $_.State -ne 'Disabled' } |
ForEach-Object {
$t = $_
$actions = $t.Actions | ForEach-Object { "$($_.Execute) $($_.Arguments)" }
[PSCustomObject]@{
Path = $t.TaskPath + $t.TaskName
Actions = $actions -join '; '
Author = $t.Author
RunAs = ($t.Principal | Select-Object -ExpandProperty UserId)
}
} | Where-Object {
# flag tasks with PowerShell, encoded content, or unusual run-as accounts
$_.Actions -match 'powershell|cmd|wscript|cscript|mshta|regsvr32' -or
$_.Actions -match '-enc|-encoded|-e ' -or
$_.Actions -match 'Temp|AppData|Users\\[^\\]+\\' -or
($_.RunAs -eq 'SYSTEM' -and $_.Author -notmatch 'Microsoft|Windows')
} | Format-List
# check task XML for hidden flags
Get-ScheduledTask | ForEach-Object {
$xml = [xml](Export-ScheduledTask -TaskName $_.TaskName -TaskPath $_.TaskPath)
if ($xml.Task.Settings.Hidden -eq 'true') {
Write-Output "HIDDEN TASK: $($_.TaskPath)$($_.TaskName)"
}
}
Windows: WMI subscriptions¶
# enumerate all three required objects
Write-Output "=== Event Filters ==="
Get-WMIObject -Namespace root\subscription -Class __EventFilter |
Select-Object Name, Query | Format-Table -AutoSize
Write-Output "=== CommandLine Consumers ==="
Get-WMIObject -Namespace root\subscription -Class CommandLineEventConsumer |
Select-Object Name, CommandLineTemplate | Format-Table -AutoSize
Write-Output "=== Script Consumers ==="
Get-WMIObject -Namespace root\subscription -Class ActiveScriptEventConsumer |
Select-Object Name, ScriptText | Format-Table -AutoSize
Write-Output "=== Bindings ==="
Get-WMIObject -Namespace root\subscription -Class __FilterToConsumerBinding |
Select-Object Filter, Consumer | Format-Table -AutoSize
Any WMI subscription not associated with a known endpoint management product (SCCM, endpoint protection) should be treated as suspicious. Most systems have zero legitimate WMI subscriptions.
Windows: services¶
# services with automatic start and non-standard binary paths
Get-WmiObject Win32_Service |
Where-Object { $_.StartMode -eq 'Auto' -and $_.State -eq 'Running' } |
Where-Object {
$_.PathName -notmatch '^"?C:\\Windows\\' -and
$_.PathName -notmatch '^"?C:\\Program Files'
} |
Select-Object Name, DisplayName, PathName, StartName | Format-List
Linux: cron¶
# check all cron locations for all users
for user in $(cut -f1 -d: /etc/passwd); do
crontab -l -u "$user" 2>/dev/null | grep -v '^#' |
awk -v u="$user" 'NF {print u": "$0}'
done
cat /etc/crontab
for f in /etc/cron.d/*; do
echo "=== $f ==="; grep -v '^#' "$f" | grep -v '^$'
done
Look for:
curl | bashorwget | shpatternsReferences to unusual paths (user home directories in system cron)
Scripts placed in directories consistent with attacker naming (see checklist)
Linux: systemd¶
# all enabled services and timers
systemctl list-units --type=service --state=enabled --no-pager
systemctl list-timers --all --no-pager
# check unit files for suspicious content
for f in /etc/systemd/system/*.service; do
if grep -qE 'curl|wget|bash.*http|python.*-c' "$f" 2>/dev/null; then
echo "SUSPICIOUS: $f"
cat "$f"
fi
done
# show recently modified unit files (last 30 days)
find /etc/systemd/system /lib/systemd/system -name '*.service' -newer /etc/passwd -ls
Linux: SSH keys¶
# check authorized_keys for all users with home directories
while IFS=: read -r user _ uid _ _ homedir _; do
[[ $uid -ge 1000 || $user == "root" ]] || continue
keyfile="$homedir/.ssh/authorized_keys"
if [[ -f "$keyfile" ]]; then
echo "=== $user ($keyfile) ==="
cat "$keyfile"
fi
done < /etc/passwd
Compare every key against the organisation’s authorised key inventory. Any unrecognised key is a finding.
Velociraptor (at scale)¶
For fleet-wide hunting, Velociraptor provides artefact-based collection:
# collect Windows.Sysinternals.Autoruns across all endpoints
# from Velociraptor server UI or using velociraptor query:
SELECT * FROM Artifact.Windows.Sysinternals.Autoruns()
WHERE NOT Signer =~ 'Microsoft'
AND NOT Signer =~ 'Windows'
AND Enabled = 'true'
# hunt for WMI subscriptions fleet-wide
SELECT * FROM Artifact.Windows.Persistence.PermanentWMIEvents()
Results from multiple endpoints can be correlated: an identical WMI subscription name appearing on many endpoints is likely a managed entry; one appearing on a single endpoint is suspicious.