PhantomSync: pacchetti crittografici npm per nascondere il ladro di portafogli

PhantomSync: otto pacchetti npm per sviluppatori di criptovalute nascondono un dropper ritardato e auto-persistente

TL; DR

Un singolo editore npm ha distribuito otto piccoli pacchetti i cui nomi sembrano elementi costitutivi di uso quotidiano per lo sviluppo di blockchain e wallet, base58-utils, abi-encode, eth-dev, arb-kit, layer2-sdk, solana-key-utils, eth-wallet-helperse crypto-validate-libCiascuno di essi contiene un'utilità funzionante. Tuttavia, dopo tale utilità, viene aggiunto un blocco di codice auto-invocante che viene eseguito nel momento in cui il modulo viene importato.

Circa 37 secondi dopo l'importazione, quel blocco decodifica un payload che viene spedito all'interno del pacchetto camuffato da test fixture, lo scrive in un file nascosto nella directory home dell'utente, si registra per riavviarsi ad ogni login Funziona su Windows, macOS e Linux e avvia lo script decodificato come processo separato. Nulla di tutto ciò si attiva durante l'installazione di npm; attende che il codice venga importato ed eseguito, un momento decisamente più tranquillo rispetto all'installazione vera e propria.

Il payload non è opaco. Viene spedito come base64 semplice, quindi decodificando il "test fixture" si recupera completamente il secondo stadio: un ladro di portafogli di criptovalute e segreti che attende che la macchina rimanga inattiva, raccoglie le chiavi private e le frasi di recupero, crittografa ogni dato trovato con una chiave RSA-4096 predefinita e lo esfiltra fissandolo a un archivio IPFS pubblico, inviando un segnale di ritorno ogni 12 ore.

Monitoriamo il cluster come PhantomSyncTutti e otto i pacchetti erano attivi nel registro npm al momento dell'analisi, pubblicati con un unico account.

Ecosistemanpm
personalizzatibase58-utils, abi-encode, eth-dev, arb-kit, layer2-sdk, solana-key-utils, eth-wallet-helpers, crypto-validate-lib
Piattaforme di destinazioneWindows, Mac OS, Linux
Comportamento fondamentaleDropper ritardato, in fase di importazione, nascosto come fixture di test; installa la persistenza multipiattaforma
Carico utileFurto di portafogli di criptovalute e segreti, crittografia RSA-4096, esfiltrazione tramite pinning IPFS pubblico

Attacco anatomico

A una prima occhiata, ogni confezione sembra insignificante. base58-utils, ad esempio, è un codice helper Base58 / Bitcoin-WIF di pochi kilobyte senza dipendenze, esattamente ciò che promette il nome. Il codice rilevante si trova dopo il modulo modulo.esportazioni, dove è improbabile che un lettore che scorre velocemente l'inizio del file guardi: una funzione auto-invocante che si programma con un timer.

La sequenza delle operazioni, ricostruita a partire dal codice sorgente del pacchetto, si svolge in questo modo:

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

Due scelte di design spiccano in particolare.

Il carico utile viaggia come dispositivo di prova. La seconda fase non è scritta come codice ovvio. Vive in test/fixtures/keypairs.dat, un file base64 il cui nome si mimetizza in un pacchetto che afferma di gestire coppie di chiavi. Per un essere umano che scorre velocemente il tarball, sembra un dato di esempio; per il codice allegato, è uno script da decodificare ed eseguire. Il dropper stesso non contiene alcun indirizzo di rete - quelli risiedono nella seconda fase - ma non c'è ulteriore offuscamento: il fixture è un singolo strato di base64, quindi decodificandolo (base64 -d) recupera il completo syncd.js e il suo comportamento in rete. La sezione successiva illustra nel dettaglio cosa fa quella fase di recupero.

