What You'll Learn
- Explain why attackers require persistence and how it maps to ATT&CK Tactic TA0003
- Identify the six major persistence categories on both Windows and Linux systems
- Detect scheduled task and cron-based persistence using OS-native commands and Velociraptor artifacts
- Recognize malicious services and boot/logon autostart entries on both operating systems
- Distinguish attacker-installed persistence from legitimate system configurations using timestamps, signatures, and package ownership
- Collect persistence evidence with Velociraptor VQL queries across Windows and Linux endpoints
Why Attackers Need Persistence
Initial access is temporary. An attacker who exploits a vulnerability, phishes a credential, or lands a payload through a malicious document has a foothold — but that foothold is fragile. Reboots kill running processes. Logouts terminate user sessions. Patches close the vulnerability that granted entry. Credential resets invalidate stolen passwords. EDR scans may detect and quarantine the initial payload.
Without persistence, every intrusion is a one-shot opportunity. The attacker must re-exploit the target from scratch after every system restart — assuming the vulnerability still exists and defenses have not been updated.
Persistence is the set of techniques an adversary uses to survive system restarts, credential changes, and other interruptions. In the MITRE ATT&CK framework, persistence is catalogued under Tactic TA0003 and contains dozens of techniques spanning operating systems, firmware, and cloud environments.
ATT&CK TA0003 — Persistence contains over 20 technique families and 100+ sub-techniques. This lesson focuses on the six categories most commonly encountered in SOC investigations on Windows and Linux endpoints.
From a defender's perspective, persistence is one of the most valuable evidence sources in an investigation. Initial access artifacts are often overwritten or logged only briefly. Command-and-control traffic is encrypted and ephemeral. But persistence mechanisms — a scheduled task, a new service, a modified registry key, an added SSH key — leave durable, inspectable artifacts on the filesystem and in system configuration databases. If you can find the persistence, you can often reconstruct the entire attack chain backward from that anchor point.
This is why endpoint investigation dedicates significant effort to persistence enumeration. In Lesson 6.3, you used Velociraptor to hunt for process anomalies and network connections. In this lesson, you will learn to systematically hunt for persistence across six categories, on both Windows and Linux, using both native OS commands and Velociraptor artifacts.
Persistence: Windows vs Linux
Attackers use the same conceptual categories on both operating systems, but the specific mechanisms differ significantly. Understanding both sides is essential — modern SOC environments monitor heterogeneous fleets with Windows workstations, Linux servers, and everything in between.
1. Scheduled Execution (T1053)
Scheduled execution lets an attacker run code at defined intervals or on specific triggers — surviving reboots and running without user interaction.
Windows: Task Scheduler (T1053.005)
Windows Task Scheduler stores tasks as XML files under C:\Windows\System32\Tasks\ and in the registry under HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\. Attackers create tasks using schtasks.exe or the COM API.
A suspicious scheduled task might look like:
schtasks /create /tn "SystemHealthCheck" /tr "powershell.exe -ep bypass -w hidden -f C:\ProgramData\health.ps1" /sc minute /mo 5 /ru SYSTEM
This creates a task running every 5 minutes as SYSTEM, executing a hidden PowerShell script from a writable directory. Red flags: execution from ProgramData, Temp, or AppData; hidden window; bypass execution policy; SYSTEM privileges for a task with a generic name.
Detection commands (Windows):
# List all scheduled tasks with details
schtasks /query /fo LIST /v | findstr /i "TaskName\|Task To Run\|Run As User\|Schedule Type"
# Look for tasks executing from suspicious paths
schtasks /query /fo CSV /v | findstr /i "ProgramData\|Temp\|AppData\|tmp"
# Check task XML files for recently modified entries
Get-ChildItem C:\Windows\System32\Tasks -Recurse | Sort-Object LastWriteTime -Descending | Select-Object -First 20
Linux: Cron (T1053.003)
Linux cron stores scheduled jobs in several locations: per-user crontabs (/var/spool/cron/crontabs/), system-wide crontabs (/etc/crontab, /etc/cron.d/), and periodic directories (/etc/cron.hourly/, /etc/cron.daily/, etc.).
A suspicious cron entry:
*/5 * * * * /tmp/.cache/update.sh >/dev/null 2>&1
This runs every 5 minutes, executes a hidden script from /tmp, and suppresses all output. Red flags: hidden directories (dot-prefix), writable world-accessible paths (/tmp, /var/tmp, /dev/shm), output suppression, short intervals.
Detection commands (Linux):
# List all user crontabs
for user in $(cut -f1 -d: /etc/passwd); do echo "=== $user ==="; crontab -u "$user" -l 2>/dev/null; done
# Check system cron directories
ls -la /etc/cron.d/ /etc/cron.hourly/ /etc/cron.daily/ /etc/cron.weekly/ /etc/cron.monthly/
cat /etc/crontab
# Find recently modified cron files
find /etc/cron* /var/spool/cron -type f -mtime -7 -ls 2>/dev/null
2. Services (T1543)
Operating system services run in the background, start automatically on boot, and restart on failure — making them ideal persistence containers.
Windows: Windows Services (T1543.003)
Windows services are defined in the registry under HKLM\SYSTEM\CurrentControlSet\Services\. Attackers create services using sc.exe, PowerShell, or direct registry manipulation.
A suspicious service:
sc.exe create SystemHealthMonitor binPath= "C:\ProgramData\Microsoft\health-svc.exe" start= auto DisplayName= "System Health Monitor"
Red flags: binary in a user-writable path (ProgramData, Temp), generic or imitative name, no description, recently created, unsigned binary.
Detection commands (Windows):
# List services with binary paths
Get-WmiObject Win32_Service | Select-Object Name, DisplayName, PathName, StartMode, State | Format-List
# Find services running from suspicious locations
Get-WmiObject Win32_Service | Where-Object { $_.PathName -match "ProgramData|Temp|AppData|Users" } | Select-Object Name, PathName, State
# Check service creation timestamps via registry
Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Services\*" | Where-Object { $_.ImagePath -and $_.ImagePath -match "ProgramData|Temp" } | Select-Object PSChildName, ImagePath
Linux: Systemd Services (T1543.002)
Systemd unit files live in /etc/systemd/system/, /usr/lib/systemd/system/, and ~/.config/systemd/user/. Attackers create custom unit files that start on boot.
A suspicious systemd service:
# /etc/systemd/system/system-health-monitor.service
[Unit]
Description=System Health Monitor
[Service]
ExecStart=/var/tmp/.fonts/xmr-stak
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target
Red flags: binary in /var/tmp, /tmp, or hidden directories; vague description mimicking system services; Restart=always ensuring the process restarts if killed; no matching package ownership.
Detection commands (Linux):
# List all enabled services
systemctl list-unit-files --state=enabled --type=service
# Find service files not owned by any package (Debian/Ubuntu)
for f in /etc/systemd/system/*.service; do dpkg -S "$f" 2>/dev/null || echo "UNOWNED: $f"; done
# Find service files not owned by any package (RHEL/CentOS)
for f in /etc/systemd/system/*.service; do rpm -qf "$f" 2>/dev/null || echo "UNOWNED: $f"; done
# Check recently modified unit files
find /etc/systemd/system /usr/lib/systemd/system -name "*.service" -mtime -7 -ls
3. Boot/Logon Autostart Execution (T1547)
These mechanisms execute code when the system boots or when a user logs in, without requiring a service or scheduled task.
Windows: Registry Run Keys (T1547.001)
The classic Windows persistence locations:
HKCU\Software\Microsoft\Windows\CurrentVersion\Run
HKCU\Software\Microsoft\Windows\CurrentVersion\RunOnce
HKLM\Software\Microsoft\Windows\CurrentVersion\Run
HKLM\Software\Microsoft\Windows\CurrentVersion\RunOnce
HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders
HKCU keys persist per-user (no admin required). HKLM keys persist system-wide (admin required). Attackers also use the Startup folder: C:\Users\<user>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\.
# Suspicious run key entry
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v "WindowsUpdate" /t REG_SZ /d "rundll32.exe C:\Users\Public\update.dll,DllMain" /f
Red flags: entries pointing to Users\Public, ProgramData, or Temp; use of rundll32, regsvr32, or mshta as loaders; names mimicking legitimate Windows components.
Detection commands (Windows):
# Check all Run keys
Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -ErrorAction SilentlyContinue
Get-ItemProperty "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run" -ErrorAction SilentlyContinue
Get-ItemProperty "HKCU:\Software\Microsoft\Windows\CurrentVersion\RunOnce" -ErrorAction SilentlyContinue
Get-ItemProperty "HKLM:\Software\Microsoft\Windows\CurrentVersion\RunOnce" -ErrorAction SilentlyContinue
# Check Startup folders
Get-ChildItem "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup"
Get-ChildItem "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup"
Linux: Shell Profiles and Init Scripts (T1547.004)
Linux executes profile scripts on login. Attackers append commands to:
/etc/profile,/etc/profile.d/*.sh— all users~/.bashrc,~/.bash_profile,~/.profile— per-user/etc/rc.local— legacy boot script (still works on many distros)
A suspicious shell profile modification:
# Appended to /root/.bashrc
(nohup /tmp/.cache/update.sh &) >/dev/null 2>&1
This launches a background process silently every time root opens a shell. The parentheses create a subshell so the process detaches. nohup ensures it survives if the shell exits.
Detection commands (Linux):
# Check common profile files for suspicious additions
grep -r "nohup\|/tmp/\|/dev/shm\|/var/tmp\|curl\|wget\|base64" /etc/profile /etc/profile.d/ /root/.bashrc /root/.profile /home/*/.bashrc /home/*/.profile 2>/dev/null
# Check rc.local
cat /etc/rc.local 2>/dev/null
# Find recently modified profile files
find /etc/profile.d /root /home -name ".bashrc" -o -name ".profile" -o -name ".bash_profile" | xargs ls -la
4. Remote Access (T1021 / T1133)
Attackers ensure they can regain access through remote access channels, even if their primary payload is detected.
Windows: RDP, WinRM, and Admin Shares
Attackers enable RDP (reg add "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fDenyTSConnections /t REG_DWORD /d 0), enable WinRM (winrm quickconfig -q), or create hidden local admin accounts. They may also create scheduled tasks that open reverse shells via PowerShell.
Detection commands (Windows):
# Check if RDP is enabled
Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server" -Name fDenyTSConnections
# List local administrators
net localgroup administrators
# Check for recently created accounts
Get-LocalUser | Where-Object { $_.Enabled -and $_.PasswordLastSet -gt (Get-Date).AddDays(-30) } | Select-Object Name, Enabled, PasswordLastSet
Linux: SSH Authorized Keys and Reverse Shells
Attackers add their public key to ~/.ssh/authorized_keys on target accounts, granting password-less access. They may also install persistent reverse shells via cron, systemd, or shell profiles.
# Attacker adds their key
echo "ssh-rsa AAAAB3NzaC1yc2EAAAA... attacker@c2" >> /root/.ssh/authorized_keys
Detection commands (Linux):
# List all authorized_keys files
find / -name authorized_keys -type f 2>/dev/null -exec ls -la {} \; -exec cat {} \;
# Check for recently modified SSH configurations
find /etc/ssh /root/.ssh /home/*/.ssh -type f -mtime -7 -ls 2>/dev/null
# Look for active reverse shells in process list
ps auxf | grep -E "bash -i|/dev/tcp|nc -e|ncat|socat"
5. Binary Modification (T1574)
Rather than adding new persistence entries, attackers hijack the execution path of existing, legitimate binaries.
Windows: DLL Hijacking and IFEO (T1574.001 / T1546.012)
DLL search order hijacking places a malicious DLL in a directory that Windows searches before the legitimate DLL location. Image File Execution Options (IFEO) lets an attacker set a "debugger" for any executable — so running notepad.exe actually launches the attacker's payload first.
# IFEO persistence — every time notepad.exe runs, it launches the attacker's binary first
reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\notepad.exe" /v Debugger /t REG_SZ /d "C:\ProgramData\payload.exe"
Detection commands (Windows):
# Check all IFEO entries
Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options" | ForEach-Object { $debugger = (Get-ItemProperty $_.PSPath -Name Debugger -ErrorAction SilentlyContinue).Debugger; if ($debugger) { Write-Output "$($_.PSChildName): $debugger" } }
# Find DLLs in suspicious locations alongside executables
Get-ChildItem "C:\Program Files" -Recurse -Filter "*.dll" | Where-Object { $_.DirectoryName -notmatch "System32|SysWOW64" } | Sort-Object LastWriteTime -Descending | Select-Object -First 20 FullName, LastWriteTime
Linux: LD_PRELOAD and Shared Library Injection (T1574.006)
LD_PRELOAD forces the dynamic linker to load a specified shared library before all others, allowing attackers to hook system calls. This can be set in /etc/ld.so.preload (system-wide) or the environment variable LD_PRELOAD.
# System-wide library injection
echo "/var/tmp/.libs/libhook.so" >> /etc/ld.so.preload
Detection commands (Linux):
# Check ld.so.preload
cat /etc/ld.so.preload 2>/dev/null
# Check environment for LD_PRELOAD
env | grep LD_PRELOAD
grep -r LD_PRELOAD /etc/profile /etc/profile.d/ /etc/environment 2>/dev/null
# Find shared libraries in unusual locations
find /tmp /var/tmp /dev/shm -name "*.so" -type f 2>/dev/null
6. Event-Triggered Execution (T1546)
These mechanisms execute code in response to specific system events rather than on a schedule.
Windows: WMI Event Subscriptions (T1546.003)
WMI event subscriptions are powerful and stealthy. They consist of three components: an event filter (the trigger), an event consumer (the action), and a binding that links them. They survive reboots and are not visible in Task Scheduler or the services list.
# Create WMI persistence — executes payload when any user logs in
$filter = Set-WmiInstance -Class __EventFilter -Namespace "root\subscription" -Arguments @{Name="PersistenceFilter"; EventNamespace="root\cimv2"; QueryLanguage="WQL"; Query="SELECT * FROM __InstanceCreationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_LogonSession'"}
$consumer = Set-WmiInstance -Class CommandLineEventConsumer -Namespace "root\subscription" -Arguments @{Name="PersistenceConsumer"; CommandLineTemplate="C:\ProgramData\beacon.exe"}
Set-WmiInstance -Class __FilterToConsumerBinding -Namespace "root\subscription" -Arguments @{Filter=$filter; Consumer=$consumer}
Detection commands (Windows):
# List all WMI event subscriptions
Get-WmiObject -Namespace "root\subscription" -Class __EventFilter
Get-WmiObject -Namespace "root\subscription" -Class __EventConsumer
Get-WmiObject -Namespace "root\subscription" -Class __FilterToConsumerBinding
Linux: Systemd Timers and at Jobs (T1053.006)
Systemd timers are the modern replacement for cron and offer event-driven scheduling. at jobs schedule one-time executions. Both can serve as persistence mechanisms.
# /etc/systemd/system/health-check.timer
[Unit]
Description=Health Check Timer
[Timer]
OnBootSec=60
OnUnitActiveSec=300
[Install]
WantedBy=timers.target
# at job — runs once at a specific time, but can re-schedule itself
echo "/tmp/.cache/update.sh | at now + 5 minutes" | at now + 5 minutes
Detection commands (Linux):
# List all systemd timers
systemctl list-timers --all
# Check at job queue
atq
for job in $(atq | awk '{print $1}'); do echo "=== Job $job ==="; at -c "$job" | tail -5; done
# Find timer unit files
find /etc/systemd /usr/lib/systemd -name "*.timer" -ls
Detection with Velociraptor
Native OS commands work for manual investigation, but Velociraptor lets you hunt for persistence across hundreds of endpoints simultaneously. Each persistence category has corresponding Velociraptor artifacts.
Windows Persistence Artifacts
Windows.Sys.StartupItems — Collects all autostart entries including Run keys, Startup folders, and shell extensions:
SELECT * FROM Artifact.Windows.Sys.StartupItems()
WHERE NOT Binary =~ "Microsoft|Windows"
Windows.System.TaskScheduler — Enumerates all scheduled tasks with full XML details:
SELECT * FROM Artifact.Windows.System.TaskScheduler()
WHERE Command =~ "ProgramData|Temp|AppData|Users\\Public"
Windows.Sys.Services — Lists all services with binary paths and startup types:
SELECT Name, DisplayName, PathName, StartMode, State
FROM Artifact.Windows.Sys.Services()
WHERE PathName =~ "ProgramData|Temp|AppData"
AND NOT PathName =~ "Microsoft|Windows Defender"
Windows.Persistence.PermanentWMIEvents — Detects WMI event subscriptions:
SELECT * FROM Artifact.Windows.Persistence.PermanentWMIEvents()
Linux Persistence Artifacts
Linux.Sys.Crontab — Collects all cron entries across all users and system directories:
SELECT * FROM Artifact.Linux.Sys.Crontab()
WHERE Command =~ "/tmp/|/var/tmp/|/dev/shm|\\."
Linux.Sys.Services — Enumerates systemd unit files and their configurations:
SELECT * FROM Artifact.Linux.Sys.Services()
WHERE ExecStart =~ "/tmp/|/var/tmp/|/dev/shm"
OR NOT FilePath =~ "/usr/lib/systemd"
Linux.Ssh.AuthorizedKeys — Collects all SSH authorized keys across all user accounts:
SELECT * FROM Artifact.Linux.Ssh.AuthorizedKeys()
Cross-Platform VQL Hunt
To hunt for persistence across your entire fleet regardless of OS, you can build a combined VQL notebook:
-- Suspicious scheduled execution on any OS
LET suspicious_scheduled = SELECT * FROM if(
condition=version().os = "windows",
then={ SELECT * FROM Artifact.Windows.System.TaskScheduler() WHERE Command =~ "ProgramData|Temp|AppData" },
else={ SELECT * FROM Artifact.Linux.Sys.Crontab() WHERE Command =~ "/tmp/|/var/tmp/" }
)
SELECT * FROM suspicious_scheduled
Check file timestamps. One of the simplest ways to spot attacker persistence is comparing file creation or modification timestamps against known-good baselines. A service binary created at 2:37 AM on a Saturday — three hours after the first alert — is almost certainly attacker-placed. Use stat on Linux and Get-Item with .CreationTime on Windows.
Spotting the Fake: Legitimate vs Attacker Persistence
Not every new service or scheduled task is malicious. Operating system updates create services. Application installers add startup items. Sysadmins create cron jobs for legitimate maintenance. The skill is distinguishing attacker persistence from normal system changes.
Windows — Check These:
| Signal | Legitimate | Suspicious |
|---|---|---|
| Binary location | C:\Program Files\, C:\Windows\System32\ | C:\ProgramData\, C:\Users\Public\, Temp\ |
| Digital signature | Signed by Microsoft, Adobe, known vendor | Unsigned, self-signed, or signature mismatch |
| Service description | Detailed, matches installed software | Empty, generic, or copied from another service |
| Creation timestamp | Aligns with software install or Windows Update | Night/weekend, coincides with alert timeline |
| Task XML | Created by known tool (SCCM, GPO) | Manually created, author field empty or suspicious |
# Verify digital signature of a service binary
Get-AuthenticodeSignature "C:\ProgramData\health-svc.exe"
Linux — Check These:
| Signal | Legitimate | Suspicious |
|---|---|---|
| Binary location | /usr/bin, /usr/sbin, /opt | /tmp, /var/tmp, /dev/shm, hidden directories |
| Package ownership | Owned by dpkg/rpm package | dpkg -S returns "not found" |
| File permissions | Standard (755 for bins, 644 for configs) | World-writable, SUID on unusual binaries |
| Creation timestamp | Matches package install date | Post-incident, odd hours |
| Service unit file | Installed by package manager in /usr/lib/systemd | Hand-created in /etc/systemd/system |
# Check if a binary belongs to a package (Debian/Ubuntu)
dpkg -S /var/tmp/.fonts/xmr-stak # "dpkg-query: no path found" = suspicious
# Check file attributes and timestamps
stat /var/tmp/.fonts/xmr-stak
file /var/tmp/.fonts/xmr-stak
Never remove persistence without completing your investigation. Deleting a scheduled task or service before documenting it, collecting the binary, and recording its configuration destroys critical evidence. Always collect first, then contain.
Some persistence survives OS reinstall. UEFI firmware implants (T1542.001), bootkits (T1542.003), and BMC/IPMI backdoors operate below the operating system. If you find evidence of firmware-level persistence, escalate immediately to your incident response team — this is beyond standard SOC remediation.
Key Takeaways
Key Takeaways
- Persistence (TA0003) allows attackers to survive reboots, logouts, and credential resets — without it, every intrusion is temporary
- The six major categories — Scheduled Execution, Services, Boot/Logon Autostart, Remote Access, Binary Modification, and Event-Triggered — each have distinct implementations on Windows and Linux
- Windows persistence lives in the registry, Task Scheduler, WMI, services, and startup folders; Linux persistence lives in cron, systemd, shell profiles, SSH keys, and LD_PRELOAD
- Velociraptor artifacts enable fleet-wide persistence hunting: Windows.Sys.StartupItems, Windows.System.TaskScheduler, Windows.Sys.Services on Windows; Linux.Sys.Crontab, Linux.Sys.Services, Linux.Ssh.AuthorizedKeys on Linux
- Distinguish legitimate persistence from attacker persistence by checking binary location, digital signatures (Windows) or package ownership (Linux), creation timestamps, and service descriptions
- Always collect and document persistence artifacts before removing them — premature remediation destroys evidence
What's Next
In Lesson 6.5 — Endpoint Triage Workflow, you will combine everything from this module — process investigation, network connections, and persistence enumeration — into a complete end-to-end triage pipeline. You will learn the six-stage workflow that takes you from a SIEM alert through intel enrichment, endpoint deep dive, and evidence packaging, culminating in a confident verdict and professional escalation.
Knowledge Check: Persistence Mechanisms
10 questions · 70% to pass
Which ATT&CK tactic covers persistence techniques?
An attacker creates a scheduled task on Windows that runs a PowerShell script from C:\ProgramData every 5 minutes as SYSTEM. Which ATT&CK technique does this map to?
On Linux, which file would you check for system-wide shared library injection via LD_PRELOAD?
Which of the following is the BEST indicator that a Windows service was created by an attacker rather than a legitimate application?
On Linux, what is the MOST reliable way to determine if a systemd service file was installed by the operating system or manually created by an attacker?
Windows WMI event subscriptions are particularly dangerous for persistence because they:
An analyst finds an SSH public key in /root/.ssh/authorized_keys that was added at 3:15 AM on a Saturday — 2 hours after the first alert on this host. No authorized change window was scheduled. What should the analyst conclude?
In Lab 6.3, you find a cron entry running /tmp/.cache/update.sh every 5 minutes. Which persistence category is this?
In the lab, the service system-health-monitor.service executes /var/tmp/.fonts/xmr-stak. Which persistence category is this?
In Lab 6.3, you find (nohup /tmp/.cache/update.sh &) appended to /root/.bashrc. Which persistence category is this?
0/10 answered