Resumen Ejecutivo
Este artículo detalla nuestro análisis técnico de VVS stealer, también estilizado como VVS stealer, incluyendo el uso de ofuscación y evasión de detección por parte de sus distribuidores.
El stealer está escrito en Python y tiene como objetivo a los usuarios de Discord, exfiltrando información sensible como credenciales y tokens almacenados en cuentas de Discord. Este stealer estuvo en desarrollo activo y se comercializó para su venta en Telegram desde abril de 2025.
El código de VVS stealer está ofuscado mediante Pyarmor. Esta herramienta se utiliza para ofuscar scripts de Python con el fin de dificultar el análisis estático y la detección basada en firmas. Pyarmor puede usarse con fines legítimos y también aprovecharse para crear malware sigiloso.
Los autores de malware están aprovechando cada vez más técnicas de ofuscación avanzadas para evadir la detección de las herramientas de ciberseguridad, haciendo que su software malicioso sea más difícil de analizar y aplicar ingeniería inversa. Este artículo muestra cómo desofuscamos las muestras de VVS stealer para comprender mejor sus operaciones.
Debido a que Python es fácil de usar para los autores de malware y a la compleja ofuscación utilizada por esta amenaza, el resultado es una familia de malware altamente efectiva y sigilosa.
Los clientes de Palo Alto Networks están mejor protegidos a través de los siguientes productos y servicios:
- Advanced WildFire
- Advanced URL Filtering y Advanced DNS Security
- Cortex XDR y XSIAM previenen las amenazas descritas en este artículo empleando el Malware Prevention Engine (Motor de Prevención de Malware).
Si cree que podría haber sido comprometido o tiene un asunto urgente, comuníquese con el equipo de Respuesta a Incidentes de Unit 42.
| Temas relacionados de Unit 42 | Infostealer, Anti-analysis, Discord, Pyarmor |
Introducción
Discord es una plataforma de mensajería social y comunicaciones que se ha convertido en un objetivo popular para el malware, como VVS stealer. VVS stealer está diseñado para robar la información de Discord y los datos del navegador de la víctima.
La Figura 1 muestra las capacidades anunciadas de VVS stealer, que incluyen:
- Robo de datos de Discord (tokens e información de la cuenta)
- Intercepción de sesiones activas de Discord mediante inyección
- Extracción de datos del navegador web (cookies, contraseñas, historial de navegación y detalles de autocompletado)

El stealer también logra persistencia instalándose automáticamente al inicio. Opera sigilosamente mostrando mensajes de error falsos y capturando capturas de pantalla. Para una investigación más profunda sobre la operación, consulte el artículo de DeepCode, Investigating VVS $tealer: A Python-Based Discord Malware.
Análisis Técnico
Esta sección analiza una muestra del malware VVS stealer protegida por Pyarmor con el siguiente hash SHA-256:
- c7e6591e5e021daa30f949a6f6e0699ef2935d2d7c06ea006e3b201c52666e07
La Figura 2 muestra un diagrama resumen que ilustra el flujo de trabajo completo del análisis de la muestra.

Paso Uno: Extracción desde el binario de PyInstaller
La muestra que analizamos se distribuye como un paquete de PyInstaller. PyInstaller es una herramienta que empaqueta una aplicación Python y sus dependencias en un paquete para permitir la ejecución de una aplicación empaquetada sin instalar módulos adicionales.
Cualquier instalación estándar de PyInstaller incluye la utilidad integrada pyi-archive_viewer. Utilizamos esta utilidad para extraer e inspeccionar los siguientes archivos de nuestra muestra:
- El archivo de bytecode de Python llamado vvs
- El archivo de biblioteca de vínculos dinámicos (DLL) del entorno de ejecución de Pyarmor llamado pyarmor_runtime.pyd, ubicado en la subcarpeta pyarmor_runtime_007444
- El archivo __init__.py adjunto dentro de esta misma subcarpeta, que incluye la siguiente información:
- Versión de Pyarmor: 9.1.4 (Pro)
- Número de licencia único: 007444
- Marca de tiempo: 2025-04-27T11:04:52.523525
- Nombre del producto: vvs
- El archivo __init__.py adjunto dentro de esta misma subcarpeta, que incluye la siguiente información:
- Archivo DLL de Python 3.11 llamado python311.dll
- La información de la versión del archivo indica que la versión de Python es 3.11.5
PyInstaller almacena el bytecode de Python (listado como 1.) en su forma cruda (raw). Esta forma cruda se refiere a la secuencia de bytecode que comienza con el valor e3. El valor e3 es una combinación tanto de bandera (flag) como de tipo (type), combinada a través de la constante FLAG_REF.
El tipo representado por el valor e3 se calcula como: type = e3 & ~FLAG_REF. Esto significa que el valor e3 es en realidad el tipo 0x63 (la letra c), también conocido como la constante de enumeración TYPE_CODE. La implementación completa de esta derivación se puede encontrar en el código base de CPython 3.11.
La Figura 3 a continuación muestra que este objeto de código serializado por el módulo marshal está desnudo (bare), le falta un encabezado acompañante de 16 bytes (marcado en azul). Para proporcionar suficiente contexto de Python y que el descompilador no rechace el archivo, necesitamos restaurar al menos uno de los valores del encabezado (el número mágico de Python 3.11.5 en formato little-endian de 4 bytes) antes de la descompilación, porque el descompilador de Python espera un archivo de bytecode de Python válido (.pyc) como entrada.