La detonazione è ritardata ed è legata all'importazione, non all'installazione. Poiché il grilletto è richiedere () più un timer di ~37 secondi invece di un hook di installazione, il comportamento aggira i controlli che guardano solo il installazione di npm Questo passaggio, e il ritardo dura più a lungo di molte esecuzioni di breve durata in ambienti sandbox e CI. Quando finalmente qualcosa viene eseguito, l'installazione che ha scaricato il pacchetto è già terminata da un pezzo.

Una volta che lo script decodificato è sul disco ~/.cache-db/.node-sync/syncd.js — un percorso scelto per essere letto come una normale directory di cache — il dropper fa in modo che sopravviva ai riavvii su tutte e tre le principali piattaforme:

  • Linux: una voce cron che riavvia lo script. La voce viene installata filtrando il crontab esistente tramite grep -v syncd, il che ha come effetto collaterale quello di escludere la nuova voce da un semplice elenco che effettua una ricerca con lo stesso nome.
  • Windows: un'attività pianificata denominata WinNodeSync, impostato per essere eseguito nuovamente a intervalli di 12 minuti.
  • Mac OS: un lavoro lanciato etichettato com.apple.syncd, presente in diverse confezioni — un'etichetta che imita un servizio di sistema Apple legittimo.

Lo script viene quindi avviato immediatamente come processo separato, in modo da continuare a essere eseguito anche dopo la chiusura del programma che lo ha importato.

Cosa fa la seconda fase

Poiché il fixture è un singolo livello base64, il secondo stadio si decodifica correttamente e può essere letto per intero. Tutti gli otto pacchetti contengono una delle tre varianti dello stesso script, che si identifica in un commento di intestazione come phantom syncd v3 — topo durmiente (una talpa dormiente) Il suo compito è rubare materiale relativo ai portafogli di criptovalute e segreti degli sviluppatori per poi trasferirli fuori dal sistema. Il processo si articola in questi passaggi:

  • 1. Attendi che la macchina sia inattiva. Prima di fare qualsiasi cosa, syncd.js controlla da quanto tempo l'utente è inattivo e procede solo dopo aver superato una determinata soglia (circa 15 minuti) — xstampante su Linux, ioreg HIDIdleTime su macOS e una query PowerShell per il tempo di inattività su Windows. La raccolta dei dati tende quindi ad avvenire quando nessuno sta utilizzando la tastiera.
  • 2. Recuperare un interruttore di attivazione controllato a distanza. Lo script preleva una piccola configurazione da un deaddrop prima di agire. La fonte primaria è un URL raw di un gist di GitHub (gist.githubusercontent.com/juang55/…/cfg.txt); lo script si attiva solo se la configurazione è configurata attivo=1Se il gist non è disponibile, si ricorre a tre identificatori di contenuto IPFS predefiniti, recuperati tramite i gateway pubblici gateway.pinata.cloud, ipfs.ioe cloudflare-ipfs.com. Ciò fornisce all'operatore un controllo di attivazione/disattivazione a posteriori e un meccanismo di sicurezza resistente allo smontaggio. (La variante più piccola, spedita in libreria di convalida crittografica, eth-wallet-helperse solana-key-utils(ha rimosso il livello gist e si basa esclusivamente sugli identificatori IPFS.)
  • 3. Raccogli materiale e segreti del portafoglio. Lo script percorre la directory home dell'utente — ~/.config/solana, ~/.ethereum/keystore, ~/.foundry, ~/.hardhat, ~/.sshe Desktop/Documenti/Download (compresi i nomi delle cartelle in lingua spagnola), più ~/.env e file shell rc e l'equivalente AppData posizioni su Windows. Prende di mira le chiavi private di Ethereum, le chiavi WIF di Bitcoin, le frasi di recupero BIP-39, le coppie di chiavi Solana, il JSON del keystore di Ethereum, le chiavi SSH e le variabili d'ambiente contenenti segreti. La variante più grande (in abi-encode, base58-utils, eth-dev) contiene il dizionario BIP-39 completo di 2048 parole e utilizza espressioni regolari per estratto chiavi individuali e frasi seme validate da qualsiasi testo che legge; le due varianti più piccole invece corrispondono ai file tramite parola chiave (seme, mnemonico, portafoglio, metamask, fantasma, libro mastro, Trezor, …) e caricare file interi.
  • 4. Crittografare ed esfiltrare tramite un servizio di pinning pubblico. Ogni risultato è corredato da un'impronta digitale dell'ospite (nomeutente@nomehost, piattaforma, timestamp) e crittografato con una chiave pubblica RSA-4096 hardcoded incorporata nello script. Il record crittografato viene quindi caricato fissandolo a IPFS tramite api.pinata.cloud/pinning/pinJSONToIPFS, autenticato con credenziali API Pinata hardcoded. Non c'è un server C2 personalizzato da sequestrare: i dati rubati sono parcheggiati in un archivio pubblico decentralizzato, recuperabile dall'operatore utilizzando gli hash del contenuto risultanti. I caricamenti sono distanziati con pochi secondi di jitter casuale e un registro locale di timestamp | tipo di chiave | hash restituito viene tenuto a ~/.cache-db/.node-sync/.sl.
  • 5. Mantenere la persistenza e trasmettere il segnale con un ciclo di 12 ore. La seconda fase ristabilisce la propria persistenza: una voce cron su Linux, un com.apple.syncd avviare il lavoro su macOS e un'attività pianificata denominata WindowsNodeSync su Windows — impostato per essere eseguito nuovamente ogni 12 ore. Nota che si tratta di un diverso Nome dell'attività di Windows da quella installata dal dropper (WinNodeSync); entrambi meritano di essere cercati.

