怎么确认自己有没有中 AUR 投毒
2026 年 6 月的「Atomic Arch」事件里,1936 个 AUR 包被投毒,而 PKGBUILD 本身看着是干净的。这份自查分两轮:先比包名,再扫 IOC。末尾是 2026-09 在本机复跑的结果。
2026 年 6 月,AUR 上有一批包被投毒,官方名单里是 1936 个包名。
这件事值得单独立一份自查清单,原因载荷:窃取浏览器 Cookie、SSH 密钥、 各类 Token 之外,它在 root 权限下会加载一个 eBPF rootkit 来隐藏自己的进程、文件和网络连接。 按公开分析的说法,那东西卸载软件包去不掉,只能重装系统。
所以自查这件事,做得越早越省事(当然,由于 AUR 投毒事件发生时我没有使用 Arch,而用回 Arch 的时候已经是 7 月了,所以在时间上看起来有些奇怪)。
先决条件
| 需要 | 说明 |
|---|---|
| Arch 系发行版 | CachyOS / EndeavourOS / Manjaro 同样适用 |
能读 `/var/lib/pacman/` |
查已装包与安装脚本,普通用户通常可读 |
| 几分钟 | 第一轮几秒,第二轮主要花在搜文件上 |
两个自查命令:
```bashpacman -Qm # 列出所有非官方仓库的包(也就是 AUR 来的) ls /sys/fs/bpf/ # eBPF 映射,rootkit 藏身的地方之一
```
事件经过
| 日期 | 发生了什么 |
|---|---|
| 6 月 11 日 | 第一波。载荷通过 `npm install atomic-lockfile` 拉取,1500+ 包 |
| 6 月 13 日 | 第二波。改用 `bun install js-digest`,代码混淆规避检测 |
| 6 月 14 日 | 第三波。载荷高度混淆,靠 AI 模型辅助分析才发现 |
| 6 月 15 日 | Arch 暂停 AUR 新用户注册,清理全部恶意包 |
官方仓库没有受影响,被渗透的是 AUR 这个社区仓库。
攻击是怎么做的
```plaintext① 找孤儿包 AUR 允许认领无人维护的包 ↓ ② 认领后改 PKGBUILD 与 .install ↓ ③ 伪造提交元数据,看起来像原维护者 ↓ ④ 载荷不写在 PKGBUILD 里 而是让 PKGBUILD 去 npm / bun 下载
```
第 ④ 步是这件事最难查的地方。你去读 PKGBUILD,上面没有一行恶意代码 —— 它只是调用了一次包管理器,而恶意内容在远端那个 npm 包里。
所以「我每次都读过 PKGBUILD」在这里不构成防线。要防的是另一件事: AUR 包为什么要从 npm / pip / bun 下载东西。
第一轮:比包名
拿到官方名单,和自己的 AUR 包列表做一次全等比对。
```bash# 官方名单:https://md.archlinux.org/s/SxbqukK6IA # 存成 package-list.txt,一行一个包名 pacman -Qm | awk '{print $1}' > /tmp/aur-mine.txt comm -12 <(sort /tmp/aur-mine.txt) <(sort package-list.txt)
```
`comm -12` 输出的是两个文件的交集。有输出就是命中,没有输出就是干净。
用 `comm` 而不是 `grep -f`,是因为它按整行精确比对,不会把 `fcitx5` 匹配到名单里的 `fcitx`。
本机(2026-09-20)的结果:
```bashpacman -Qm | wc -l # → 27
```
27 个 AUR 包逐个比对,0 命中。
第二轮:扫 IOC
包名干净不等于没中 —— 包可能已经删掉,载荷还在。下面每一项都是可以独立跑的。
① eBPF rootkit 的映射
```bashls /sys/fs/bpf/ | grep hidden # → 无输出
```
公开分析里提到的映射名是 `hidden_pids`、`hidden_names`、`hidden_inodes`。
这几个名字本身就是「我要藏东西」的意思,正常系统上不会有。
② 恶意二进制
```bashfind /usr /opt /var/lib /tmp -maxdepth 4 -name deps -type f 2>/dev/null # → 无输出
```
载荷是一个 Rust 写的、名叫 `deps` 的 ELF 文件。
③ npm 缓存里的恶意包
```bashgrep -rl 'atomic-lockfile\|js-digest\|lockfile-js\|nextfile-js' ~/.npm/_cacache/index-v5/ # → 无输出
```
四个恶意 npm 包名:
| 包名 | 版本 | 说明 |
|---|---|---|
`atomic-lockfile` |
1.4.2 | 第一波,载荷文件 `src/hooks/deps` |
`js-digest` |
4.2.2 | 第二波,载荷伪装成 `lib/install-deps.mjs` |
`lockfile-js` |
1.4.2 | 变体 |
`nextfile-js` |
1.4.2 | 变体 |
④ 持久化
```bashls ~/.config/systemd/user/*.service # 逐个看:是不是你自己建的
```
载荷会往 systemd 里塞服务,特征是 `Restart=always` 配 `RestartSec=30`。
```bashgrep -rl 'RestartSec=30' /etc/systemd/system/ # → 无输出
```
⑤ 出站连接
```bashpgrep -a tor # → 无输出
```
C2 走的是 Tor v3 onion:
```plaintextolrh4mibs62l6kkuvvjyc5lrercqg5tz543r4lsw3o6mh5qb7g7sneid.onion
```
同时也往 `temp.sh` 上传。系统上没有 Tor 进程,这条基本可以排除。
⑥ 安装脚本
```bashfind /var/lib/pacman/local/ -name '*.install'
```
本机 1763 个包目录里,`.install` 文件数是 0 —— 这条检查项在本机没有可检查的对象。
写出来是因为它对你的机器可能不是 0。
复跑结果(2026-09-20)
上面两轮在本机重跑了一遍,不是复述七月的结论:
| 检查项 | 结果 |
|---|---|
| AUR 包名比对(27 个 vs 1936 行名单) | 0 命中 |
eBPF `hidden_*` 映射 |
0 处 |
恶意二进制 `deps` |
0 个 |
| npm 缓存中的恶意包 | 0 处 |
| 用户级 systemd 服务 | 3 个,都是自己建的(主题同步、mip-paper、kdeconnect 监听) |
| Tor 进程 | 0 个 |
`/var/lib` 下 `atomic*` 植入物 |
0 个 |
`RestartSec=30` 的系统服务 |
0 个 |
pacman `.install` 脚本 |
全机 0 个 |
三个「看起来像、其实无关」的
自查时容易误报的几处:
| 看着可疑 | 实际情况 |
|---|---|
| linux-cachyos 内核 | CachyOS 内核来自官方仓库。投毒名单里的是 AUR 上的 `linux-cachyos-native`,另一个东西 |
`fcitx` 在名单里 |
那是旧版 `fcitx`。官方仓库的 `fcitx5` 不受影响 |
有 `proper-lockfile` |
合法 npm 包,和恶意的 `lockfile-js` 不是一回事 |
装之前怎么防
第一轮和第二轮都是事后检查,而 eBPF rootkit 那一步是不可逆的。所以在装之前:
- 能用官方仓库就用官方仓库。 AUR 只留给确实没有替代的包,本机 1763 个包里只有 27 个来自 AUR,这个比例是刻意维持的。
- AUR 包主动去 npm / pip / bun 下载依赖的,直接当可疑处理。 这是这次事件的核心手法。
- 警惕「最近换过维护者」的废弃包。 攻击入口就是孤儿包收养。
- 看 `
source` 字段的上游 URL,是不是指向项目的官方地址。 - 别跳过 PKGBUILD 的 diff。 `
yay` 默认会展示,认真读完再回车。
正常打包不会在安装阶段去网上抓代码。这么做既违反打包惯例,也绕开了读 PKGBUILD 这个动作 —— 而那是你手上仅有的审查点。
如果中了
按公开分析的描述,eBPF rootkit 会隐藏自己的进程、文件和套接字,卸载软件包去不掉。
处理顺序:
- 断网,别让它继续外传
- 备份数据,不要备份二进制和配置(`
~/.ssh`、浏览器配置、各类 Token 都算泄露,要轮换而不是备份) - 重装系统
- 重装后轮换所有凭据:SSH 密钥、GitHub Token、各类 API Key、浏览器登录态