关于 analyzing-linux-persistence
这是一份面向 Linux 主机调查与事件响应人员的持久化机制排查指南,涵盖从 systemd、计划任务到内核及容器的多条路径。它强调按系统化顺序全面检查,并结合日志和文件时间线确认攻击者改动。
主要功能
- 01系统化检查 systemd 单元
- 02排查 cron 与 at 任务
- 03检查 Shell、SSH 与自动启动项
- 04审查内核模块及软件包钩子
- 05检查库注入、udev 与 D-Bus
- 06排查容器持久化与取证痕迹
适用场景
- 01调查遭入侵 Linux 主机的访问维持方式
- 02分析在线系统并搜寻后门或未授权服务
- 03验证清除操作已移除持久化机制
- 04通过日志和文件时间线界定改动范围
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ trilwu/ secskills --skill analyzing-linux-persistence安装后在 Claude Code 中输入/analyzing-linux-persistence确认已加载。
详细介绍
分析 Linux 持久化机制
Linux 持久化机制分散在数十个位置,没有任何单一工具能全面检查这些位置。攻击者只需利用一个你漏掉的位置即可。要做的是按序系统性地检查内核和用户空间会处理的每条初始化路径,先检查常见项,再检查少见项。
适用场景
- 调查遭到入侵的 Linux 主机,查找攻击者维持访问权限的方式
- 对 Linux 磁盘映像或运行中的系统进行取证分析
- 查找后门、未经授权的服务或恶意定时任务
- 在 IR 期间确定攻击者修改的完整范围
- 验证清除操作是否移除了所有持久化机制
不适用场景
- 执行完整的事件响应——使用
responding-to-incidents - 以攻击为目的建立持久化机制——使用
establishing-persistence - 主动提升权限——使用
escalating-linux-privileges - 主机是 Windows——使用
investigating-windows-endpoints - 你拿到的是已获取的磁盘映像,而非运行中的主机——使用
analyzing-disk-images - 分析恢复的恶意软件样本——使用
analyzing-malware
Systemd 单元
Systemd 是现代 Linux 上最常见的持久化途径。检查所有搜索路径,而不只是 /etc/systemd/system。
搜索路径(按优先级顺序评估):
/etc/systemd/system/ # admin-installed, highest priority
/run/systemd/system/ # runtime units, survive until reboot
/usr/lib/systemd/system/ # package-installed defaults
/usr/local/lib/systemd/system/
~/.config/systemd/user/ # per-user units (no root needed)
枚举:
# All enabled units -- compare against a known-good baseline
systemctl list-unit-files --state=enabled --type=service
systemctl list-unit-files --state=enabled --type=timer
# Recently modified service files
find /etc/systemd /run/systemd /usr/lib/systemd ~/.config/systemd \
-name '*.service' -o -name '*.timer' -o -name '*.socket' \
2>/dev/null | xargs ls -lt | head -30
# Show the full contents of a suspect unit
systemctl cat suspicious.service
# Check for drop-in overrides that modify legitimate units
find /etc/systemd/system/*.d /run/systemd/system/*.d \
-name '*.conf' 2>/dev/null
# Masked units -- attacker may mask a security service
systemctl list-unit-files --state=masked
# Systemd generators -- scripts that dynamically create units at boot.
# Four directories, in ascending precedence. Checking only the first two
# misses generators dropped in the others.
ls -la /etc/systemd/system-generators/
ls -la /run/systemd/system-generators/ # tmpfs: runtime-injected
ls -la /usr/local/lib/systemd/system-generators/
ls -la /usr/lib/systemd/system-generators/
/run 下的生成器在重启后不会保留,因此严格来说不属于持久化机制,但它是一种可即时执行的手段。重启后的映像中找不到它,并不意味着它之前从未存在过。在运行中的主机上,采集前要记录这一情况。
检查要点: ExecStart 指向 /tmp、/dev/shm 或可写路径。设置了 Restart=always 的单元。时间安排异常的计时器。会更改合法服务 ExecStart 的 drop-in 文件。不属于任何软件包的生成器。
Cron 和 At 任务
# System crontab
cat /etc/crontab
# System cron directories
ls -la /etc/cron.d/
ls -la /etc/cron.daily/ /etc/cron.hourly/ /etc/cron.weekly/ /etc/cron.monthly/
# Per-user crontabs -- enumerate every user
for user in $(cut -d: -f1 /etc/passwd); do
echo "=== $user ==="; crontab -l -u "$user" 2>/dev/null
done
# Anacron
cat /etc/anacrontab
# At queue -- one-shot scheduled commands
atq
for job in $(atq | awk '{print $1}'); do at -c "$job"; done
# Systemd timers (modern cron replacement)
systemctl list-timers --all
# Recently modified cron files
find /etc/cron* /var/spool/cron -type f -newer /etc/hostname 2>/dev/null
检查要点: 追加到现有文件中的条目。以 root 身份从异常路径运行的任务。修改时间与软件包安装日期不符的 Cron 脚本。会下载并执行内容的一次性 at 任务。
Shell 初始化文件
# System-wide
cat /etc/profile
ls -la /etc/profile.d/
cat /etc/bash.bashrc # Debian/Ubuntu
cat /etc/bashrc # RHEL/CentOS
cat /etc/environment
ls -la /etc/environment.d/
# Per-user -- check every user with a login shell
for home in $(awk -F: '$7 !~ /(nologin|false)/ {print $6}' /etc/passwd); do
echo "=== $home ==="
for f in .bashrc .bash_profile .bash_login .profile .zshrc .zprofile \
.zlogin .zshenv .config/fish/config.fish; do
[ -f "$home/$f" ] && echo "--- $f ---" && tail -5 "$home/$f"
done
done
# Check for sourced external files
grep -rn 'source \|^\. ' /etc/profile.d/ /root/.bashrc /root/.profile 2>/dev/null
检查要点: 追加到 rc 文件中、会启动后台进程或修改 PATH 的行。位于异常位置的 source 文件。设置 LD_PRELOAD、LD_LIBRARY_PATH 或将攻击者目录置于 PATH 前面的环境变量。
SSH 持久化
# Authorized keys -- check every user
find / -name authorized_keys -o -name authorized_keys2 2>/dev/null | \
while read f; do echo "=== $f ==="; cat "$f"; done
# Look for command= and from= restrictions (or lack thereof)
grep -n 'command=\|from=\|environment=\|no-pty\|permitopen' \
/root/.ssh/authorized_keys /home/*/.ssh/authorized_keys 2>/dev/null
# SSH daemon configuration
sshd -T 2>/dev/null | grep -iE 'permit|auth|allow|forcecommand|match'
diff /etc/ssh/sshd_config /etc/ssh/sshd_config.dpkg-dist 2>/dev/null
# Config drop-ins
ls -la /etc/ssh/sshd_config.d/ 2>/dev/null
# PAM configuration for SSH
cat /etc/pam.d/sshd
# Look for added auth sufficient lines or custom PAM modules
# PAM module locations are distro-specific. Debian/Ubuntu use a multiarch
# path, so the RHEL-style list alone returns nothing there -- and an empty
# result reads as "no rogue module" when you simply looked in the wrong place.
find /lib/security/ /lib64/security/ /usr/lib/security/ /usr/lib64/security/ \
/lib/*-linux-gnu/security/ /usr/lib/*-linux-gnu/security/ \
-name 'pam_*.so' 2>/dev/null | \
xargs -I{} sh -c 'rpm -qf {} 2>/dev/null || dpkg -S {} 2>/dev/null || echo "UNOWNED: {}"'
# SSH agent hijacking -- check for forwarded agent sockets
find /tmp -name 'agent.*' -type s 2>/dev/null
grep -r SSH_AUTH_SOCK /proc/*/environ 2>/dev/null | tr '\0' '\n'
# SSH certificates
ls -la /etc/ssh/ssh_host_*cert* 2>/dev/null
cat /etc/ssh/sshd_config | grep -i trustedusercakeys
检查要点: 添加到 root 或服务账户中的密钥。执行反向 Shell 或隧道的 command= 条目。违反策略的 PermitRootLogin yes 或 PasswordAuthentication yes。不属于任何软件包的自定义 PAM 模块。用于代理访问的 ForceCommand 或 Match 块。
内核级持久化
# Loaded kernel modules
lsmod
# Details on each module -- look for unsigned or out-of-tree modules
for mod in $(lsmod | awk 'NR>1{print $1}'); do
modinfo "$mod" 2>/dev/null | grep -E 'filename|description|author|sig'
done
# Modules configured to load at boot
cat /etc/modules
ls -la /etc/modules-load.d/
cat /etc/modprobe.d/*
# DKMS-built modules (may rebuild on kernel update)
dkms status 2>/dev/null
# eBPF programs -- increasingly used for rootkits
bpftool prog list 2>/dev/null
bpftool map list 2>/dev/null
# Check for modifications to the kernel command line
cat /proc/cmdline
检查要点: 不属于任何已安装软件包,或从可写目录加载的模块。附加到 kprobe 或跟踪点的 eBPF 程序。内核升级后仍会保留的 DKMS 模块。内核命令行中的 init= 指向异常二进制文件。
软件包管理器钩子
# APT hooks (Debian/Ubuntu)
ls -la /etc/apt/apt.conf.d/
grep -r 'Pre-Invoke\|Post-Invoke\|DPkg::Pre-Install\|DPkg::Post-Install' \
/etc/apt/apt.conf.d/ 2>/dev/null
# dpkg triggers and post-install scripts
ls -la /var/lib/dpkg/info/*.postinst
# Check recently modified postinst scripts
find /var/lib/dpkg/info/ -name '*.postinst' -newer /var/log/dpkg.log \
2>/dev/null
# YUM/DNF plugins (RHEL/CentOS/Fedora)
ls -la /etc/yum/pluginconf.d/ /etc/dnf/plugins/ 2>/dev/null
ls -la /usr/lib/yum-plugins/ /usr/lib/python*/site-packages/dnf-plugins/ \
2>/dev/null
# RPM scriptlets
rpm -qa --scripts 2>/dev/null | grep -B2 -A5 'postinstall\|preinstall'
# Pacman hooks (Arch)
ls -la /etc/pacman.d/hooks/ /usr/share/libalpm/hooks/ 2>/dev/null
检查要点: 执行软件包管理器控制范围之外二进制文件的钩子。安装后被修改的安装后脚本。与任何已安装软件包都不对应的插件。
LD_PRELOAD 和库注入
cat /etc/ld.so.preload
grep -r LD_PRELOAD /etc/environment /etc/profile.d/ /etc/ld.so.conf.d/ \
/etc/systemd/system/*.service 2>/dev/null
# Find libraries not owned by any package
ldconfig -p | awk '{print $NF}' | sort -u | \
xargs -I{} sh -c 'dpkg -S "{}" 2>/dev/null || rpm -qf "{}" 2>/dev/null || echo "UNOWNED: {}"' | \
grep UNOWNED
# Verify shared library integrity against package records
for lib in $(ldd /usr/sbin/sshd | awk '/=>/{print $3}'); do
dpkg -V $(dpkg -S "$lib" 2>/dev/null | cut -d: -f1) 2>/dev/null || \
rpm -V $(rpm -qf "$lib" 2>/dev/null) 2>/dev/null
done
检查要点: /etc/ld.so.preload 中的任何条目(几乎从不合法)。环境文件或 systemd Environment= 指令中的 LD_PRELOAD。不属于任何软件包的库,或校验和与软件包管理器记录不一致的库。
Udev 规则和 D-Bus 激活
# Udev rules -- trigger on hardware events
ls -la /etc/udev/rules.d/
ls -la /usr/lib/udev/rules.d/
grep -r 'RUN+=' /etc/udev/rules.d/ /run/udev/rules.d/ /usr/lib/udev/rules.d/ 2>/dev/null
# D-Bus system services
ls -la /etc/dbus-1/system.d/ /usr/share/dbus-1/system-services/ 2>/dev/null
# Look for services that auto-activate unexpected binaries
grep -r 'Exec=' /usr/share/dbus-1/system-services/ 2>/dev/null
检查要点: 包含 RUN+=、会在设备事件发生时执行脚本的 Udev 规则(插入 USB 是常见触发条件)。会从异常路径激活二进制文件的 D-Bus 服务文件。
容器专属持久化
# Docker -- compare entrypoints against original images
docker inspect --format='{{.Config.Entrypoint}} {{.Config.Cmd}}' \
$(docker ps -aq) 2>/dev/null
docker diff $(docker ps -q) 2>/dev/null
docker images --digests 2>/dev/null
cat /etc/docker/daemon.json 2>/dev/null
# Kubernetes -- mutating webhooks and unexpected sidecars
kubectl get mutatingwebhookconfigurations -o yaml 2>/dev/null
kubectl get pods --all-namespaces -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name}: {range .spec.containers[*]}{.name} {end}{"\n"}{end}' 2>/dev/null
**检查要点:**入口点不同于原始镜像的容器。docker diff 显示有新增的可执行文件。变更准入 Webhook 注入了 Sidecar。没有注册表摘要的镜像(本地构建或遭到篡改)。
XDG 自动启动和桌面条目
# System-wide autostart
ls -la /etc/xdg/autostart/
# Per-user autostart
find /home/*/.config/autostart/ /root/.config/autostart/ \
-name '*.desktop' 2>/dev/null
# Check Exec= lines in desktop files
grep -r 'Exec=' /etc/xdg/autostart/ /home/*/.config/autostart/ 2>/dev/null
**检查要点:**从可写位置执行脚本的桌面条目。为本不应启动桌面会话的服务帐户添加了自动启动条目。
全面排查与取证痕迹
# Timestamp-sorted inventory across all persistence paths
{
find /etc/systemd/system /run/systemd/system -type f 2>/dev/null
find /etc/cron* /var/spool/cron -type f 2>/dev/null
find /etc/profile.d/ -type f 2>/dev/null
find / -name authorized_keys 2>/dev/null
echo /etc/ld.so.preload
find /etc/udev/rules.d/ /etc/xdg/autostart/ /etc/apt/apt.conf.d/ \
-type f 2>/dev/null
} | xargs ls -lt --time-style=full-iso 2>/dev/null | head -60
# Files modified in the last 30 days across persistence paths
find /etc/systemd /etc/cron* /var/spool/cron /etc/profile.d \
/etc/ssh /etc/pam.d /etc/modules-load.d /etc/udev/rules.d \
/etc/xdg/autostart -type f -mtime -30 2>/dev/null | sort
# Files not owned by any package
# Debian/Ubuntu:
find /etc/systemd/system /etc/cron.d /etc/profile.d \
-type f 2>/dev/null | xargs dpkg -S 2>&1 | grep 'not found'
# RHEL/CentOS:
find /etc/systemd/system /etc/cron.d /etc/profile.d \
-type f 2>/dev/null | xargs rpm -qf 2>&1 | grep 'not owned'
# Process tree and network listeners
ps auxwwf
ss -tlnp
收集以下日志,以便关联时间线:/var/log/auth.log(或
/var/log/secure)、/var/log/syslog(或 journalctl 导出内容)、
/var/log/apt/history.log(或 /var/log/yum.log)、/var/log/cron、
/etc/passwd、/etc/shadow、/etc/group。使用 fls 或
find -printf 根据 MAC 时间构建文件系统时间线。
应予驳斥的说辞
- “我们检查了 cron 和 systemd,已经涵盖了所有内容。” 这只是十多种持久化途径中的两种。只有检查完上述所有部分,排查才算完整。
- “文件时间戳看起来很早,所以它是合法的。” 时间戳可以轻易地通过
touch修改。应结合软件包管理器记录和日志条目进行核对,而不能只看mtime。 - “攻击者只有用户级访问权限,所以我们只需检查用户可写的位置。” 用户级持久化包括 systemd 用户单元、crontab、rc 文件、authorized_keys、XDG 自动启动,以及通过环境文件配置的 LD_PRELOAD。
- “我们运行了 AV 扫描,什么也没发现。” AV 能检测已知恶意软件,却检测不出利用合法系统功能的持久化机制。运行
curl | bash的 cron 任务不会触发特征检测。 - “系统已经重建,所以持久化不重要。” 如果不知道攻击者如何实现持久化,就无法确认重建已堵住该途径。
- “我们只需检查 EDR 监控的位置。” EDR 覆盖范围因产品和配置而异。全面排查不依赖特定工具。
参考资料
responding-to-incidents-- 更广泛的 IR 方法论,本分析可为其提供依据establishing-persistence-- 从攻防视角分析相同机制escalating-linux-privileges-- 经常与持久化相结合的权限提升途径engineering-detections-- 为发现的机制构建检测规则hunting-threats-- 主动在整个资产群中搜索持久化