timeline

Gli otto pacchetti sono stati pubblicati in un breve lasso di tempo il 13 e il 14 luglio 2026. Diversi pacchetti sono disponibili in più di una versione; il meccanismo di inserimento è identico in tutte le versioni, con la sola differenza degli offset di riga che variano in base alle dimensioni del codice di utilità sottostante.

Data Event
2026-07-13 I primi pacchetti del cluster appaiono sotto un unico editore (solana-key-utils, eth-wallet-helpers, crypto-validate-lib e le prime versioni del resto)
2026-07-13 → 07-14 Nomi rimanenti e versioni successive pubblicate; lo stesso contagocce allegato viene spedito in ogni
2026-07-14 Tutti e otto sono stati segnalati e analizzati; ogni pacchetto è ancora installabile dal registro di sistema.

Nel corso dell'ondata di attacchi, la reputazione del registro dell'editore è passata da pressoché neutra all'inizio del cluster a fortemente negativa con l'accumularsi dei rilevamenti: un effetto collaterale osservabile della segnalazione dei pacchetti, non una caratteristica progettata.

Indicatori di compromesso

Tutti gli indicatori seguenti sono stati confermati presenti al momento dell'analisi: quelli nelle tabelle “Seconda fase” tramite decodifica test/fixtures/keypairs.dat e leggendo il recuperato syncd.js.

File e percorsi

Ruolo
~/.cache-db/.node-sync/syncd.js Decodificato secondo stadio, scritto con modalità 0o700
~/.cache-db/.node-sync/.sl Registro locale dell'esfiltrazione (timestamp | tipo di chiave | hash IPFS)
test/fixtures/keypairs.dat Il payload codificato in Base64 è posizionato all'interno del tarball come "strumento di prova".

Infrastruttura di rete di secondo livello (recuperata da syncd.js)