Comenzamos nuestro análisis descompilando el archivo de bytecode de Python (.pyc) llamado vvs para recuperar su código fuente de Python equivalente (.py).
Paso Dos: Descompilación a código fuente de Python
Pycdc es un descompilador de bytecode de Python escrito en C++. Es parte del proyecto Decompyle++. Admite la descompilación de bytecode de Python 3.11 "de vuelta a código fuente de Python válido y legible por humanos". (Fuente: GitHub). PyLingual es otro descompilador de bytecode de Python.
Después de clonar el repositorio de código y compilar el código base, el ejecutable generado se puede invocar de la siguiente manera para descompilar el bytecode de Python a código fuente de Python a través de Pycdc:
- pycdc.exe -c -v "3.11.5" "vvs.pyc" > "vvs.py"
Esto producirá el código fuente de Python descompilado que se muestra en la Figura 4.

Luego analizamos el último argumento de la función, que se puede extraer a través de ast.NodeVisitor de Python 3.
Paso Tres: Desentrañando la ofuscación de Pyarmor
La carga útil (payload) comienza con el encabezado de Pyarmor que se muestra en la Figura 5.

La criptografía se realiza en todo momento utilizando el algoritmo Estándar de Cifrado Avanzado (AES) con una clave de 128 bits, operando en modo Contador (CTR) con un valor inicial de dos (es decir, AES-128-CTR). La Tabla 1 muestra el desglose de los campos.
| Desplazamientos (Offsets) | Valores | Descripción |
| 0x00 … 0x07 | PY007444 | Firma del archivo que contiene el número de licencia única |
| 0x09 | 03 | Versión mayor de Python |
| 0x0a | 0b | Versión menor de Python |
| 0x14 | 09 | Tipo de protección:
|
| 0x1c … 0x1f | 40 00 00 00 | Inicio del payload ELF, en formato little-endian |
| 0x24 … 0x27 | 12 c9 06 00 | Primeros cuatro bytes del nonce AES-128-CTR |
| 0x2c … 0x33 | dc d2 98 a1 ea 11 fd f4 | Ocho bytes restantes del nonce AES-128-CTR |
| 0x38 … 0x3b | a0 7f 02 00 | Fin del payload ELF, en formato little-endian |
Tabla 1. Desglose de campos presentes en el encabezado de Pyarmor.
Este mismo patrón (resaltado en amarillo) se repite una vez más después del final del payload ELF, para extraer y descifrar el payload de bytecode de Pyarmor.
Modo BCC
El modo BCC (probablemente una abreviatura de ByteCode-to-Compilation) convierte la mayoría de las "funciones y métodos en los scripts a funciones C equivalentes. Esas funciones C se compilarán directamente a instrucciones de máquina, luego serán llamadas por los scripts ofuscados." (Fuente: Documentación de Pyarmor).
El modo BCC se invoca de la siguiente manera: pyarmor gen --enable-bcc script.py.
Estas funciones C convertidas se almacenan en un archivo ELF separado, producido junto con el bytecode serializado (marshaled) por Pyarmor.
El mapeo de constantes de Python a funciones BCC se puede obtener utilizando esta implementación. Por ejemplo, en el método de Python get_encryption_key(browser_path), la constante __pyarmor_bcc_58580__ se asigna a la función BCC bcc_180, cuyo cuerpo de función se encuentra en el desplazamiento 0x4e70 del archivo ELF.
Haciendo referencia a este análisis del contenido del archivo ELF, especialmente la estructura bcc_ftable, la Figura 6 muestra parte de la función BCC bcc_180 descompilada:

Podemos recuperar aproximadamente un equivalente del código original del método de Python get_encryption_key, como se muestra en la Figura 7.

Formato de Bytecode Serializado (Marshaled)
El bytecode serializado de Pyarmor 9 difiere del bytecode estándar de Python 3.11 en varios aspectos. En primer lugar, el bit 0x20000000 se establece en el campo co_flags para indicar que está ofuscado por Pyarmor. En segundo lugar, hay un campo de datos adicional, cuya longitud se denota por el valor de su primer byte.
Además, deopt_code() debe estar deshabilitado para que la secuencia de bytecode se descifre con éxito. Discutiremos los parámetros criptográficos en una sección posterior de este artículo.
Estructura del Objeto de Código
Los objetos de código de Pyarmor están diseñados especialmente, en el sentido de que deben contener ciertos artefactos. Es común esperar encontrar la instrucción LOAD_CONST __pyarmor_enter_*__ en el preámbulo y la instrucción LOAD_CONST __pyarmor_exit_*__ en el cierre del desensamblado. Estas dos instrucciones envolverían el bytecode cifrado, como se muestra en la Tabla 2.
| Operación | Argumento |
| … | |
| LOAD_CONST | __pyarmor_enter_58592__ |
| LOAD_CONST | \x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x20\x16\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 |
| … secuencia de bytecode cifrada (a examinar en la siguiente sección) … | |
| LOAD_CONST | __pyarmor_exit_58593__ |
| LOAD_CONST | \x00\x00\x00\x00\x00\x00\x00\x00\x05\x00\x00\x20\x16\x0b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 |
| … | |
Tabla 2. Instrucciones relacionadas con Pyarmor en el desensamblado de <module>.
Una vez descifrada la secuencia de bytecode cifrada, podría revelar cadenas cifradas o invocaciones de funciones BCC. Las cadenas cifradas (revisadas en una sección posterior de este artículo) están precedidas por una instrucción LOAD_CONST __pyarmor_assert_*__. También existe la instrucción LOAD_CONST __pyarmor_bcc_*__ para invocar una función BCC (revisada anteriormente en este artículo).
Cifrado del Objeto de Código
Las secuencias de bytecode entre el marcador de inicio (__pyarmor_enter_*__) y el marcador final (__pyarmor_exit_*__) están cifradas con AES-128-CTR. La clave AES asociada (273b1b1373cf25e054a61e2cb8a947b8) se extrae de la DLL del entorno de ejecución de Pyarmor vinculada al número de licencia único.
Por otro lado, la clave XOR del nonce AES correspondiente (2db99d18a0763ed70bbd6b3c) solo es específica para el payload de bytecode de Pyarmor, para lo cual hay una implementación de la lógica para extraer este valor. Esta clave se somete a XOR con los 12 bytes en el marcador final (__pyarmor_exit_*__) para producir el nonce AES correcto utilizado en el descifrado.
Cifrado de Cadenas
De manera similar, las constantes de cadena de más de ocho caracteres están cifradas con AES-128-CTR (conocido como "mixto" en la terminología de Pyarmor). La clave AES asociada también es 273b1b1373cf25e054a61e2cb8a947b8, pero esta vez, el nonce AES correspondiente (692e767673e95c45a1e6876d) se calcula desde la DLL del entorno de ejecución de Pyarmor vinculada al número de licencia único.
Además, un valor de prefijo 0x81 denota que la constante de cadena está cifrada. De lo contrario, se utiliza un valor de prefijo 0x01 en su lugar.
Ahora que la protección de Pyarmor está desarmada, procederemos a cubrir algunas de las capacidades clave de VVS stealer en la siguiente sección.
Capacidades del Malware
Con las capas de ofuscación de Pyarmor —incluyendo el modo BCC y el cifrado de cadenas AES-128-CTR— eliminadas con éxito, pudimos exponer la lógica de Python subyacente. Este código desofuscado reveló un stealer diseñado no solo para la exfiltración de datos, sino también para el secuestro activo de sesiones y la persistencia. La siguiente sección detalla las capacidades operativas específicas de VVS stealer que se descubrieron durante este análisis.
La muestra de malware expira después del 2026-10-31 23:59:59. Dejará de funcionar terminándose prematuramente.
La muestra de malware realiza todas las solicitudes HTTP enviando la cadena fija de User-Agent Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36.
Ahora proporcionaremos una descripción general de las principales capacidades del malware, tal como se anuncian en Telegram.
Datos de Discord
La muestra de malware primero busca posibles tokens de Discord cifrados. Los tokens de Discord cifrados son cadenas que comienzan con el prefijo dQw4w9WgXcQ:. La muestra de malware utiliza expresiones regulares para formar un patrón a partir de este prefijo de cadena. Luego utiliza este patrón para buscar dentro del contenido de archivos con las extensiones .ldb o .log, almacenados dentro del directorio LevelDB.
A continuación, la muestra de malware descifra el valor encrypted_key en el archivo Local State, a través de la Interfaz de Programación de Aplicaciones de Protección de Datos (DPAPI). Con este valor encrypted_key descifrado como parámetro de clave AES, la muestra de malware aplica el algoritmo AES, operando en modo Galois/Counter Mode (GCM), sobre los tokens de Discord cifrados para descifrarlos.
Luego, la muestra de malware utiliza los tokens de Discord descifrados para consultar varios puntos finales (endpoints) de la interfaz de programación de aplicaciones (API) de Discord para obtener información del usuario, incluyendo:
- Suscripción Nitro (características Premium de Discord)
- Métodos de pago
- ID de usuario
- Nombre de usuario
- Correo electrónico
- Número de teléfono
- Amigos
- Gremios (Guilds)
- Estado de autenticación multifactor (MFA)
- Configuración regional (Locale)
- Estado de verificación
- Imagen de avatar
- Dirección IP (a través del servicio ipify)
- Nombre de la computadora
Después de recopilar toda esta información, la muestra de malware procede a exfiltrarla en formato de Notación de Objetos de JavaScript (JSON). La exfiltración se realiza a través de solicitudes HTTP POST a los endpoints de webhook predefinidos (variable de entorno %WEBHOOK% y URL de respaldo codificadas).
Los webhooks son "una forma de bajo esfuerzo para publicar mensajes en canales de Discord. No requieren un usuario bot ni autenticación para usarse." (Fuente: Portal de Desarrolladores de Discord).
Inyección en Discord
El código responsable de esta funcionalidad está en la clase Inj, probablemente una abreviatura de Injection (Inyección).
En esta clase, la muestra de malware primero elimina los procesos de la aplicación Discord en ejecución, si hay alguno ejecutándose. Luego descarga el payload de JavaScript (JS) desde un archivo remoto llamado injection-obf.js (el sufijo -obf probablemente significa una versión ofuscada del script), reemplazando la URL del endpoint del webhook y discord_desktop_core, dentro del directorio de la aplicación Discord. Este archivo JS está ofuscado por la Herramienta de Ofuscación de JavaScript y puede ser desofuscado a través del Desofuscador de Obfuscator.io.
Algunas de las funcionalidades principales del código JS inyectado se destacan en las siguientes capturas de pantalla, comenzando con su configuración y fragmentos de código de exfiltración, que se muestran en la Figura 8.

