TL; DR
Një botues i vetëm i npm dërgoi tetë paketa të vogla, emrat e të cilave lexoheshin si blloqe ndërtimi të përditshme për zhvillimin e blockchain dhe portofoleve. base58-utils, abi-encode, eth-dev, arb-kit, layer2-sdk, solana-key-utils, eth-wallet-helpersdhe crypto-validate-libSecili prej tyre përmban një program funksional. Megjithatë, pas këtij programi është shtuar një bllok kodi që vetë-thirret dhe ekzekutohet në momentin që importohet moduli.
Afërsisht 37 sekonda pas importimit, ai bllok dekodon një ngarkesë që dërgohet brenda paketës e maskuar si një pajisje testimi, e shkruan atë në një skedar të fshehur nën drejtorinë kryesore të përdoruesit, regjistrohet për të rinisur në çdo login në Windows, macOS dhe Linux, dhe e nis skriptin e dekoduar si një proces të shkëputur. Asgjë në lidhje me këtë nuk aktivizohet gjatë instalimit të npm; pret që kodi të importohet dhe të ekzekutohet, që është një moment më i qetë se instalimi.
Ngarkesa nuk është e errët. Ajo dërgohet si bazë e thjeshtë64, kështu që dekodimi i "pajisjes së testimit" rikuperon fazën e dytë në tërësi: një vjedhës i kriptoportofolit dhe sekreteve që pret që makina të rrijë në pritje, mbledh çelësa privatë dhe fraza fillestare, enkripton çdo gjetje me një çelës të koduar fort RSA-4096 dhe e nxjerr atë duke e fiksuar në hapësirën publike të ruajtjes IPFS, duke u rikthyer çdo 12 orë.
Ne e ndjekim klasterin si PhantomSyncTë tetë paketat ishin aktive në regjistrin e npm në kohën e analizës, të publikuara nën një llogari të vetme.
| ekosistemit | NPM |
| Paketat | base58-utils, abi-encode, eth-dev, arb-kit, layer2-sdk, solana-key-utils, eth-wallet-helpers, crypto-validate-lib |
| Platformat e synuara | Windows, macOS, Linux |
| Sjellja thelbësore | Ulës i vonuar i kohës së importimit i fshehur si një pajisje testimi; instalime të vazhdueshme ndërplatformore |
| payload | Vjedhës i portofolit të kriptovalutave dhe sekreteve, enkriptim RSA-4096, nxjerrje nëpërmjet pinning-ut publik të IPFS-së |
Anatomia e sulmit
Çdo paketë nuk duket e jashtëzakonshme në leximin e parë. bazë58-utils, për shembull, është disa kilobajt kod ndihmës Base58 / Bitcoin-WIF me varësi zero — pikërisht atë që premton emri. Kodi përkatës ndodhet pas modulit module.eksporte, ku një lexues që shfleton shkurtimisht pjesën e sipërme të skedarit ka pak gjasa ta shohë: një funksion vetëthirrës që planifikon veten me një kohëmatës.
Zinxhiri i operacioneve, i rindërtuar nga burimi i paketës, funksionon kështu:
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 Dy zgjedhje dizajni dallohen.
Ngarkesa udhëton si një pajisje prove. Faza e dytë nuk është shkruar si kod i qartë. Ajo ndodhet në test/fixtures/keypairs.dat, një skedar base64 emri i të cilit përzihet në një paketë që pretendon se trajton çifte çelësash. Për një njeri që lexon shpejt tarball-in, duket si të dhëna shembull; për kodin e bashkangjitur, është një skript për t'u deshifruar dhe ekzekutuar. Vetë dropperi nuk përmban adresë rrjeti - ato ndodhen në fazën e dytë - por nuk ka asnjë errësim shtesë: pajisja është një shtresë e vetme e base64, kështu që deshifrimi i saj (bazë64 -d) rikuperon plotësisht syncd.js dhe sjelljen e tij në rrjet. Seksioni tjetër shpjegon se çfarë bën ajo fazë e rikuperuar.
Detonimi vonohet dhe lidhet me importin, jo me instalimin. Sepse shkaktari është kërkoj() plus një kohëmatës prej ~37 sekondash në vend të një grepi instalimi, sjellja anashkalon kontrollet që shikojnë vetëm instaloni npm hap, dhe vonesa zgjat më shumë se shumë ekzekutime afatshkurtra sandbox dhe CI. Derisa të ekzekutohet diçka, instalimi që tërhoqi paketën ka kohë që ka mbaruar.
Pasi skripti i dekoduar të jetë në disk në ~/.cache-db/.node-sync/syncd.js — një shteg i zgjedhur për t’u lexuar si një direktori rutinë e memorjes së përkohshme — dropper-i e bën atë të mbijetojë rinisjet në të tre platformat kryesore:
- Linux: një hyrje cron që rilançon skriptin. Hyrja instalohet duke filtruar crontab-in ekzistues përmes grep -v sinkronizuar, gjë që ka efektin anësor të lënies së hyrjes së re jashtë një liste të thjeshtë që kërkon grep për të njëjtin emër.
- Windows: një detyrë e planifikuar e quajtur WinNodeSync, e vendosur të ritransmetohet në një interval prej 12 minutash.
- MacOS: një punë e nisur e etiketuar com.apple.syncd, i pranishëm në disa nga paketat — një etiketë që imiton një shërbim legjitim të sistemit Apple.
Skripti më pas niset menjëherë si një proces i shkëputur, kështu që vazhdon të funksionojë pasi të përfundojë programi importues.
Çfarë bën faza e dytë
Meqenëse pajisja është një shtresë e vetme base64, faza e dytë dekodifikohet qartë dhe mund të lexohet plotësisht. Të tetë paketat mbajnë një nga tre variantet e të njëjtit skript, i cili identifikohet në një koment në kokë si phantom syncd v3 — topo durmiente (“urith i fjetur”). Detyra e tij është të vjedhë materiale nga portofolet e kriptomonedhave dhe sekretet e zhvilluesve dhe t'i nxjerrë ato nga makina. Funksionon në këto hapa:
- 1. Prisni derisa makina të jetë në punë. Para se të bësh ndonjë gjë, syncd.js kontrollon se sa kohë përdoruesi ka qenë joaktiv dhe vazhdon vetëm përtej një pragu (rreth 15 minuta) — xprintidle në Linux, ioreg HIDdleTime në macOS dhe një pyetje në kohë të papërgatitur PowerShell në Windows. Prandaj, mbledhja e të dhënave tenton të ndodhë kur askush nuk është pranë tastierës.
- 2. Merrni një çelës aktivizimi të kontrolluar nga distancaSkripti nxjerr një konfigurim të vogël nga një dead-drop përpara se të veprojë. Burimi kryesor është një URL i papërpunuar i gist-it të GitHub (gist.githubusercontent.com/juang55/…/cfg.txt); skripti aktivizohet vetëm nëse ai konfigurim lexon aktiv=1Nëse thelbi nuk është i disponueshëm, ai mbështetet te tre identifikues të përmbajtjes IPFS të koduar në mënyrë të ngurtë, të marrë përmes portave publike. gateway.pinata.cloud, ipfs.iodhe cloudflare-ipfs.comKjo i jep operatorit një kontroll të aktivizimit/çarmatimit pas operacionit dhe një alternativë rezistente ndaj rrëzimit. (Varianti më i vogël, i dërguar në lib-i-validimit-të-kriptos, ndihmësit e portofolit-ethdhe solana-key-utils, e ka të hequr shtresën thelbësore dhe mbështetet vetëm në identifikuesit IPFS.)
- 3. Materiali dhe sekretet e portofolit të korrjes. Skripti ecën nëpër direktorinë kryesore të përdoruesit — ~/.config/solana, ~/.ethereum/keystore, ~/.foundry, ~/.hardhat, ~ / .sshdhe Desktop/Dokumentet/Shkarkime (duke përfshirë emrat e dosjeve në gjuhën spanjolle), plus ~/.env dhe skedarët shell rc, dhe ekuivalentët Të dhenat e programit vendndodhjet në Windows. Ai synon çelësat privatë të Ethereum, çelësat WIF të Bitcoin, frazat fillestare BIP-39, çiftet e çelësave Solana, JSON-in e ruajtjes së çelësave të Ethereum, çelësat SSH dhe variablat e mjedisit që mbajnë sekrete. Varianti më i madh (në abi-encode, bazë58-utils, eth-dev) mbart fjalorin e plotë BIP-39 me 2048 fjalë dhe përdor shprehje të rregullta për të ekstrakt çelësa individualë dhe fraza fillestare të validuara nga çdo tekst që lexon; dy variantet më të vogla në vend të kësaj përputhen skedarët sipas fjalës kyçe (farë, menemonike, portofol, metamask, fantazmë, libër i llogarive, i sigurt, ...) dhe ngarkoni skedarë të tërë.
- 4. Enkripto dhe nxirr përmes një shërbimi publik të pinning-ut. Çdo gjetje shoqërohet me një gjurmë gishtash të strehuesit (emri i përdoruesit@emri i strehuesit, platformë, pullë kohore) dhe të enkriptuara me një çelës publik RSA-4096 të koduar fort të integruar në skript. Regjistrimi i enkriptuar ngarkohet më pas duke e fiksuar në IPFS nëpërmjet api.pinata.cloud/pinning/pinJSONToIPFS, të autentifikuara me kredencialet e koduara të Pinata API. Nuk ka një server C2 të personalizuar për t'u sekuestruar: të dhënat e vjedhura janë të parkuara në një ruajtje publike të decentralizuar, të rikuperueshme nga operatori duke përdorur hash-et e përmbajtjes që rezultojnë. Ngarkimet ndahen me disa sekonda luhatje të rastësishme dhe një regjistër lokal të vula kohore | lloji i çelësit | hash-i-kthyer mbahet në ~/.cache-db/.node-sync/.sl.
- 5. Këmbëngulni dhe sinjalizoni në një cikël 12-orësh. Faza e dytë rivendos qëndrueshmërinë e vet — një hyrje cron në Linux, një com.apple.syncd nisi një punë në macOS dhe një detyrë e planifikuar me emrin WindowsNodeSync në Windows — i vendosur të riekzekutohet çdo 12 orë. Vini re se kjo është një tjetër Emri i detyrës së Windows nga ajo që instalon dropper (WinNodeSync); të dyja ia vlen t'i kërkosh.
Kohështrirje
Tetë paketat u publikuan në një seri të ngushtë më 13 dhe 14 korrik 2026. Disa prej tyre mbartin më shumë se një version; zgjidhja është identike në të gjitha versionet, me vetëm zhvendosjet e vijave që ndryshojnë ndërsa ndryshon madhësia e kodit të shërbimeve të dobishme sipër tij.
| data | ngjarje |
|---|---|
| 2026-07-13 | Paketat e para në klaster shfaqen nën një botues (solana-key-utils, eth-wallet-helpers, crypto-validate-lib dhe versionet e hershme të pjesës tjetër) |
| 2026-07-13 → 07-14 | Emrat e mbetur dhe versionet pasuese të publikuara; i njëjti dropper i shtuar dërgohet në secilin |
| 2026-07-14 | Të tetë paketat u raportuan dhe u analizuan; çdo paketë ende mund të instalohet nga regjistri |
Gjatë gjithë shpërthimit, reputacioni i regjistrit të botuesit ndryshoi nga afërsisht neutral në fillim të grupit në fort negativ ndërsa zbulimet grumbulloheshin — një efekt anësor i vëzhgueshëm i paketave që sinjalizoheshin, jo një veçori e projektuar.
Treguesit e Kompromisit
Të gjithë treguesit më poshtë u konfirmuan të pranishëm në kohën e analizës - ata në tabelat e "Fazës së Dytë" duke deshifruar test/fixtures/keypairs.dat dhe duke lexuar të rikuperuarën syncd.js.
Skedarët dhe shtigjet
| Tregues | Rol |
|---|---|
~/.cache-db/.node-sync/syncd.js | Faza e dytë e dekoduar, e shkruar me modalitetin 0o700 |
~/.cache-db/.node-sync/.sl | Regjistri lokal i nxjerrjes (vula kohore | lloji i çelësit | hash IPFS) |
test/fixtures/keypairs.dat | Ngarkesë e koduar në Base64 e vendosur brenda tarball si një "pajisje testimi" |
Infrastruktura e rrjetit të fazës së dytë (e rikuperuar nga syncd.js)
| Tregues | Rol |
|---|---|
gist.githubusercontent.com/juang55/b298754cb72942b1cdcf02ccd45cde2f/raw/cfg.txt | Aktivizimi dead-drop; skripti ekzekutohet vetëm nëse lexon konfigurimin active=1 |
Qmcqz3w8j4qFQXDAXAxnrdc2oSX3nzBT4NqtpTqL8mr1ga | Rezervimi i konfigurimit IPFS (CID) |
QmdTXoqVmTHY1i4ZWLdLkoQ9YChp5TXPh5cWXwnAYZt5iF | Rezervimi i konfigurimit IPFS (CID) |
QmfJkLU5gdCpqbbqEjWYC2anXW9FmuEeSLLeLiHVJKYUjp | Rezervimi i konfigurimit IPFS (CID) |
gateway.pinata.cloud, ipfs.io, cloudflare-ipfs.com | Porta IPFS të përdorura për të marrë rezervën e konfigurimit |
api.pinata.cloud/pinning/pinJSONToIPFS | Pika fundore e eksfiltrimit, të dhënat e vjedhura të fiksuara në IPFS publike |
| Çelësi API i Pinata-s | 13c766575b9270a9825d, kredenciale eksfiltrimi të koduara fort |
Objektivat e mbledhjes së fazës së dytë (të rikuperuara nga syncd.js)
| Tregues | Rol |
|---|---|
~/.config/solana, ~/.ethereum/keystore, ~/.foundry, ~/.hardhat, ~/.ssh, ~/.env, skedarët shell rc | Drejtoritë/skedarët u kërkuan për çelësa dhe sekrete |
AppData\Roaming\Solana, AppData\Local\ethereum\keystore | Kërkime për ekuivalentët e Windows |
| Llojet e artefakteve të mbledhura | Çelësa privatë ETH, Bitcoin WIF, fraza fillestare BIP-39, çifte çelësash Solana, JSON i ruajtjes së çelësave Ethereum, çelësa SSH, variabla të mjedisit sekret |
Artefakte të këmbënguljes
| platformë | Tregues |
|---|---|
| Linux | hapja e hyrjes cron syncd.js; instaluar nëpërmjet crontab filtruar përmes grep -v syncd |
| Dritaret | detyrë e planifikuar WinNodeSync (pikues) dhe WindowsNodeSync (faza e dytë) |
| MacOS | etiketë e lançuar com.apple.syncd |
| të gjithë | Faza e dytë përsëritet në një cikël 12-orësh |
sjelljes
- Funksioni vetëthirrës i shtuar pas module.eksporte, duke planifikuar një setTimeout prej ~37,000 ms në import.
- proces_fëmijë spawn("nyje", ) me grupin e opsioneve të shkëputura.
- Aktivizimi i kontrolluar në gjendje boshe në fazën e dytë (xprintidle / ioreg Koha e mosveprimit në HIDdleTime / PowerShell; pragu ~15-minutësh).
- Enkriptimi RSA-4096 sipas gjetjes, pastaj HTTPS POST në një API publik të pinning-ut IPFS.
Paketat dhe versionet (npm, botuesi solbuilder_io)
| paketë | Versione |
|---|---|
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 |
Botues
- solbuilder_io - angel_lopez89[@]proton[.]me, email i paverifikuar, pa llogari të verifikuar të kontrollit të burimit, pa depo të lidhur.
Atribuimi dhe Sjellja e Vëzhguar
Tetë paketat ndajnë një llogari botuesi dhe një ngarkesë. Secila është një paketë e thjeshtë - disa kilobajt kodi të vërtetë të shërbimeve me të njëjtin dropper të bashkangjitur - e publikuar pa një depo të lidhur dhe nën një email të paverifikuar në stilin e njëpërdorimshëm. Kjo uniformitet, rruga e përbashkët e dropimit, etiketat e përbashkëta të qëndrueshmërisë dhe e përbashkëta keypairs.dat skedarët e fazës janë ato që lidhin klasterin së bashku.
Pakot janë jashtëzakonisht të sinqerta në lidhje me sjelljen e tyre. solana-key-utils mbart komente brenda rreshtit që përshkruajnë bllokun e shtuar në terma të thjeshtë - e etiketon atë FANTOM: këmbëngulje e padukshme, një tjetër (në spanjisht) thotë Nxirr topin në sfond, “ekzekuto molin në sfond.” Këto janë shënimet e vetë kodit për atë që bën; emri i fushatës në këtë postim është nxjerrë nga ajo etiketë e parë së bashku me nyjen e rreme “daemon i sinkronizimit” (sinkronizuar) që makineria e këmbënguljes imiton.
Ne përshkruajmë vetëm atë që bën kodi në mënyrë të dukshme. Emërtimi i paketave - të gjitha termat e kriptove, portofoleve dhe mjeteve të blockchain - tregon audiencën e zhvilluesve që ka më shumë gjasa t'i tërheqë ato me emër; kjo nuk përcakton në vetvete se kush është botuesi. Faza e dytë ishte plotësisht e rikuperueshme duke dekoduar pajisjen base64, dhe sjellja e saj është përshkruar më sipër: ajo mbledh çelësat e portofolit, frazat fillestare dhe sekretet dhe i nxjerr ato, të enkriptuara me RSA, në ruajtjen publike IPFS nëpërmjet Pinata. Një zgjedhje e dukshme e dizajnit është mungesa e një serveri privat C2 - konfigurimi vjen nga një gist GitHub dhe IPFS, dhe të dhënat e vjedhura parkohen në ruajtjen publike të decentralizuar të çelësuar nga hash përmbajtjeje, të dyja janë më të vështira për t'u kapur sesa një host i vetëm i kontrolluar nga sulmuesi. Asnjë raportim publik paraprak që përputhet me këtë klaster nuk u gjet në kohën e shkrimit.
Ndikimi, Trendet dhe Udhëzimet për Mbrojtësit
Kush është i ekspozuar. Kushdo që ka shtuar një nga këto paketa në një projekt Node dhe më pas ka ekzekutuar kod që e importon atë. Meqenëse detonimi është në kohë importimi dhe jo në kohë instalimi, thjesht prania e paketës nuk është e mjaftueshme - por çdo përdorim normal që ngarkon modulin arrin ngarkesën. Emrat e joshjes synojnë zhvilluesit që ndërtojnë mbi Ethereum, Solana, Arbitrum, Layer-2 dhe mjetet e përgjithshme të portofolit/kodimit - popullata që ka më shumë gjasa të ketë pikërisht asetet që kërkon faza e dytë. Kushdo që ka ekzekutuar një nga këto module në një makinë që mban çelësa portofoli, fraza fillestare, depo çelësash, çelësa SSH ose .zili sekretet duhet t'i trajtojnë ato kredenciale si të kompromentuara dhe t'i rrotullojnë ato.
Dy modele që ia vlen të përvetësohen. Parë, ngarkesë-si-fiksues: duke dërguar fazën e dytë si base64 brenda një skedari të dhënash me emër të besueshëm (test/fixtures/keypairs.dat) e mban burimin e dukshëm të paketës të duket i pastër dhe e shtyn përmbajtjen dashakeqe në një skedar që mjetet e rishikimit dhe skip-et njerëzore shpesh e trajtojnë si të dhëna inerte. Së dyti, ekzekutim i vonuar në kohën e importimit me qëndrueshmëri ndërplatformoreHeqja e aktivizuesit nga grepi i instalimit, shtimi i një kohëmatësi dhe më pas vazhdimësia në të gjitha operacionet cron, detyrat e planifikuara dhe launchd është një hap i qëllimshëm larg teknikave më të zhurmshme të skripteve të instalimit që skanimi i automatizuar i regjistrit i vëzhgon më nga afër.
Udhëzime për mbrojtësit dhe mirëmbajtësit:
- Trajto kodin e shtuar pas module.eksporte si përparësi rishikimi — logjika e lëshuesit shpesh fshihet nën sipërfaqen "reale" të modulit.
- Mos supozo se skedarët e të dhënave janë inertë. Një blob base64 nën test/pajisje/ që lexohet në kohën e ekzekutimit dhe dekodohet është ngjitur me ekzekutuesin; flagu lexon në kohën e ekzekutimit skedarët e fiksimeve që ushqejnë një funksion/vlerësoj/shkruaj-pastaj-pjellem zinxhir.
- Kërko rrugën e zbritjes ~/.cache-db/.node-sync/ dhe për njësitë e qëndrueshmërisë WinNodeSync (detyrë e planifikuar) dhe com.apple.syncd (lançuar) në makinat e zhvilluesve që tërhoqën këto emra.
- Alarm për proceset Node që krijojnë hyrje cron, detyra të planifikuara ose punë të nisura — libraritë legjitime rrallë e bëjnë këtë gjatë importimit.
- Preferoni skedarët e kyçur dhe versionet e fiksuara, dhe rishikoni ndryshimin e çdo varësie të re të vogël "të shërbimeve", veçanërisht paketa me varësi zero të publikuara nga llogari të paverifikuara pa depo të lidhura.
Për mbrojtësit e regjistrit, përfundimi është se monitorimi i grepit të instalimit është i nevojshëm, por jo i mjaftueshëm: një dropper me vonesë kohore në kohën e importimit i vendosur brenda një skedari të dhënash do të kalojë një kontroll vetëm në kohën e instalimit, dhe hapi i këmbënguljes është shpesh sinjali më i zhurmshëm i vëzhgueshëm që mbetet për t'u kapur.




