When a server is compromised to act as a “zombie machine” (botnet node), the most common symptom is consistently high CPU usage, followed by abnormal outbound connections. The difficulty in handling this issue lies in the fact that: killing the process alone is ineffective because it will respawn—there is another mechanism launching it.
Therefore, troubleshooting must cover four key areas: Processes, Cron Jobs, Startup Items, and User Accounts.
① Processes
# Sort by CPU usage to identify resource hogs
top
ps aux --sort=-%cpu | head -15
# View full command lines (paths and arguments are critical)
ps auxww | head -20
# Check the executable path of a process
ls -l /proc/PID/exe
cat /proc/PID/cmdline | tr '' ' '; echo
# Check external connections opened by the process
ss -antp | grep PID
lsof -p PID
# Find abnormal established outbound connections
ss -antp state established | awk '{print $5, $7}' | sort | uniq -c | sort -rn | head -20
# Check for listeners on non-standard ports
ss -lntp
Hidden Processes
# Compare PIDs from ps with those in /proc (hidden processes appear in /proc but not in ps)
ls /proc | grep -E '^[0-9]+$' | sort -n > /tmp/proc_pids.txt
ps -e -o pid= | sort -n > /tmp/ps_pids.txt
comm -23 /tmp/proc_pids.txt /tmp/ps_pids.txt
If PIDs are output, hidden processes exist (usually due to replaced system commands or loaded malicious modules). In such cases, a full OS reinstallation is recommended.
② Cron Jobs (Scheduled Tasks)
This is the most common reason for processes respawning after being killed.
# Crontab for current user and root
crontab -l
crontab -u root -l
ls -l /var/spool/cron/ /var/spool/cron/crontabs/
# System-level cron
cat /etc/crontab
ls -l /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/ /etc/cron.weekly/
# Review all cron files
find /etc/cron* /var/spool/cron -type f -exec echo '=== {}' ; -exec cat {} ;
# systemd timers
systemctl list-timers --all
Typical characteristics: A scheduled task that downloads and executes scripts from external sources, or periodically writes to specific files.
# Find tasks involving download and execution
grep -rE 'curl|wget|bash -c|/tmp/' /etc/cron* /var/spool/cron 2>/dev/null
③ Startup Items
# systemd services
systemctl list-unit-files --state=enabled
ls -l /etc/systemd/system/ | head -30
# Traditional rc.local
cat /etc/rc.local
ls -l /etc/init.d/
# Profile scripts (triggered upon login)
cat /etc/profile
ls -l /etc/profile.d/
# Find recently added service units
find /etc/systemd/system /usr/lib/systemd/system -name '*.service' -mtime -30
④ Accounts and Keys
# List users (look for unfamiliar accounts)
cat /etc/passwd | awk -F: '$3 >= 1000 {print $1, $3}'
# Check for newly added sudo privileges
grep -rE 'ALL=(ALL' /etc/sudoers /etc/sudoers.d/ 2>/dev/null
# Login keys (Critical! Attackers often inject their own public keys)
cat ~/.ssh/authorized_keys
ls -l /root/.ssh/ /home/*/.ssh/
# Recent login records
last | head -20
lastb | head -20 # Failed login attempts
authorized_keys is the most critical file to check. After gaining access, attackers often write their own public keys here, allowing them to log in even if passwords are changed.
⑤ Dynamic Library Hijacking
# Check for LD_PRELOAD hijacking
cat /etc/ld.so.preload 2>/dev/null
echo $LD_PRELOAD
# If /etc/ld.so.preload contains content, it points to libraries preloaded into all processes
This type of hijacking infects all commands, including the troubleshooting tools themselves (ps, top may fail to show abnormal processes). In such cases, a full OS reinstallation is recommended.
Remediation Order
① Disconnect network or restrict outbound traffic (prevent further external connections and spawning)
② Clean up cron jobs and startup items ← Do this first, otherwise processes will respawn
③ Terminate abnormal processes
④ Delete malicious files
⑤ Clean up authorized_keys and unauthorized accounts
⑥ Change all credentials
⑦ Patch the entry point used for intrusion
⑦ Re-check the next day
Order matters: Cut off the “spawning mechanism” before killing processes. If done in reverse, processes will restart immediately, making it seem like they cannot be killed.
# Emergency: Temporarily block outbound traffic (example, adjust based on environment)
iptables -A OUTPUT -p tcp --dport 3333 -j DROP
# Stop and disable malicious systemd services
systemctl stop ServiceName
systemctl disable ServiceName
rm -f /etc/systemd/system/ServiceName.service
systemctl daemon-reload
Decision Tree
Abnormal High CPU / Abnormal Outbound Connections
├─ Use top to find high-resource processes
│ ├─ Found and path is suspicious → Continue below
│ └─ Not found (hidden) → Proceed with OS reinstallation
├─ Check cron jobs (crontab / cron.d / systemd timer)
│ └─ If abnormal → Clean up first, then kill processes
├─ Check startup items (systemd / rc.local / profile.d)
├─ Check accounts and authorized_keys
├─ Check /etc/ld.so.preload (for hijacking)
└─ Remediation Order: Block outbound → Remove spawn mechanisms → Kill processes → Delete files
→ Clean keys/accounts → Change credentials → Patch entry point → Re-check next day
Scenario Walkthrough: CPU at 100% Long-term, Process Respawns After Killing
Symptom: A server’s CPU remains at full capacity for an extended period, causing extremely slow business response.
Identifying the highest resource-consuming process and terminating it restores normal performance temporarily, but the CPU hits 100% again within minutes.
- Analyze load composition first to rule out I/O wait masquerading as CPU issues → Wait percentage is low, confirming active computation.
- Gather evidence before taking action → Record command lines, executable locations, and parent processes,
as this information disappears once the process ends. - Analyze process characteristics → Executables located in temporary directories and names mimicking system processes indicate malware, not legitimate services.
- Understand why it respawns → Repeatedly killing the process is ineffective, indicating a persistence mechanism;
find this mechanism instead of continuing to kill the process. - Trace the source via cron jobs → A scheduled task was found downloading and executing code periodically.
- Break the mechanism first, clean the process second, then secure channels → Correct order prevents recurrence.
# Distinguish between true CPU load and I/O wait
top -bn1 | head -5 # Look at %wa
iostat -x 1 3 2>/dev/null | tail -5
# Gather evidence: Command line and executable path
ps -eo pid,ppid,user,%cpu,etime,cmd --sort=-%cpu | head -10
ls -l /proc/SuspiciousPID/exe 2>/dev/null
tr '' ' ' < /proc/SuspiciousPID/cmdline; echo
# Find spawning mechanisms (must be done before killing processes)
crontab -l -u root; ls -la /etc/cron.d/
systemctl list-unit-files --type=service | grep enabled
ls -lt /etc/systemd/system/ | head
Root Cause: A resource-intensive program is automatically spawned and kept alive by a cron job. Killing the process repeatedly is futile because the spawning mechanism persists.
Two Key Takeaways:
- If a process keeps respawning, do not kill it again; investigate the spawning mechanism directly.
The correct order is “cut off the source, then clean the process,” otherwise you are just repeating the same action. - Gather evidence before handling abnormal processes.
Command lines and executable paths are crucial for determining intent; this data is lost once the process terminates.
Locations and Commands for Five Types of Hiding Spots
| Hiding Spot | Where to Look | Commands | Suspicious Characteristics |
|---|---|---|---|
| Processes | Abnormal CPU, disguised process names | top / ps auxf |
Names resembling system processes, paths in tmp |
| Cron Jobs | User crontab and system cron | crontab -l / /etc/cron* |
End-to-end download and execution chains |
| Startup Items | systemd / rc.local | systemctl list-unit-files |
Recently added with unknown origins |
| Accounts & Keys | passwd, sudoers, authorized_keys | last / awk -F: |
Unexpected login accounts, unfamiliar public keys |
| Dynamic Lib Hijacking | LD_PRELOAD, ld.so.preload | cat /etc/ld.so.preload |
File existence is highly suspicious |
An easily overlooked aspect is the order: Check cron jobs before killing processes.
Many mining processes respawn minutes after being killed because a cron job is still configured to launch them.
Cutting off the spawning mechanism first, then cleaning the process, ensures a permanent fix.
# Cron jobs (check every user individually, do not just check root)
crontab -l -u root
ls -la /etc/cron.d/ /etc/cron.daily/ /var/spool/cron/
cut -d: -f1 /etc/passwd | xargs -I{} crontab -l -u {} 2>/dev/null
# Recently added services in startup items
systemctl list-unit-files --type=service | grep enabled
ls -lt /etc/systemd/system/ | head -20
# Dynamic library hijacking (any output indicates a problem)
cat /etc/ld.so.preload 2>/dev/null && echo "!! ld.so.preload exists, verify source"
# Login-capable accounts and recent login records
awk -F: '$3>=1000 && $7 !~ /nologin|false/ {print $1, $3, $7}' /etc/passwd
last -20
The /etc/ld.so.preload item deserves special attention: Libraries specified here are preloaded into all processes.
It is commonly used to hide processes and files (a common rootkit technique).
Normally, this file should not exist. If it contains content, treat it as an intrusion signal even if you don’t understand the contents.
8 Common Pitfalls
- Killing processes without cleaning mechanisms: They restart immediately.
- Incorrect remediation order: Spawning mechanisms should be cut off before killing processes.
- Neglecting authorized_keys: Attackers can still log in.
- Ignoring systemd timers: Checking only crontab leads to missed threats.
- Using infected commands for troubleshooting: Results are unreliable (due to LD_PRELOAD hijacking).
- Failing to change passwords: The entry point remains open.
- Not restricting outbound traffic: External connections and downloads continue.
- Skip re-checking: Cannot confirm if the system is fully cleaned.