Persistence Hunter Playbook
Externalized detailed command sequences from the agent definition per the few-shot-examples rule (#1
Persistence Hunter — Detection Playbook
Externalized detailed command sequences from the agent definition per the few-shot-examples rule (#1587, #1600). The agent references this on demand; it is not loaded into the system-prompt budget.
The agent definition keeps the enumerated detection targets, MITRE ATT&CK mappings, and escalation gates inline. This file carries the exact command sequences per OS/runtime.
Linux Persistence
1. Cron Persistence (T1053.003)
# System-wide crontabs
cat /etc/crontab
ls -la /etc/cron.d/
cat /etc/cron.d/*
# Hourly, daily, weekly, monthly
ls -la /etc/cron.hourly/ /etc/cron.daily/ /etc/cron.weekly/ /etc/cron.monthly/
cat /etc/cron.hourly/* /etc/cron.daily/* /etc/cron.weekly/* /etc/cron.monthly/* 2>/dev/null
# Per-user crontabs
ls -la /var/spool/cron/crontabs/ 2>/dev/null
for user in $(cut -d: -f1 /etc/passwd); do
crontab -l -u "$user" 2>/dev/null && echo "--- $user ---"
done
# Recently modified cron files (high-value: modified during suspected intrusion window)
find /etc/cron* /var/spool/cron -newer /etc/passwd -ls 2>/dev/null
Flag any cron entry that contains: `curl`, `wget`, `bash -c`, `python`, `perl`, `nc`, `ncat`, encoded content (base64), or references to /tmp, /dev/shm, or /var/tmp.
2. Systemd Persistence (T1543.002)
# All unit files — look for recently created or modified ones
find /etc/systemd/system/ /lib/systemd/system/ /usr/lib/systemd/system/ \
-name "*.service" -o -name "*.timer" -o -name "*.socket" | \
xargs ls -la 2>/dev/null | sort -k6,7
# Units created recently (modify cutoff to match incident window)
find /etc/systemd/system/ -newer /etc/hostname -ls 2>/dev/null
# Enabled units — these survive reboot
systemctl list-unit-files --state=enabled 2>/dev/null
# Contents of suspicious units
# cat /etc/systemd/system/<suspicious>.service
# Timers (scheduled execution — like cron via systemd)
systemctl list-timers --all 2>/dev/null
find /etc/systemd/system/ -name "*.timer" -ls 2>/dev/null
# User-level systemd units (per-user persistence)
find /home /root -path "*.config/systemd/user*" -name "*.service" -ls 2>/dev/null
A legitimate service unit has a known package origin. An attacker unit often has a plausible name (`update-service.service`, `cachemanager.service`) but executes from an unusual path or runs a shell command.
3. SSH Key Persistence (T1098.004)
# All authorized_keys files across the system
find / -name "authorized_keys" -ls 2>/dev/null
# Contents of every authorized_keys file
find / -name "authorized_keys" -readable 2>/dev/null | while read f; do
echo "=== $f ==="
cat "$f"
echo ""
done
# Recently modified authorized_keys files
find / -name "authorized_keys" -newer /etc/passwd -ls 2>/dev/null
# SSH daemon configuration — check for unexpected directives
cat /etc/ssh/sshd_config | grep -v "^#" | grep -v "^$"
# AuthorizedKeysFile directive — attacker may change this to a controlled path
grep "AuthorizedKeysFile" /etc/ssh/sshd_config
# Root's authorized_keys specifically
cat /root/.ssh/authorized_keys 2>/dev/null
# SSH known_hosts — who has this system connected to
cat /root/.ssh/known_hosts 2>/dev/null
find /home -name "known_hosts" -readable 2>/dev/null | xargs cat
Any key that does not match the system owner's known public keys is suspicious. Document the full key, its from= restriction (if any), and the key comment field — comments sometimes include attacker hostnames or email addresses.
4. LD_PRELOAD / Library Injection (T1574.006)
# /etc/ld.so.preload — global LD_PRELOAD for all processes
cat /etc/ld.so.preload 2>/dev/null && echo "WARNING: ld.so.preload exists"
# LD_PRELOAD in process environments
grep -l LD_PRELOAD /proc/*/environ 2>/dev/null | while read f; do
pid=$(echo "$f" | grep -oP '\d+')
echo "PID $pid: $(cat "$f" 2>/dev/null | tr '\0' '\
' | grep LD_PRELOAD)"
done
# Unexpected files in library directories
find /lib /lib64 /usr/lib /usr/lib64 -name "*.so*" -newer /etc/passwd -ls 2>/dev/null
# Libraries not associated with any package (unregistered shared objects)
find /lib /usr/lib -name "*.so*" | while read f; do
dpkg -S "$f" 2>/dev/null || rpm -qf "$f" 2>/dev/null || echo "UNPACKAGED: $f"
done 2>/dev/null | grep UNPACKAGED
Any entry in `/etc/ld.so.preload` is a critical finding. Legitimate systems rarely use this file. The presence of an unpackaged shared object in a library directory is a strong rootkit indicator.
5. PAM Module Tampering (T1556.003)
# PAM configuration files
ls -la /etc/pam.d/
cat /etc/pam.d/common-auth 2>/dev/null
cat /etc/pam.d/sshd 2>/dev/null
cat /etc/pam.d/sudo 2>/dev/null
# Recently modified PAM config
find /etc/pam.d/ -newer /etc/hostname -ls 2>/dev/null
# Installed PAM modules
find /lib/security/ /lib64/security/ /usr/lib/security/ -name "*.so" -ls 2>/dev/null
# Recently modified PAM modules
find /lib/security/ /lib64/security/ /usr/lib/security/ -newer /etc/passwd -ls 2>/dev/null
# PAM modules not associated with any package
find /lib/security/ /lib64/security/ /usr/lib/security/ -name "*.so" | while read f; do
dpkg -S "$f" 2>/dev/null || rpm -qf "$f" 2>/dev/null || echo "UNPACKAGED: $f"
done 2>/dev/null | grep UNPACKAGED
# Check for pam_exec (executes external commands on auth events)
grep -r "pam_exec" /etc/pam.d/ 2>/dev/null
An unpackaged PAM module is a critical finding. A `pam_exec` directive executing a script is a critical finding. Either warrants immediate escalation.
6. Kernel Module Persistence (T1547.006)
# Currently loaded modules
lsmod
# Modules auto-loaded at boot
cat /etc/modules 2>/dev/null
ls -la /etc/modules-load.d/
cat /etc/modules-load.d/*.conf 2>/dev/null
# Module files on disk — recently modified
find /lib/modules -name "*.ko" -newer /etc/passwd -ls 2>/dev/null
# Module files not associated with kernel package
# (attacker modules are typically not registered)
find /lib/modules -name "*.ko" | while read f; do
dpkg -S "$f" 2>/dev/null || rpm -qf "$f" 2>/dev/null || echo "UNPACKAGED: $f"
done 2>/dev/null | grep UNPACKAGED
# Modprobe blacklisting of security modules (attacker may blacklist audit or IDS modules)
cat /etc/modprobe.d/*.conf 2>/dev/null | grep -i blacklist
An unpackaged `.ko` file is a critical finding. A blacklisted security module (e.g., `auditd`, `apparmor`) is a persistence enabler that warrants immediate investigation.
7. Login Script Injection (T1546.004)
# System-wide login scripts
cat /etc/profile
ls -la /etc/profile.d/
cat /etc/profile.d/*.sh 2>/dev/null
cat /etc/bash.bashrc 2>/dev/null
cat /etc/environment
# Root's personal login scripts
cat /root/.bashrc 2>/dev/null
cat /root/.bash_profile 2>/dev/null
cat /root/.profile 2>/dev/null
cat /root/.bash_logout 2>/dev/null
# All users' login scripts
for home in $(awk -F: '$6 ~ /home|root/ {print $6}' /etc/passwd); do
for script in .bashrc .bash_profile .profile .zshrc .zprofile .bash_logout; do
[ -f "$home/$script" ] && echo "=== $home/$script ===" && cat "$home/$script"
done
done
# Recently modified login scripts
find /etc/profile.d /home /root -name ".*rc" -o -name ".profile" -o \
-name ".bash_*" -newer /etc/passwd 2>/dev/null | xargs ls -la 2>/dev/null
Flag any login script that contains: outbound network calls, base64 encoded commands, references to /tmp or /dev/shm, or additions made during the suspected intrusion window.
8. SUID/SGID Binary Analysis (T1548.001)
# Find all SUID binaries
find / -xdev -perm -4000 -type f 2>/dev/null
# Find all SGID binaries
find / -xdev -perm -2000 -type f 2>/dev/null
# Cross-reference with package manager
dpkg -S /path/to/binary 2>/dev/null || echo "NOT FROM PACKAGE"
rpm -qf /path/to/binary 2>/dev/null || echo "NOT FROM PACKAGE"
Red flags: recently modified SUID binaries, SUID binaries not owned by any package, SUID shells (e.g., `/bin/bash` with SUID set), and SUID copies of standard tools in unexpected locations (e.g., `/tmp/nmap` with SUID). Any SUID binary in `/tmp`, `/dev/shm`, or `/var/tmp` is a critical finding. ATT&CK: T1548.001 (Setuid and Setgid).
Windows Persistence
Windows provides numerous persistence locations spanning the registry, task scheduler, WMI, and service control manager. Attackers commonly layer multiple mechanisms — check all of them regardless of which one you find first.
Registry Run Keys (T1547.001)
# HKLM Run keys (all users, requires admin)
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce"
Get-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunServices" -ErrorAction SilentlyContinue
Get-ItemProperty "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run"
# HKCU Run keys (current user context)
Get-ItemProperty "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
Get-ItemProperty "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce"
Flag any entry pointing to `%TEMP%`, `%APPDATA%`, or `C:\Users\*\AppData\Local\Temp`. Flag encoded PowerShell invocations (`-enc`, `-EncodedCommand`).
Scheduled Tasks (T1053.005)
# List all scheduled tasks with details
schtasks /query /fo LIST /v 2>$null
# PowerShell — filter for non-Microsoft tasks
Get-ScheduledTask | Where-Object { $_.TaskPath -notlike "\Microsoft\*" } |
Select-Object TaskName, TaskPath, State, @{n='Action';e={$_.Actions.Execute}}
# Show task XML for full detail (substitute task name)
Export-ScheduledTask -TaskName "<suspicious task>" | Out-String
Flag tasks with actions invoking `powershell.exe`, `cmd.exe`, `wscript.exe`, `cscript.exe`, or executables in user-writable paths. Flag tasks running as `SYSTEM` created by non-system accounts.
WMI Event Subscriptions (T1546.003)
# Event filters (the trigger condition)
Get-WMIObject -Namespace root\subscription -Class __EventFilter |
Select-Object Name, Query, QueryLanguage
# Event consumers (the action taken)
Get-WMIObject -Namespace root\subscription -Class __EventConsumer
# Bindings (links filters to consumers)
Get-WMIObject -Namespace root\subscription -Class __FilterToConsumerBinding
Any event filter or consumer in `root\subscription` that is not from a known security or management product is a critical finding. WMI subscriptions survive reboots and are invisible to most AV tools.
Non-Microsoft Services (T1543.003)
# List all services with signature verification
Get-Service | Where-Object { $_.Status -eq "Running" } | ForEach-Object {
$svc = $_
$path = (Get-WmiObject Win32_Service | Where-Object { $_.Name -eq $svc.Name }).PathName
$sig = Get-AuthenticodeSignature $path -ErrorAction SilentlyContinue
[PSCustomObject]@{
Name = $svc.Name
Status = $svc.Status
Path = $path
Publisher = $sig.SignerCertificate.Subject
Valid = $sig.Status
}
} | Where-Object { $_.Publisher -notlike "*Microsoft*" -or $_.Valid -ne "Valid" }
Flag services with unsigned or invalidly signed binaries, services pointing to executables in user-writable directories, and services with generic or misspelled names mimicking legitimate services.
Startup Folder (T1547.001)
# Current user startup
explorer shell:startup
# All-users startup
explorer "shell:common startup"
# List contents programmatically
Get-ChildItem "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup"
Get-ChildItem "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup"
Any `.lnk`, `.bat`, `.vbs`, `.js`, or `.exe` in startup folders that is not part of a known installed application warrants investigation.
DLL Hijacking (T1574.001)
DLL hijacking exploits Windows DLL search order — the attacker places a malicious DLL earlier in the search path than the legitimate one.
# Use Process Monitor (Sysinternals) with these filters:
# Operation: Load Image
# Result: NAME NOT FOUND (DLL not found in earlier path locations)
# Path: ends with .dll
# Known-DLLs are immune — check which are registered
reg query "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDLLs"
# Identify writable directories in system PATH
$env:PATH -split ";" | ForEach-Object {
$acl = Get-Acl $_ -ErrorAction SilentlyContinue
if ($acl) { [PSCustomObject]@{ Path=$_; ACL=$acl.AccessToString } }
}
Flag DLLs in user-writable directories that share names with system DLLs. Flag applications loading DLLs from `%TEMP%` or `%APPDATA%`.
COM Hijacking (T1546.015)
# HKCU COM registrations override HKLM — attackers register here without admin rights
Get-ChildItem "HKCU:\SOFTWARE\Classes\CLSID" |
ForEach-Object {
$clsid = $_.PSChildName
$server = Get-ItemProperty "$($_.PSPath)\InprocServer32" -ErrorAction SilentlyContinue
if ($server) {
[PSCustomObject]@{ CLSID=$clsid; Server=$server."(default)" }
}
}
# Cross-reference against HKLM entries (HKCU overrides take precedence)
# Any CLSID in HKCU that also exists in HKLM is a hijack candidate
Flag HKCU `InprocServer32` entries pointing to non-system paths. Flag CLSIDs registered in HKCU that correspond to frequently loaded system COM objects.
macOS Persistence
macOS uses a layered launch system. LaunchAgents run as the user; LaunchDaemons run as root. Both survive reboots and are the primary persistence mechanism for macOS malware.
LaunchAgents (T1543.001)
# Per-user LaunchAgents (run as logged-in user)
ls -la ~/Library/LaunchAgents/
cat ~/Library/LaunchAgents/*.plist 2>/dev/null
# System-wide LaunchAgents (run for every user login)
ls -la /Library/LaunchAgents/
cat /Library/LaunchAgents/*.plist 2>/dev/null
# Recently modified agents
find ~/Library/LaunchAgents /Library/LaunchAgents -newer /etc/hosts -ls 2>/dev/null
Compare every `.plist` program path against known installed applications. Flag agents with `RunAtLoad` set to true and program arguments invoking shells, curl, or python. Flag plists with obfuscated or encoded command strings.
LaunchDaemons (T1543.004)
# System LaunchDaemons (run as root, no user session required)
ls -la /Library/LaunchDaemons/
cat /Library/LaunchDaemons/*.plist 2>/dev/null
# Compare against known Apple daemons (these are in /System/Library/LaunchDaemons/)
ls /System/Library/LaunchDaemons/ > /tmp/apple-daemons.txt
ls /Library/LaunchDaemons/ > /tmp/third-party-daemons.txt
diff /tmp/apple-daemons.txt /tmp/third-party-daemons.txt
# Recently modified daemons
find /Library/LaunchDaemons -newer /etc/hosts -ls 2>/dev/null
Daemons in `/System/Library/LaunchDaemons/` are Apple-owned (protected by SIP). Daemons in `/Library/LaunchDaemons/` are third-party. Any daemon in `/Library/LaunchDaemons/` with a non-vendor origin warrants investigation.
Login Items (T1547.015)
# Query login items via osascript
osascript -e 'tell application "System Events" to get the name of every login item'
osascript -e 'tell application "System Events" to get the path of every login item'
# Login items plist (pre-macOS 13)
cat ~/Library/Preferences/com.apple.loginitems.plist 2>/dev/null
# macOS 13+ Background Task Management
# Check System Settings > General > Login Items for GUI review
Any login item pointing to a path in `/tmp`, `~/Downloads`, or other user-writable locations outside of `/Applications` is suspicious. Cross-reference item paths against known installed software.
Authorization Plugins (T1547.002)
# Authorization plugins execute during authentication events — high-value target
ls -la /Library/Security/SecurityAgentPlugins/
# Review plugin bundles
find /Library/Security/SecurityAgentPlugins -name "*.bundle" -ls 2>/dev/null
# Verify against known Apple plugins (shipped in /System/Library/Security/SecurityAgentPlugins/)
ls /System/Library/Security/SecurityAgentPlugins/
Any plugin in `/Library/Security/SecurityAgentPlugins/` that is not from Apple or a known security vendor (e.g., endpoint agent) is a critical finding. These plugins have access to authentication credentials in cleartext.
Container Persistence
Container environments introduce unique persistence surfaces. Attackers target image layers, orchestrator configurations, and cluster-wide controllers that survive pod restarts and node replacements.
ENTRYPOINT Modification (T1525)
# Compare running container's ENTRYPOINT against the image manifest
docker inspect <container_id> --format '{{.Config.Entrypoint}}'
docker image inspect <image_name> --format '{{.Config.Entrypoint}}'
# Check for CMD overrides in running containers
docker inspect <container_id> --format '{{.Config.Cmd}}'
# Identify containers running unexpected commands
docker ps --no-trunc --format "table {{.Image}}\ {{.Command}}\ {{.Names}}"
Any discrepancy between a running container's command and its image definition indicates runtime injection or a modified image. Flag containers whose `CMD` or `ENTRYPOINT` includes curl, bash -c with encoded content, or references to external IPs.
Kubernetes DaemonSets
DaemonSets run a pod on every node — attackers use them to ensure persistence across all cluster nodes simultaneously.
# List all DaemonSets across all namespaces
kubectl get daemonsets --all-namespaces -o wide
# Inspect DaemonSet spec for unexpected images or commands
kubectl get daemonset <name> -n <namespace> -o yaml
# Compare against expected baseline (version control or CMDB)
kubectl get daemonsets --all-namespaces -o json | \
jq '.items[] | {name: .metadata.name, namespace: .metadata.namespace, image: .spec.template.spec.containers[].image}'
Flag any DaemonSet in non-system namespaces (i.e., not `kube-system`) that was not deployed via your standard CI/CD pipeline. Flag DaemonSets using `hostPID: true`, `hostNetwork: true`, or privileged containers.
Kubernetes CronJobs (T1053.007)
# List all CronJobs across all namespaces
kubectl get cronjobs --all-namespaces -o wide
# Inspect schedule and command for each CronJob
kubectl get cronjob <name> -n <namespace> -o yaml
# Extract all schedules and images for review
kubectl get cronjobs --all-namespaces -o json | \
jq '.items[] | {name: .metadata.name, namespace: .metadata.namespace, schedule: .spec.schedule, image: .spec.jobTemplate.spec.template.spec.containers[].image, command: .spec.jobTemplate.spec.template.spec.containers[].command}'
Flag CronJobs with frequent schedules (e.g., ` *`) that are not part of expected workloads. Flag jobs invoking shells with network callbacks or running privileged containers.
Init Containers
Init containers run before application containers start — attackers use them to stage payloads, modify the filesystem, or establish backdoors before the main container is monitored.
# List all pods with init containers across all namespaces
kubectl get pods --all-namespaces -o json | \
jq '.items[] | select(.spec.initContainers != null) | {name: .metadata.name, namespace: .metadata.namespace, initContainers: [.spec.initContainers[] | {name: .name, image: .image, command: .command}]}'
# Inspect a specific pod's init containers
kubectl get pod <pod_name> -n <namespace> -o jsonpath='{.spec.initContainers}'
Flag init containers using images not from your organization's approved registry. Flag init containers that mount host paths (`hostPath` volumes) or modify files in shared volumes before the main container starts.