TL; DR
Un seul éditeur npm a distribué huit petits paquets dont les noms ressemblent à des éléments de base courants pour le développement de la blockchain et des portefeuilles numériques. base58-utils, abi-encode, eth-dev, arb-kit, layer2-sdk, solana-key-utils, eth-wallet-helpers et crypto-validate-libChacun contient bien un utilitaire fonctionnel. Cependant, un bloc de code auto-invoqué, qui s'exécute dès l'importation du module, est ajouté après cet utilitaire.
Environ 37 secondes après l'importationCe bloc décode une charge utile contenue dans le paquet, dissimulée sous forme de fichier de test, l'écrit dans un fichier caché du répertoire personnel de l'utilisateur et s'enregistre pour redémarrer à chaque exécution. login Ce script fonctionne sous Windows, macOS et Linux et exécute le script décodé en tant que processus indépendant. Aucune action n'est déclenchée lors de l'installation via `npm install` ; le script attend l'importation et l'exécution du code, une opération plus silencieuse que l'installation elle-même.
La charge utile n'est pas opaque. Elle est transmise au format base64 simple, de sorte que le décodage du « jeu de test » permet de récupérer intégralement la deuxième étape : un portefeuille crypto et voleur de secrets qui attend que la machine soit inactive, récupère les clés privées et les phrases d'amorçage, chiffre chaque élément trouvé avec une clé RSA-4096 codée en dur, et l'exfiltre en l'épinglant sur un stockage IPFS public, en renvoyant un signal toutes les 12 heures.
Nous suivons le groupe comme PhantomSyncLes huit paquets étaient tous actifs dans le registre npm au moment de l'analyse, publiés sous un seul compte.
| Écosystème | NPM |
| Forfaits | base58-utils, abi-encode, eth-dev, arb-kit, layer2-sdk, solana-key-utils, eth-wallet-helpers, crypto-validate-lib |
| Plateformes ciblées | Windows, Mac OS, Linux |
| Comportement fondamental | Déploiement différé lors de l'importation, dissimulé comme élément de test ; installe la persistance multiplateforme |
| Charge utile | Voleur de portefeuilles cryptographiques et de secrets, chiffrement RSA-4096, exfiltration via l'épinglage IPFS public |
Anatomie de l'attaque
Chaque paquet semble banal au premier abord. utilitaires de base58Par exemple, il s'agit de quelques kilo-octets de code d'assistance Base58/Bitcoin-WIF sans dépendance — exactement ce que son nom promet. Le code concerné se trouve à l'emplacement suivant : après le module module.exports, là où un lecteur parcourant rapidement le haut du fichier a peu de chances de regarder : une fonction qui s'exécute automatiquement à l'aide d'une minuterie.
La chaîne d'opérations, reconstituée à partir du code source du paquet, se déroule comme suit :
1. Package is required() by the host project 2. A self-invoking function schedules a callback ~37,000 ms later (setTimeout) 3. On fire, the callback reads test/fixtures/keypairs.dat (a base64 blob) 4. It base64-decodes that blob into a Node.js script 5. It writes the script to ~/.cache-db/.node-sync/syncd.js (mode 0o700) 6. It installs login-persistence for that script (see below) 7. It spawns "node syncd.js" as a detached process Deux choix de conception se distinguent.
La charge utile est transportée en tant que dispositif d'essai. La deuxième étape n'est pas écrite sous forme de code évident. Elle réside dans test/fixtures/keypairs.dat, un fichier base64 dont le nom se confond avec celui d'un paquet prétendant gérer des paires de clés. Pour un humain parcourant rapidement l'archive tar, il ressemble à des données d'exemple ; pour le code ajouté, c'est un script à décoder et à exécuter. Le programme d'installation lui-même ne contient aucune adresse réseau — celles-ci se trouvent dans la deuxième étape — mais il n'y a pas d'obfuscation supplémentaire : le fichier de configuration est une simple couche de base64, donc le décoder (base64 -d) récupère la totalité syncd.js et son comportement au sein du réseau. La section suivante décrit le rôle de cette phase de récupération.
La détonation est différée et liée à l'importation, et non à l'installation. Parce que le déclencheur est require () en plus d'un minuteur d'environ 37 secondes au lieu d'un hook d'installation, ce comportement contourne les vérifications qui se contentent de surveiller le npm installer Cette étape, et le délai, persistent bien au-delà de la durée de nombreuses exécutions de courte durée en environnement isolé et en intégration continue. Au moment où une opération s'exécute, l'installation ayant récupéré le paquet est déjà terminée depuis longtemps.
Une fois le script décodé sur disque à ~/.cache-db/.node-sync/syncd.js — un chemin choisi pour ressembler à un répertoire cache classique — le programme d'installation lui permet de survivre aux redémarrages sur les trois principales plateformes :
- linux: une entrée cron qui relance le script. L'entrée est installée en filtrant la crontab existante via grep -v synchronisé, ce qui a pour effet secondaire d'exclure la nouvelle entrée d'une simple liste qui recherche le même nom.
- Windows: une tâche planifiée nommée WinNodeSync, programmé pour se relancer à un intervalle de 12 minutes.
- macOS: un travail de lancement étiqueté com.apple.syncd, présente dans plusieurs paquets — une étiquette qui imite un service système Apple légitime.
Le script est alors immédiatement lancé en tant que processus détaché, et continue donc à s'exécuter après la fermeture du programme d'importation.
Que fait la deuxième étape ?
Comme le dispositif est constitué d'une seule couche base64, la deuxième étape de décodage est propre et peut être lue intégralement. Les huit paquets contiennent l'une des trois variantes du même script, qui s'identifie dans un commentaire d'en-tête comme phantom syncd v3 — haut durmiente (« taupe endormie »). Son rôle est de voler des données de portefeuilles de cryptomonnaies et des secrets de développeurs, puis de les transférer hors du système. Son fonctionnement se déroule en plusieurs étapes :
- 1. Attendez que la machine soit inactive. Avant toute chose, syncd.js vérifie la durée d'inactivité de l'utilisateur et ne passe à l'étape suivante qu'au-delà d'un certain seuil (environ 15 minutes). xprintinactif sous Linux, ioreg Sur macOS, HIDIdleTime est utilisé, et sur Windows, une requête PowerShell permet de récupérer le temps d'inactivité. La collecte a donc généralement lieu lorsque personne n'est devant son clavier.
- 2. Procurez-vous un interrupteur d'activation télécommandé.Le script récupère une petite configuration depuis un dépôt mort avant d'agir. La source principale est une URL brute de gist GitHub (gist.githubusercontent.com/juang55/…/cfg.txt); le script ne s'active que si cette configuration est correcte actif=1Si le gist est indisponible, il utilise trois identifiants de contenu IPFS codés en dur, récupérés via les passerelles publiques. passerelle.piñata.cloud, ipfs.io et cloudflare-ipfs.comCela offre à l'opérateur une commande d'armement/désarmement a posteriori et une solution de repli résistante au démantèlement. (La plus petite variante, livrée en crypto-validate-lib, eth-wallet-helpers et utilitaires de clé Solana, la couche gist a été supprimée et elle repose uniquement sur les identifiants IPFS.)
- 3. Récoltez les informations et les secrets du portefeuille. Le script parcourt le répertoire personnel de l'utilisateur. ~/.config/solana, ~/.ethereum/keystore, ~/.foundry, ~/.hardhat, ~ / .ssh et Bureau/Documentation/Télécharger (y compris les noms de dossiers en espagnol), plus ~/.env et les fichiers rc shell, et l'équivalent AppData Il cible les emplacements sous Windows. Il s'applique aux clés privées Ethereum, aux clés WIF Bitcoin, aux phrases de récupération BIP-39, aux paires de clés Solana, aux fichiers JSON des keystores Ethereum, aux clés SSH et aux variables d'environnement contenant des secrets. La variante plus étendue (dans abi-encode, utilitaires de base58, eth-dev) contient l'intégralité du dictionnaire BIP-39 de 2048 mots et utilise des expressions régulières pour extrait clés individuelles et phrases d'amorçage validées à partir de n'importe quel texte qu'il lit ; les deux variantes plus petites correspondent plutôt aux fichiers par mot-clé (seed, mnémonique, portefeuille, metamask, fantôme, grand livre, Trezor, …) et téléchargez des fichiers entiers.
- 4. Chiffrer et exfiltrer via un service d'épinglage public. Chaque résultat est associé à une empreinte digitale de l'hôte (nom_utilisateur@nom_hôte, plateforme, horodatage) et chiffré avec une clé publique RSA-4096 intégrée au script. L'enregistrement chiffré est ensuite téléchargé en l'épinglant à IPFS via api.pinata.cloud/pinning/pinJSONToIPFSL'authentification se fait à l'aide d'identifiants API Pinata codés en dur. Aucun serveur C2 dédié n'est disponible pour la saisie : les données volées sont stockées dans un espace de stockage public décentralisé et récupérables par l'opérateur grâce aux hachages de contenu obtenus. Les chargements sont espacés de quelques secondes par une gigue aléatoire, et un journal local est enregistré. horodatage | type de clé | hachage renvoyé est maintenu à ~/.cache-db/.node-sync/.sl.
- 5. Persister et émettre un signal toutes les 12 heures. La deuxième étape rétablit sa propre persistance — une entrée cron sous Linux, une com.apple.syncd une tâche launchd sur macOS et une tâche planifiée nommée WindowsNodeSync Sous Windows, la commande est configurée pour se réexécuter toutes les 12 heures. Notez qu'il s'agit d'un différent Nom de la tâche Windows de celle installée par le programme d'installation (WinNodeSync); les deux valent la peine d'être recherchés.
Forum
Les huit paquets ont été publiés en une courte période, les 13 et 14 juillet 2026. Plusieurs comportent plus d'une version ; le programme d'installation est identique d'une version à l'autre, seuls les décalages de ligne varient en fonction de la taille du code utilitaire inoffensif situé au-dessus.
| Date | Espaces |
|---|---|
| 2026-07-13 | Les premiers paquets du cluster apparaissent sous un seul éditeur (solana-key-utils, eth-wallet-helpers, crypto-validate-lib et les premières versions du reste) |
| 2026-07-13 → 07-14 | Les noms restants et les versions suivantes ont été publiés ; le même distributeur ajouté est expédié dans chaque |
| 2026-07-14 | Les huit paquets ont été signalés et analysés ; chaque paquet reste installable depuis le registre. |
Au cours de cette période, la réputation de l'éditeur auprès du registre est passée d'un niveau relativement neutre au début du groupe à un niveau fortement négatif à mesure que les détections s'accumulaient — un effet secondaire observable du signalement des paquets, et non une fonctionnalité prévue.
Indicateurs de compromis
Tous les indicateurs ci-dessous ont été confirmés présents au moment de l'analyse — ceux des tableaux de la « Deuxième étape » par décodage test/fixtures/keypairs.dat et la lecture des données récupérées syncd.js.
Fichiers et chemins
| Indicateur | Rôle |
|---|---|
~/.cache-db/.node-sync/syncd.js | Deuxième étape décodée, écrite en mode 0o700 |
~/.cache-db/.node-sync/.sl | Journal d'exfiltration local (horodatage | type de clé | hachage IPFS) |
test/fixtures/keypairs.dat | Charge utile encodée en Base64 placée à l'intérieur de l'archive tar en tant que « dispositif de test » |
Infrastructure réseau de deuxième niveau (récupérée à partir de syncd.js)
| Indicateur | Rôle |
|---|---|
gist.githubusercontent.com/juang55/b298754cb72942b1cdcf02ccd45cde2f/raw/cfg.txt | Activation par dépôt mort ; le script s'exécute uniquement si la configuration est lue active=1 |
Qmcqz3w8j4qFQXDAXAxnrdc2oSX3nzBT4NqtpTqL8mr1ga | Configuration de repli IPFS (CID) |
QmdTXoqVmTHY1i4ZWLdLkoQ9YChp5TXPh5cWXwnAYZt5iF | Configuration de repli IPFS (CID) |
QmfJkLU5gdCpqbbqEjWYC2anXW9FmuEeSLLeLiHVJKYUjp | Configuration de repli IPFS (CID) |
gateway.pinata.cloud, ipfs.io, cloudflare-ipfs.com | Les passerelles IPFS étaient utilisées pour récupérer la configuration de secours. |
api.pinata.cloud/pinning/pinJSONToIPFS | Point d'exfiltration, données volées épinglées à un IPFS public |
| Clé API Pinata | 13c766575b9270a9825didentifiant d'exfiltration codé en dur |
Cibles de collecte de deuxième étape (récupérées de syndd.js)
| Indicateur | Rôle |
|---|---|
~/.config/solana, ~/.ethereum/keystore, ~/.foundry, ~/.hardhat, ~/.ssh, ~/.env, fichiers rc shell | Répertoires/fichiers analysés pour trouver des clés et des secrets |
AppData\Roaming\Solana, AppData\Local\ethereum\keystore | équivalents Windows recherchés |
| Types d'artefacts récoltés | Clés privées ETH, WIF Bitcoin, phrases de récupération BIP-39, paires de clés Solana, JSON du magasin de clés Ethereum, clés SSH, variables d'environnement secrètes |
Artefacts de persistance
| Plateforme complète | Indicateur |
|---|---|
| Linux | lancement de l'entrée cron syncd.js; installé via crontab filtré par grep -v syncd |
| Fenêtres | tâche planifiée WinNodeSync (compte-gouttes) et WindowsNodeSync (deuxième étape) |
| macOS | label de lancement com.apple.syncd |
| Tous | La deuxième étape se répète selon un cycle de 12 heures. |
Comportementale
- Fonction auto-invoquée ajoutée après module.exports, planifier un setTimeout d'environ 37 000 ms à l'importation.
- enfant_processus faire apparaître("nœud", ) avec l'option détachée activée.
- Activation par inactivité au deuxième stade (xprintinactif / ioreg HIDIdleTime / Temps d'inactivité de PowerShell ; seuil d'environ 15 minutes).
- Chiffrement RSA-4096 avant la détection, puis HTTPS POSTEZ à une API d'épinglage IPFS publique.
Packages et versions (npm, éditeur solbuilder_io)
| Forfait | versions |
|---|---|
base58-utils | 1.0.0, 1.0.1, 1.0.3 |
abi-encode | 1.0.0, 1.0.1, 1.0.2 |
eth-dev | 1.0.0, 1.0.1, 1.0.2 |
arb-kit | 1.0.0, 1.0.1 |
layer2-sdk | 1.0.0, 1.0.1 |
solana-key-utils | 1.0.0 |
eth-wallet-helpers | 1.0.0 |
crypto-validate-lib | 1.0.0 |
Publisher
- solbuilder_io - angel_lopez89[@]proton[.]moiAdresse électronique non vérifiée, aucun compte de contrôle de version vérifié, aucun dépôt lié.
Attribution et comportement observé
Les huit paquets partagent un compte d'éditeur et une charge utile. Chacun est un paquet trivial — quelques kilo-octets de code utilitaire avec le même programme d'installation — publié sans dépôt lié et avec une adresse électronique jetable non vérifiée. Cette uniformité, le chemin de dépôt partagé, les étiquettes de persistance partagées et le partage keypairs.dat Les fichiers intermédiaires sont ce qui assure la cohésion du cluster.
Les emballages font preuve d'une franchise inhabituelle quant à leur propre comportement. utilitaires de clé Solana contient des commentaires en ligne qui décrivent le bloc ajouté en termes simples — l'un d'eux l'étiquette PHANTOM : persistance invisible, un autre (en espagnol) se lit Ejecutar topo en background« Exécuter la taupe en arrière-plan. » Ce sont les annotations du code lui-même sur son fonctionnement ; le nom de la campagne dans cet article est tiré de cette première étiquette ainsi que du faux nœud « démon de synchronisation » (synchronisé) que le mécanisme de persistance imite.
Nous décrivons uniquement le fonctionnement observable du code. Le nom des paquets — tous liés à la crypto, aux portefeuilles et aux outils blockchain — indique le public de développeurs le plus susceptible de les télécharger par leur nom ; il ne permet pas, à lui seul, d'identifier l'éditeur. La seconde étape était entièrement récupérable par décodage de la chaîne de caractères base64, et son comportement est décrit ci-dessus : elle collecte les clés de portefeuille, les phrases de récupération et les secrets, puis les exfiltre, chiffrés RSA, vers un stockage IPFS public via Pinata. Un choix de conception notable est l'absence de serveur C2 privé : la configuration provient d'un gist GitHub et d'IPFS, et les données volées sont stockées dans un stockage public décentralisé dont la clé est basée sur le hachage du contenu, deux éléments plus difficiles à saisir qu'un hôte unique contrôlé par un attaquant. Aucun rapport public antérieur correspondant à ce cluster n'a été trouvé au moment de la rédaction.
Impact, tendances et conseils pour les défenseurs
Qui est exposé ? Toute personne ayant ajouté l'un de ces paquets à un projet Node puis exécuté du code l'important est vulnérable. L'activation se produisant lors de l'importation et non lors de l'installation, la simple présence du paquet ne suffit pas ; toute utilisation normale chargeant le module déclenche l'attaque. Les noms des appâts ciblent les développeurs travaillant sur Ethereum, Solana, Arbitrum, Layer-2 et les outils généraux de portefeuille/encodage — ceux qui sont les plus susceptibles de posséder les actifs ciblés par la seconde étape. Toute personne ayant exécuté l'un de ces modules sur une machine contenant des clés de portefeuille, des phrases de récupération, des keystores, des clés SSH, ou .env Les responsables des comptes secrets doivent considérer ces identifiants comme compromis et les renouveler régulièrement.
Deux modèles à intégrer. Tout d'abord, charge utile en tant qu'installation: envoi de la deuxième étape au format base64 dans un fichier de données au nom plausible (test/fixtures/keypairs.dat) préserve l'apparence propre du code source visible du paquet et dissimule le contenu malveillant dans un fichier que les outils d'analyse et les vérifications humaines traitent souvent comme des données inertes. Deuxièmement, Exécution différée lors de l'importation avec persistance multiplateforme: déplacer le déclencheur hors du processus d'installation, ajouter une minuterie, puis persister à travers cron, les tâches planifiées et launchd est une démarche délibérée pour s'éloigner des techniques de script d'installation plus bruyantes que l'analyse automatisée du registre surveille de près.
Conseils à l'intention des défenseurs et des responsables de la maintenance :
- Traiter le code ajouté après module.exports En priorité, il est important de noter que la logique de gestion des gouttes se cache souvent sous la surface « réelle » du module.
- Ne présumez pas que les fichiers de données sont inertes. Un blob base64 sous test/dispositifs/ qui est lu à l'exécution et décodé est adjacent à l'exécutable ; signaler les lectures à l'exécution des fichiers de test qui alimentent un Fonction/eval/écrire-puis-frayer chaîne.
- Cherchez le chemin de chute ~/.cache-db/.node-sync/ et pour les unités de persistance WinNodeSync (tâche planifiée) et com.apple.syncd (launchd) sur les machines des développeurs qui ont récupéré ces noms.
- Alerte concernant les processus Node qui créent des entrées cron, des tâches planifiées ou des travaux launchd — les bibliothèques légitimes le font rarement lors de l'importation.
- Privilégiez les fichiers de verrouillage et les versions figées, et examinez les différences de toute nouvelle petite dépendance « utilitaire », en particulier packages sans dépendance publiés par des comptes non vérifiés sans dépôt lié.
Pour les défenseurs du registre, la leçon à retenir est que la surveillance des hooks d'installation est nécessaire mais pas suffisante : un dropper à l'importation, retardé par une minuterie et placé dans un fichier de données, passera un contrôle uniquement au moment de l'installation, et l'étape de persistance est souvent le signal observable le plus fort restant à détecter.