Ruolo
gist.githubusercontent.com/juang55/b298754cb72942b1cdcf02ccd45cde2f/raw/cfg.txt Attivazione deaddrop; lo script viene eseguito solo se la configurazione è letta active=1
Qmcqz3w8j4qFQXDAXAxnrdc2oSX3nzBT4NqtpTqL8mr1ga Fallback della configurazione IPFS (CID)
QmdTXoqVmTHY1i4ZWLdLkoQ9YChp5TXPh5cWXwnAYZt5iF Fallback della configurazione IPFS (CID)
QmfJkLU5gdCpqbbqEjWYC2anXW9FmuEeSLLeLiHVJKYUjp Fallback della configurazione IPFS (CID)
gateway.pinata.cloud, ipfs.io, cloudflare-ipfs.com Gateway IPFS utilizzati per recuperare la configurazione di fallback
api.pinata.cloud/pinning/pinJSONToIPFS Punto di esfiltrazione, dati rubati bloccati su IPFS pubblico
Chiave API di Pinata 13c766575b9270a9825d, credenziale di esfiltrazione codificata

Obiettivi della raccolta di secondo livello (recuperati da syncd.js)

Ruolo
~/.config/solana, ~/.ethereum/keystore, ~/.foundry, ~/.hardhat, ~/.ssh, ~/.env, file shell rc Ricerca di chiavi e segreti nelle directory/file
AppData\Roaming\Solana, AppData\Local\ethereum\keystore Ricerca di equivalenti Windows
Tipi di artefatti raccolti Chiavi private ETH, Bitcoin WIF, frasi di recupero BIP-39, coppie di chiavi Solana, JSON del keystore Ethereum, chiavi SSH, variabili d'ambiente segrete

manufatti di persistenza

Piattaforma
Linux avvio della voce cron syncd.js; installato tramite crontab filtrato attraverso grep -v syncd
Windows operazione programmata WinNodeSync (contagocce) e WindowsNodeSync (seconda fase)
macOS etichetta di lancio com.apple.syncd
Tutti La seconda fase si ripete in un ciclo di 12 ore

Behavioral

  • Funzione auto-invocante aggiunta dopo modulo.esportazioni, programmando un impostaTimeout di circa 37,000 ms in fase di importazione.
  • processo_figlio genera("nodo", ) con l'opzione di distacco impostata.
  • Attivazione a gate di inattività nella seconda fase (xstampante / ioreg HIDIdleTime / Tempo di inattività di PowerShell; soglia di circa 15 minuti).
  • Prima di trovare la crittografia RSA-4096, poi HTTPS POST a un'API pubblica di pinning IPFS.

Pacchetti e versioni (npm, editore solbuilder_io)

CONFEZIONE versioni
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[.]me, email non verificata, nessun account di controllo versione verificato, nessun repository collegato.

Attribuzione e comportamento osservato

Gli otto pacchetti condividono un account publisher e un payload. Ognuno è un pacchetto banale: pochi kilobyte di codice di utilità reale con lo stesso dropper aggiunto, pubblicato senza un repository collegato e con un'e-mail non verificata in stile usa e getta. Tale uniformità, il percorso di drop condiviso, le etichette di persistenza condivise e la condivisione coppie di chiavi.dat I file di staging sono ciò che collega i vari elementi del cluster.

Le confezioni sono insolitamente schiette riguardo al proprio comportamento. solana-key-utils contiene commenti in linea che descrivono il blocco aggiunto in termini semplici: uno lo etichetta FANTASMA: persistenza invisibile, un altro (in spagnolo) recita Eseguire il topo in background, “esegui la talpa in background”. Queste sono le annotazioni del codice stesso su ciò che fa; il nome della campagna in questo post è tratto da quella prima etichetta insieme al nodo fittizio “sync daemon” (sincronizzato) che il meccanismo di persistenza imita.

Descriviamo solo ciò che il codice fa in modo osservabile. La denominazione dei pacchetti — tutti termini relativi a criptovalute, wallet e strumenti blockchain — indica il pubblico di sviluppatori che più probabilmente li scaricherà per nome; di per sé non stabilisce chi sia l'editore. La seconda fase era completamente recuperabile decodificando il fixture in base64 e il suo comportamento è descritto sopra: raccoglie chiavi del wallet, frasi di recupero e segreti e li esfiltra, crittografati con RSA, in un archivio pubblico IPFS tramite Pinata. Una scelta di progettazione degna di nota è l'assenza di un server C2 privato: la configurazione arriva da un gist di GitHub e da IPFS, e i dati rubati vengono archiviati in un archivio pubblico decentralizzato con chiave basata sull'hash del contenuto, entrambi più difficili da impossessare rispetto a un singolo host controllato da un attaccante. Al momento della stesura di questo documento, non è stata trovata alcuna segnalazione pubblica precedente corrispondente a questo cluster.