La Figura 8 muestra el código JS inyectado responsable de establecer persistencia en la aplicación Discord, basado en el framework Electron. Este framework utiliza archivos de formato Atom Shell Archive (ASAR) para agrupar todo el código base de la aplicación en un solo archivo, como se muestra en la Figura 9.

La Figura 10 muestra el código JS inyectado responsable de monitorear el tráfico de red a través del Protocolo de DevTools de Chrome (CDP).

La Figura 11 muestra funciones de utilidad de apoyo y hooks de eventos en el código JS inyectado. Los hooks de eventos son funciones de devolución de llamada (callbacks) que se ejecutan cuando el usuario de la aplicación Discord realiza una acción específica. Las acciones de interés son cuando el usuario ve sus códigos de respaldo, cambia su contraseña o agrega un método de pago. Las funciones callback vinculadas a estas acciones son capaces de recopilar la cuenta de usuario de Discord y la información de facturación.

Posteriormente, la muestra de malware reinicia un proceso de aplicación Discord comprometido a través de Update.exe, lo cual hace con el modificador de línea de comandos --processStart.
Datos del Navegador Web
La muestra de malware ataca una lista de aplicaciones de navegador web, incluyendo:
- Chrome
- Edge
- 7Star
- Amigo
- Brave
- CentBrowser
- Discord
- Epic Privacy Browser
- Iridium
- Kometa
- Lightcord
- Mozilla Firefox
- Opera
- Orbitum
- Sputnik
- Torch
- Uran
- Vivaldi
- Yandex
De estos objetivos, la muestra de malware extrae los siguientes datos, donde estén presentes:
- Autocompletado
- Cookies
- Historial
- Contraseñas
Una vez extraídos estos datos, la muestra de malware los prepara para la exfiltración comprimiéndolos en un solo archivo ZIP llamado <USERNAME>_vault.zip. Luego exfiltra este archivo a través de solicitudes HTTP POST a los endpoints de webhook predefinidos, similar al proceso de exfiltración de datos de Discord.
Persistencia en el Inicio
La muestra de malware se copia a sí misma en la carpeta %APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup para lograr persistencia en el inicio. El malware permanece en el dispositivo del usuario, permitiéndole continuar exfiltrando datos si, por ejemplo, el usuario intenta instalar una copia nueva de la aplicación Discord.
Error Falso
La muestra de malware utiliza la API Win32, específicamente la función MessageBoxW en la biblioteca User32.dll, para mostrar un cuadro de mensaje modal sobre un error fatal falso que requiere reiniciar la computadora. Un cuadro de mensaje modal es una pequeña ventana de diálogo que requiere la interacción del usuario antes de que la aplicación pueda continuar, como se muestra en la Figura 12.

