kartik0025/linux-persistence-collector
GitHub: kartik0025/linux-persistence-collector
一款轻量级 Linux 持久化痕迹分诊脚本,通过六项可审查的检查规则快速标记系统中可能存在的异常持久化机制。
Stars: 0 | Forks: 0
# linux-persistence-collector
想要在 Linux 上重启后继续存活的恶意软件必须留下一些东西。一个 service 文件,一个 cron
条目,或者动态链接器在启动时读取的配置文件中的一行。这个脚本会检查通常的位置,并
告诉你是否有任何看起来不对劲的地方。
它有两种模式。
**`--baseline`** 将这些位置的当前状态记录到一个 JSON 文件中。在一台你信任的机器上运行它,
保留该文件,之后你就有了一个可以进行对比的东西。脚本本身不会为你进行对比。这是它目前
最明显缺失的功能。
**`--scan`** 应用六项检查并打印出它发现的内容。
需要 Python 3.7 或更高版本。无需安装任何东西。
## 快速开始
```
sudo python3 collector.py --baseline --output baseline.json
sudo python3 collector.py --scan
```
建议使用 root 权限,但不是必需的。如果没有 root 权限,脚本仍然会运行,跳过它无法读取的
部分,并会说明情况。
## 为什么范围这么窄
这最初是作为一个测量工具,用于一项关于 Linux 持久化机制如何在攻击者的清理脚本下存活
的研究项目。该论文目前正在审稿中。
研究工具与安全产品有着不同的职责。它必须在每次运行中以相同的方式检查一个固定的位置
列表,以便结果在不同运行之间保持可比性。它不需要捕捉所有内容。这个工具保持了那种形态。
这种局限性是一个设计决定,而不是未完成的待办事项。
如果你来这里是为了了解 Linux 持久化是什么样的,这可能是一个优势。你可以完整阅读并
提出质疑的六项检查,比一份你只能盲目相信的长列表要有用得多。
## 它检查什么,以及为什么每一项都很重要
每次检查都是一种判断,因此写明了其背后的理由。在相信脚本告诉你的结果之前,请阅读它
并决定你是否同意。
### 1. 不一致的时间戳
每个文件都有一个修改时间 (`mtime`) 和一个更改时间 (`ctime`)。`mtime` 是内容最后
更改的时间。`ctime` 是文件元数据最后更改的时间,其中包括权限、所有权和内容本身。
写入文件会同时更新这两者。
尽管名字相似,但 `ctime` **不是**创建时间。这是一个常见的误读,它有一个真实的来源:
在 Windows 上 `ctime` 确实表示创建时间。Python 的文档明确指出了这种差异,将
`st_ctime` 称为“Unix 上最近一次元数据更改的时间,Windows 上的创建时间”。Linux 确实
记录了创建时间,称为 `btime`,但它隐藏在 `statx()` 系统调用背后,较旧的工具不会显示它。
这项检查之所以有效,是因为一种不对称性:**你无法直接设置 `ctime`。** `touch -t` 和
`utimes()` 系统调用会更改 `atime` 和 `mtime`,并且这样做会作为副作用将 `ctime` 设置
为当前时间。因此,如果攻击者将文件日期倒拨以使其看起来很旧,就会导致 `mtime` 停留在过去,
而 `ctime` 在现在。要避免这种情况,意味着必须更改系统时钟或通过原始设备编辑 inode,
这要求要高得多。
如果 `ctime` 比 `mtime` 晚一个小时以上,脚本就会将其标记出来。普通的系统活动有时会
导致这种情况,因此请将其视为一个值得关注的线索,而不是最终定论。
### 2. 指向虚无的 systemd 链接
当一个服务被启用时,systemd 会在 `.wants/` 目录中创建一个指向该 unit 文件的符号链接。
禁用该服务会移除该链接。如果在禁用服务之前直接删除了 unit 文件,就会留下这个链接,
指向一个不再存在的文件。
那个残留的链接值得注意。它表明这里曾经启用过一个服务,而且有东西在没有通过 systemd 的
情况下移除了 unit 文件。这就是清理脚本在匆忙时所做的事情。
### 3. `/etc/ld.so.preload`
如果此文件存在,系统上每个动态链接的程序都会在加载任何其他内容之前,优先加载其中列出
的库。这包括 `ls`、`ps` 和 `sshd`。
这是一个受支持的功能,具有合法的用途,但它也是最古老的隐藏进程的手法之一,可以躲过你
用来寻找它的工具。大多数系统根本没有这个文件。它的存在值得探究。
### 4. 残留的用户
通常,用户的 systemd 服务会在他们注销时停止。为用户启用“linger”(驻留)会使这些服务
在他们不登录的情况下持续运行,即使重启也不会停止。
对于运行长期后台任务的人来说,这是一个真正的功能。它也是一种以非特权用户身份持久运行
某些东西,而无需触及任何系统级配置的方法。脚本会列出每个启用了该功能的用户,以便你
检查这是否符合预期。
### 5. systemd timers
Timers 是 systemd 用来替代 cron 的机制。它们做着同样的工作,但受到的关注要少得多,部分
原因是大多数人仍然首先想到检查 `crontab`。
**这项检查的实际作用比表面上看起来要少。** 它会标记文件名中包含 `research` 一词的
timer unit,这是最初实验在植入 unit 时使用的标记。没有人会给真正的恶意软件起这个名字,
所以它找不到真正的恶意软件。它保持原样是因为构建此工具的研究依赖于那种确切的行为,
更改它将意味着已发表的方法不再与代码匹配。
诚实的版本应该是一个干净安装系统预期会有的 timers 列表,标记出不在该列表中的任何内容。
这将逻辑从“寻找我植入的东西”反转为“寻找我意料之外的东西”,这才是检测的真正意义。
此功能未在此处实现。
### 6. `/etc/update-motd.d/` 中的脚本
每次有人通过 SSH 交互式登录时,此目录中的文件都会以 root 身份运行,以构建每日消息
banner。它们是普通的 shell 脚本,添加一个脚本是让代码按照你无法控制但可以预测的
计划执行的一种隐蔽方式。
脚本会标记任何以 `99-` 开头的内容,理由是高编号会最后运行,最不容易明显破坏 banner,
此外还有与上述相同的 `research` 标记。这两个测试都很薄弱。几个发行版自带了合法的
高编号脚本,任何读过这个文件的人只需选择一个不同的编号即可。
## 它检查的位置
```
/etc/systemd/system/ /etc/cron.d/ /etc/ld.so.preload
/etc/passwd /var/tmp/ /usr/lib/
/var/lib/systemd/linger/ /etc/profile.d/ /etc/update-motd.d/
```
对于这些路径下的每个文件、目录和符号链接,`--baseline` 会记录其路径、大小、所有者、组、
权限、两个时间戳和类型。易失性数据(`ps auxww` 和 `ss -tulpn`)会最先被捕获,因为
进程和网络状态在你停止观察的瞬间就会消失,而文件元数据则不会。这种顺序遵循
[RFC 3227](https://www.rfc-editor.org/rfc/rfc3227),这是关于按照易失性从高到低收集证据的标准指南。
## 输出
默认情况下,`--scan` 会打印易读的摘要,或者使用 `--format json` 输出 JSON。
```
$ sudo python3 collector.py --scan
[*] Reading file metadata...
2 alert(s)
[ORPHANED_SYMLINK] /etc/systemd/system/multi-user.target.wants/telemetry.service
Points at /etc/systemd/system/telemetry.service, which does not exist.
[LD_PRELOAD] /etc/ld.so.preload
Present. Read what it lists.
```
进度消息会输出到标准错误,而检查结果会输出到标准输出,因此这样操作会生成一个可以真正
解析的文件:
```
sudo python3 collector.py --scan --format json > findings.json
```
`--baseline` 拒绝覆盖现有文件。当你确实需要覆盖时,请传入 `--overwrite`。
## 它做不到的事情
- **这些是启发式方法。** 每个警报都可能有正当的解释。软件包更新会更改时间戳。
管理员会创建 timers。脚本告诉你去哪里看,而不是发生了什么。
- **九个目录并不能代表全貌。** Kernel modules、initramfs、shell 启动文件、PAM、
systemd generators 和 container escapes 都是目前未涵盖的真实持久化位置。
- **它读取的是活动文件系统。** 任何拥有足够权限来安装 rootkit 的程序都有足够的权限
对它撒谎。如果你正在调查真实的事件,请对磁盘进行镜像。
- **仅限带有 systemd 的 Linux。** 不支持 BSD、macOS 或非 systemd 的 init。
- **在受到入侵后获取的基线会使入侵看起来很正常。** 请在一台你有理由信任的机器上
捕获基线。
- **没有任何东西会读回基线。** `--baseline` 写入的文件不会被本仓库中的任何代码
使用。对比两个快照是明显缺失的功能,也是能将此工具从记录器转变为真正工具的功能。
它也没有退出代码,因此目前还不适合在脚本中使用。
## 如果你需要更广泛的工具
使用 [UAC](https://github.com/tclahr/uac)。它能在大多数类 Unix 系统上全面收集 artifacts,正确处理
易失性顺序,并生成完整的证据存档以供离线分析。
它解决了一个比本工具更庞大、不同的问题,并且解决得很好。
这二者之间并非二选一的关系。当你需要所有信息时,请使用 UAC 收集。当你想要快速解答
“这里有没有发生什么变化,有没有看起来奇怪的地方”时,请运行此工具。
## 构建过程
这个脚本的第一个版本是由 LLM 根据我想要的描述生成的。它能运行,产生了看似合理的输出,
我在仔细阅读它之前就将其用于了一个研究项目中。
当我真的去读它时,它有十四个缺陷。核心的时间戳检查从未检查过任何一个文件,因为一个
`isdir()` 守卫跳过了路径列表中的所有文件,并且没有任何递归操作。三个裸露的 `except:`
子句将不相关的失败合并为一条消息,并在过程中吞掉了 Ctrl-C。一个 `--baseline` 标志
接受了一个文件名,然后写入到了完全不同的地方。
这一切都没有阻止它“工作”,这才是值得深思的地方。我记录了所有的发现,以及我会告诉
那些即将信任他们生成的代码的人什么,都在这里:
[从未检查过任何文件的检查](https://kartiksankhla.com/posts/the-check-that-never-checked-a-file/)。
这个仓库中的内容是重写后的版本。上面列出的局限性是在重写中幸存下来的,之所以列出
它们,是因为只存在于你脑海中的局限性是别人无法采取行动的。
## 许可证
MIT。参见 [LICENSE](LICENSE)。
标签:API接口, Homebrew安装, Python, 动态分析, 安全, 持久化检测, 无后门, 超时处理, 逆向工具