Chi è esposto. Chiunque abbia aggiunto uno di questi pacchetti a un progetto Node e poi abbia eseguito del codice che lo importa. Poiché la detonazione avviene al momento dell'importazione piuttosto che al momento dell'installazione, la semplice presenza del pacchetto non è sufficiente, ma qualsiasi utilizzo normale che carica il modulo raggiunge il payload. I nomi dell'esca prendono di mira gli sviluppatori che lavorano su Ethereum, Solana, Arbitrum, Layer-2 e strumenti generali di wallet/codifica, ovvero la popolazione che ha maggiori probabilità di possedere esattamente le risorse che la seconda fase cerca. Chiunque abbia eseguito uno di questi moduli su una macchina che contiene chiavi del wallet, frasi di recupero, keystore, chiavi SSH o .env I gestori dei segreti dovrebbero considerare tali credenziali come compromesse e ruotarle.

Due modelli che vale la pena interiorizzare. In primo luogo, carico utile come dispositivo di fissaggio: spedizione della seconda fase come base64 all'interno di un file di dati dal nome plausibile (test/fixtures/keypairs.dat) mantiene pulito l'aspetto della sorgente visibile del pacchetto e spinge il contenuto dannoso in un file che gli strumenti di revisione e le letture umane spesso trattano come dati inerti, In secondo luogo, Esecuzione ritardata in fase di importazione con persistenza multipiattaformaSpostare il trigger fuori dall'hook di installazione, aggiungere un timer e poi renderlo persistente tramite cron, attività pianificate e launchd è un passo deliberato per allontanarsi dalle tecniche di script di installazione più rumorose che la scansione automatizzata del registro monitora più attentamente.

Linee guida per i difensori e i manutentori:

  • Trattare il codice aggiunto dopo modulo.esportazioni come priorità di revisione: la logica del dropper si nasconde spesso al di sotto della superficie "reale" del modulo.
  • Non dare per scontato che i file di dati siano inerti. Un blob base64 sotto test/attrezzature/ che viene letto in fase di esecuzione e decodificato è adiacente all'eseguibile; segnala le letture in fase di esecuzione dei file fixture che alimentano un Funzione/eval/scrivi-poi-uova catena.
  • Cerca il percorso di caduta ~/.cache-db/.node-sync/ e per le unità di persistenza WinNodeSync (attività programmata) e com.apple.syncd (launchd) sulle macchine degli sviluppatori che hanno estratto questi nomi.
  • Avviso sui processi Node che creano voci cron, attività pianificate o processi launchd: le librerie legittime raramente lo fanno durante l'importazione.
  • Preferisci i file di blocco e le versioni bloccate e rivedi il diff di qualsiasi nuova piccola dipendenza "di utilità", soprattutto pacchetti zero-dipendenza pubblicati da account non verificati senza repository collegato.

Per chi si occupa della protezione dei registri, il punto fondamentale è che il monitoraggio dell'hook di installazione è necessario ma non sufficiente: un dropper con ritardo temporizzato, posizionato all'interno di un file di dati e attivato al momento dell'importazione, supererà un controllo effettuato solo al momento dell'installazione, e la fase di persistenza è spesso il segnale più evidente da intercettare.

sca-tools-software-strumenti-di-analisi-della-composizione
Dai priorità, risolvi e proteggi i rischi del tuo software
Crea il tuo account gratuito.
Nessuna carta di credito richiesta.

Proteggi lo sviluppo e la consegna del tuo software

con la suite di prodotti Xygeni