Conclusión
VVS stealer demuestra cómo herramientas como Pyarmor, que pueden usarse con fines legítimos, también pueden aprovecharse para crear malware sigiloso destinado a secuestrar credenciales para plataformas populares como Discord. Su aparición señala la necesidad de que los defensores fortalezcan el monitoreo en torno al robo de credenciales y el abuso de cuentas.
Protección y Mitigación de Palo Alto Networks
Los clientes de Palo Alto Networks están mejor protegidos contra las amenazas discutidas anteriormente a través de los siguientes productos:
Los modelos de aprendizaje automático y las técnicas de análisis de Advanced WildFire han sido revisados y actualizados a la luz de los indicadores compartidos en esta investigación.
Advanced URL Filtering y Advanced DNS Security identifican los dominios y URL conocidos asociados con esta actividad como maliciosos.
Cortex XDR y XSIAM previenen las amenazas descritas en este artículo empleando el Malware Prevention Engine. Este enfoque combina varias capas de protección, incluyendo Advanced WildFire, Protección contra Amenazas de Comportamiento (Behavioral Threat Protection) y el módulo de Análisis Local, para evitar que tanto el malware conocido como el desconocido causen daño a los endpoints.
Si cree que puede haber sido comprometido o tiene un asunto urgente, póngase en contacto con el equipo de Respuesta a Incidentes de Unit 42 o llame al:
- Norteamérica: Llamada gratuita: +1 (866) 486-4842 (866.4.UNIT42)
- Reino Unido: +44.20.3743.3660
- Europa y Medio Oriente: +31.20.299.3130
- Asia: +65.6983.8730
- Japón: +81.50.1790.0200
- Australia: +61.2.4062.7950
- India: 000 800 050 45107
- Corea del Sur: +82.080.467.8774
Palo Alto Networks ha compartido estos hallazgos con nuestros miembros compañeros de la Cyber Threat Alliance (CTA). Los miembros de la CTA utilizan esta inteligencia para implementar rápidamente protecciones para sus clientes y para interrumpir sistemáticamente a los actores cibernéticos maliciosos. Obtenga más información sobre la Cyber Threat Alliance.
Indicadores de Compromiso
Hashes SHA-256 de muestras de malware:
- 307d9cefa7a3147eb78c69eded273e47c08df44c2004f839548963268d19dd87
- 7a1554383345f31f3482ba3729c1126af7c1d9376abb07ad3ee189660c166a2b
- c7e6591e5e021daa30f949a6f6e0699ef2935d2d7c06ea006e3b201c52666e07
URLs de webhook de Discord
- hxxps[://]ptb.discord[.]com/api/webhooks/1360401843963826236/TkFvXfHFXrBIKT3EaqekJefvdvt39XTAxeOIWECeSrBbNLKDR5yPcn75uIqKEzdfs9o2
- hxxps[://]ptb.discord[.]com/api/webhooks/1360259628440621087/YCo9eVnIBOYSMn8Xr6zX5C7AJF22z26WljaJk4zr6IiThnUrVyfWCZYs6JjSC12IC8c0
Recursos Adicionales
- Unpacking Pyarmor v8+ scripts – Leonard Rapp y Hendrik Eckardt, G DATA Advanced Analytics GmbH
- Obfuscated Malicious Python Scripts with PyArmor – Xavier Mertens, SANS Internet Storm Center