TL; DR
Jeden wydawca npm dostarczył osiem małych pakietów, których nazwy brzmią jak codzienne elementy konstrukcyjne dla rozwoju blockchain i portfela, base58-utils, abi-encode, eth-dev, arb-kit, layer2-sdk, solana-key-utils, eth-wallet-helpers, crypto-validate-libKażdy z nich zawiera działające narzędzie. Po nim jednak znajduje się samoczynnie wywołujący się blok kodu, który uruchamia się w momencie zaimportowania modułu.
Około 37 sekund po imporcie, ten blok dekoduje ładunek dostarczany w pakiecie zamaskowany jako element testowy, zapisuje go do ukrytego pliku w katalogu domowym użytkownika, rejestruje się w celu ponownego uruchomienia przy każdym login w systemach Windows, macOS i Linux, a następnie uruchamia zdekodowany skrypt jako oddzielny proces. Nic takiego nie dzieje się podczas instalacji npm; proces ten czeka na zaimportowanie i uruchomienie kodu, co jest spokojniejsze niż instalacja.
Ładunek nie jest nieprzejrzysty. Dostarczany jest w postaci zwykłego kodu base64, więc dekodowanie „urządzenia testowego” pozwala w pełni odtworzyć drugi etap: portfel kryptowalutowy i złodziej sekretów który czeka, aż maszyna będzie bezczynna, zbiera klucze prywatne i frazy startowe, szyfruje każde znalezisko zakodowanym na stałe kluczem RSA-4096 i eksfiltruje je, przypinając do publicznego magazynu IPFS, wysyłając sygnał zwrotny co 12 godzin.
Śledzimy klaster jako PhantomSyncWszystkie osiem pakietów było dostępnych w rejestrze npm w momencie analizy i opublikowanych na jednym koncie.
| ekosystem | Npm |
| Pakiety | base58-utils, abi-encode, eth-dev, arb-kit, layer2-sdk, solana-key-utils, eth-wallet-helpers, crypto-validate-lib |
| Platformy docelowe | Windows, macOS, Linux |
| Podstawowe zachowanie | Opóźniony dropper w czasie importu ukryty jako element testowy; instaluje trwałość międzyplatformową |
| Ładunek | Kryptowaluta i złodziej sekretów, szyfrowanie RSA-4096, eksfiltracja poprzez publiczne przypinanie IPFS |
Anatomia ataku
Na pierwszy rzut oka żadne z opakowań nie wydaje się niczym szczególnym. narzędzia base58Na przykład, to kilka kilobajtów kodu pomocniczego Base58/Bitcoin-WIF o zerowej zależności — dokładnie to, co obiecuje nazwa. Odpowiedni kod znajduje się po modułu moduł.eksporty, gdzie czytelnik przeglądający początek pliku raczej tego nie zauważy: funkcja wywołująca się sama, która planuje sama siebie za pomocą timera.
Łańcuch operacji, zrekonstruowany na podstawie źródła pakietu, wygląda następująco:
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 Na szczególną uwagę zasługują dwa wybory projektowe.
Ładunek podróżuje jako element testowy. Drugi etap nie jest napisany jako oczywisty kod. On żyje w test/urządzenia/pary kluczy.dat, plik base64, którego nazwa łączy się z pakietem, który rzekomo obsługuje pary kluczy. Dla człowieka przeglądającego archiwum tarball wygląda to jak przykładowe dane; dla dołączonego kodu jest to skrypt do zdekodowania i uruchomienia. Sam dropper nie zawiera adresu sieciowego — te znajdują się w drugim etapie — ale nie ma tu dodatkowego zaciemniania: element to pojedyncza warstwa base64, więc jego zdekodowanie (base64 -d) odzyskuje pełną syncd.js i jego zachowanie w sieci. W następnej sekcji omówimy działanie tego etapu odzyskiwania.
Detonacja jest opóźniona i powiązana z importem, a nie instalacją. Ponieważ wyzwalacz jest wymagają () plus ~37-sekundowy timer zamiast haka instalacyjnego, zachowanie to omija kontrole, które obserwują tylko npm zainstalować krok, a opóźnienie trwa dłużej niż wiele krótkotrwałych uruchomień sandbox i CI. Zanim cokolwiek się uruchomi, instalacja, która pobrała pakiet, dawno się zakończyła.
Po zdekodowaniu skryptu na dysku ~/.cache-db/.node-sync/syncd.js — ścieżka wybrana tak, aby czytać jak rutynowy katalog pamięci podręcznej — program do tworzenia kopii zapasowej sprawia, że przetrwa ona ponowne uruchomienia na wszystkich trzech głównych platformach:
- Linux: Wpis cron, który ponownie uruchamia skrypt. Wpis jest instalowany poprzez filtrowanie istniejącego pliku crontab przez grep -v syncd, co ma ten efekt uboczny, że nowy wpis nie pojawia się na zwykłej liście, która przeszukuje sieć w poszukiwaniu tej samej nazwy.
- Windows: zaplanowane zadanie o nazwie WinNodeSync, ustawione na powtarzanie co 12 minut.
- MacOS: zadanie launchd oznaczone etykietą com.apple.syncd, obecny w kilku pakietach — etykieta imitująca legalną usługę systemową Apple.
Skrypt jest następnie uruchamiany natychmiast jako oddzielny proces i będzie działał nadal po zamknięciu programu importującego.
Co robi drugi etap
Ponieważ oprawa jest pojedynczą warstwą base64, drugi etap dekoduje czysto i można go odczytać w całości. Wszystkie osiem pakietów zawiera jeden z trzech wariantów tego samego skryptu, który identyfikuje się w komentarzu nagłówka jako Phantom syncd v3 — topo trwałe („uśpiony kret”). Jego zadaniem jest kradzież danych z portfela kryptowalutowego i tajemnic programistycznych oraz przesyłanie ich z maszyny. Działa w następujących krokach:
- 1. Poczekaj, aż maszyna będzie bezczynna. Zanim cokolwiek zrobisz, syncd.js sprawdza, jak długo użytkownik był nieaktywny i kontynuuje działanie dopiero po przekroczeniu pewnego progu (około 15 minut) — xdrukowanie na Linuksie, Ioreg HIDIdleTime w systemie macOS i zapytanie PowerShell o czas bezczynności w systemie Windows. Dlatego zbieranie danych ma miejsce zazwyczaj wtedy, gdy nikt nie jest przy klawiaturze.
- 2. Zdobądź zdalnie sterowany przełącznik aktywującySkrypt pobiera niewielką konfigurację z martwego folderu przed podjęciem działania. Głównym źródłem jest surowy adres URL z GitHub Gist (gist.githubusercontent.com/juang55/…/cfg.txt); skrypt aktywuje się tylko wtedy, gdy ta konfiguracja odczyta aktywny=1Jeśli treść jest niedostępna, powraca do trzech zakodowanych na stałe identyfikatorów treści IPFS, pobranych przez publiczne bramy brama.pinata.chmura, ipfs.io, cloudflare-ipfs.com. Dzięki temu operator ma możliwość uzbrajania/rozbrajania broni po fakcie i jest odporny na awarię. (Najmniejsza wersja, dostarczana w crypto-validate-lib, pomocnicy portfeli elektronicznych, solana-key-utils, usunięto warstwę sedna i opiera się wyłącznie na identyfikatorach IPFS.)
- 3. Zbierz materiały i sekrety do portfela. Skrypt przegląda katalog domowy użytkownika — ~/.config/solana, ~/.ethereum/keystore, ~/.odlewnia, ~/.kask, ~ / .ssh, Stacjonarny/DOKUMENTACJA/Pobrania (w tym nazwy folderów w języku hiszpańskim) oraz ~/.env i pliki rc powłoki oraz równoważne AppData Lokalizacje w systemie Windows. Obejmuje on klucze prywatne Ethereum, klucze Bitcoin WIF, frazy początkowe BIP-39, pary kluczy Solana, JSON magazynu kluczy Ethereum, klucze SSH i zmienne środowiskowe zawierające sekrety. Większa wersja (w kodowanie abi, narzędzia base58, eth-dev) zawiera pełny słownik BIP-39 składający się z 2048 słów i wykorzystuje wyrażenia regularne do wyciąg pojedyncze klucze i sprawdzone frazy początkowe z dowolnego tekstu, który odczytuje; dwie mniejsze wersje dopasowują pliki według słów kluczowych (nasienie, mnemoniczny, portfel, metamask, fantom, księga główna, bezpieczny, …) i przesyłaj całe pliki.
- 4. Zaszyfruj i wyekstrahuj za pomocą publicznej usługi przypinania. Każde znalezisko jest dołączone do odcisku palca gospodarza (nazwa użytkownika@nazwa hosta(platforma, znacznik czasu) i zaszyfrowane za pomocą zakodowanego na stałe klucza publicznego RSA-4096 osadzonego w skrypcie. Zaszyfrowany rekord jest następnie przesyłany poprzez przypięcie go do IPFS za pośrednictwem api.pinata.cloud/pinning/pinJSONToIPFS, uwierzytelniane za pomocą zakodowanych na stałe danych logowania Pinata API. Nie ma dedykowanego serwera C2 do przejęcia: skradzione dane są przechowywane w publicznej, zdecentralizowanej pamięci masowej, skąd operator może je odzyskać za pomocą wygenerowanych skrótów treści. Przesyłanie danych jest przerywane z kilkusekundowym, losowym jitterem i lokalnym logowaniem. znacznik czasu | typ klucza | zwrócony hash jest utrzymywany w ~/.cache-db/.node-sync/.sl.
- 5. Wytrwaj i nadaj sygnał w cyklu 12-godzinnym. Drugi etap ponownie ustanawia własną trwałość — wpis cron w systemie Linux, com.apple.syncd zadanie launchd na macOS i zaplanowane zadanie o nazwie Synchronizacja węzłów systemu Windows w systemie Windows — ustaw ponowne uruchamianie co 12 godzin. Uwaga: to różne Nazwa zadania systemu Windows z zadania instalowanego przez program do kroplomierza (WinNodeSync); warto poszukać obu.
Oś czasu
Osiem pakietów opublikowano w krótkim odstępie czasu 13.07.2026 i 14.07.2026. Kilka z nich ma więcej niż jedną wersję; dropper jest identyczny we wszystkich wersjach, a jedynie przesunięcia wierszy zmieniają się wraz ze zmianą rozmiaru nieszkodliwego kodu narzędziowego nad nim.
| Data | wydarzenie |
|---|---|
| 2026-07-13 | Pierwsze pakiety w klastrze pojawiają się pod jednym wydawcą (solana-key-utils, eth-wallet-helpers, crypto-validate-lib i wczesne wersje pozostałych) |
| 2026-07-13 → 07-14 | Opublikowano pozostałe nazwy i wersje uzupełniające; w każdym z nich dodano te same statki dropperowe |
| 2026-07-14 | Wszystkie osiem oznaczono i przeanalizowano; każdy pakiet nadal można zainstalować z rejestru |
W trakcie trwania fali ataków reputacja rejestru wydawcy zmieniła się z mniej więcej neutralnej na początku klastra na silnie negatywną w miarę gromadzenia się wykryć — był to zauważalny efekt uboczny oznaczania pakietów flagą, a nie zamierzona funkcja.
Wskaźniki kompromisu
Obecność wszystkich poniższych wskaźników potwierdzono w momencie analizy — tych w tabelach „Drugiego etapu” poprzez dekodowanie test/urządzenia/pary kluczy.dat i czytanie odzyskanych syncd.js.
Pliki i ścieżki
| Wskaźnik | Rola |
|---|---|
~/.cache-db/.node-sync/syncd.js | Zdekodowany drugi etap, zapisany w trybie 0o700 |
~/.cache-db/.node-sync/.sl | Lokalny dziennik eksfiltracji (znacznik czasu | typ klucza | skrót IPFS) |
test/fixtures/keypairs.dat | Zakodowany w standardzie Base64 ładunek umieszczony w archiwum tar jako „element testowy” |
Infrastruktura sieciowa drugiego etapu (odzyskana z syncd.js)
| Wskaźnik | Rola |
|---|---|
gist.githubusercontent.com/juang55/b298754cb72942b1cdcf02ccd45cde2f/raw/cfg.txt | Aktywacja – martwy punkt; skrypt uruchamia się tylko wtedy, gdy konfiguracja zostanie odczytana active=1 |
Qmcqz3w8j4qFQXDAXAxnrdc2oSX3nzBT4NqtpTqL8mr1ga | Zapasowa konfiguracja IPFS (CID) |
QmdTXoqVmTHY1i4ZWLdLkoQ9YChp5TXPh5cWXwnAYZt5iF | Zapasowa konfiguracja IPFS (CID) |
QmfJkLU5gdCpqbbqEjWYC2anXW9FmuEeSLLeLiHVJKYUjp | Zapasowa konfiguracja IPFS (CID) |
gateway.pinata.cloud, ipfs.io, cloudflare-ipfs.com | Bramy IPFS używane do pobierania zapasowej konfiguracji |
api.pinata.cloud/pinning/pinJSONToIPFS | Punkt końcowy eksfiltracji, skradzione dane przypięte do publicznego IPFS |
| Klucz API Pinata | 13c766575b9270a9825d, zakodowane na stałe dane uwierzytelniające eksfiltrację |
Cele kolekcji drugiego etapu (odzyskane z syncd.js)
| Wskaźnik | Rola |
|---|---|
~/.config/solana, ~/.ethereum/keystore, ~/.foundry, ~/.hardhat, ~/.ssh, ~/.env, pliki rc powłoki | Przeszukano katalogi/pliki pod kątem kluczy i sekretów |
AppData\Roaming\Solana, AppData\Local\ethereum\keystore | Wyszukano odpowiedniki systemu Windows |
| Zebrane typy artefaktów | Prywatne klucze ETH, Bitcoin WIF, frazy początkowe BIP-39, pary kluczy Solana, magazyn kluczy Ethereum JSON, klucze SSH, tajne zmienne środowiskowe |
Artefakty trwałości
| Platforma | Wskaźnik |
|---|---|
| Linux | uruchomienie wpisu cron syncd.js; zainstalowany przez crontab i przefiltrowany grep -v syncd |
| Windows | zaplanowane zadanie WinNodeSync (kroplomierza) i WindowsNodeSync (drugi etap) |
| macOS | etykieta launchd com.apple.syncd |
| Wszystkie produkty | Drugi etap powtarza się w cyklu 12-godzinnym |
Behawioralny
- Funkcja samoczynnie wywołująca dołączona po moduł.eksporty, planowanie ustawiony limit czasu około 37 000 ms przy imporcie.
- proces_potomny spawn(“węzeł”, ) z zestawem opcji odłączanej.
- Aktywacja bezczynnościowa w drugim etapie (xdrukowanie / Ioreg HIDIdleTime / czas bezczynności programu PowerShell; próg ~15 minut).
- Szyfrowanie RSA-4096, następnie HTTPS POST do publicznego interfejsu API przypinania IPFS.
Pakiety i wersje (npm, wydawca solbuilder_io)
| Pakiet | wersje |
|---|---|
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 |
wydawca
- solbuilder_io Do angel_lopez89[@]proton[.]me, adres e-mail niezweryfikowany, brak zweryfikowanego konta kontroli źródła, brak powiązanego repozytorium.
Atrybucja i obserwowane zachowanie
Osiem pakietów współdzieli jedno konto wydawcy i jeden ładunek. Każdy z nich to trywialny pakiet – kilka kilobajtów prawdziwego kodu narzędziowego z dołączonym tym samym dropperem – opublikowany bez powiązanego repozytorium i pod niezweryfikowanym adresem e-mail w stylu jednorazowego użytku. Ta jednolitość, współdzielona ścieżka dropu, współdzielone etykiety trwałości i współdzielone keypairs.dat Pliki tymczasowe są elementami łączącymi klaster.
Pakiety są niezwykle szczere, jeśli chodzi o ich własne zachowanie. solana-key-utils zawiera komentarze w tekście, które opisują dołączony blok w prostych słowach — jeden z nich to etykieta PHANTOM: niewidzialna trwałość, inny (po hiszpańsku) brzmi Ejecutar topo en background„uruchom kreta w tle”. Są to własne adnotacje kodu opisujące jego działanie; nazwa kampanii w tym wpisie została zaczerpnięta z tej pierwszej etykiety wraz z fałszywym węzłem „demon synchronizacji” (synchronizacja) którą imituje mechanizm trwałości.
Opisujemy jedynie obserwowalne działanie kodu. Nazewnictwo pakietów – wszystkie terminy związane z kryptowalutami, portfelami i narzędziami blockchain – wskazuje na grupę programistów, która najprawdopodobniej je znajdzie po nazwie; samo w sobie nie określa wydawcy. Drugi etap był w pełni odzyskiwalny poprzez dekodowanie pliku base64, a jego działanie opisano powyżej: gromadzi klucze portfela, frazy początkowe i sekrety, a następnie eksfiltruje je, zaszyfrowane RSA, do publicznego magazynu IPFS za pośrednictwem Pinaty. Godnym uwagi wyborem projektowym jest brak prywatnego serwera C2 – konfiguracja pochodzi z bazy danych GitHub i IPFS, a skradzione dane są przechowywane w publicznym, zdecentralizowanym magazynie kluczowanym hashem zawartości, co jest trudniejsze do przechwycenia niż pojedynczy host kontrolowany przez atakującego. W momencie pisania tego tekstu nie znaleziono żadnych wcześniejszych publicznych raportów pasujących do tego klastra.
Wpływ, trendy i wskazówki dla obrońców
Kto jest narażony. Każdy, kto dodał jeden z tych pakietów do projektu Node, a następnie uruchomił kod, który go importuje. Ponieważ detonacja następuje w momencie importu, a nie instalacji, sama obecność pakietu nie wystarczy — ale każde normalne użycie, które ładuje moduł, dociera do ładunku. Nazwy wabiące są skierowane do deweloperów tworzących na Ethereum, Solana, Arbitrum, warstwie 2 i ogólnych narzędziach do portfeli/kodowania — populacji, która najprawdopodobniej posiada dokładnie te zasoby, których poszukuje drugi etap. Każdy, kto uruchomił jeden z tych modułów na komputerze z kluczami portfela, frazami początkowymi, magazynami kluczy, kluczami SSH lub… .env sekrety powinny traktować te dane uwierzytelniające jako zagrożone i je rotować.
Dwa wzorce warte przyswojenia. Po pierwsze, ładunek jako element mocujący:wysyłanie drugiego etapu jako base64 w pliku danych o wiarygodnie nazwanej nazwie (test/urządzenia/pary kluczy.dat) utrzymuje widoczne źródło pakietu w czystości i umieszcza szkodliwą zawartość w pliku, który narzędzia do weryfikacji i ludzkie skanowanie często traktują jako dane bezużyteczne. Po drugie, wykonanie opóźnione w czasie importu z trwałością międzyplatformową:przeniesienie wyzwalacza poza hak instalacji, dodanie timera, a następnie utrwalenie go w cronie, zadaniach zaplanowanych i launchd to celowe odejście od bardziej hałaśliwych technik skryptów instalacyjnych, które są najpilniej obserwowane przez automatyczne skanowanie rejestru.
Wskazówki dla obrońców i konserwatorów:
- Potraktuj dołączony kod po moduł.eksporty jako priorytet przeglądu — logika kroplomierza często ukrywa się pod „rzeczywistą” powierzchnią modułu.
- Nie zakładaj, że pliki danych są bezwładne. Blob base64 w test/osprzęt/ który jest odczytywany w czasie wykonywania i dekodowany, jest wykonywalny; flaga czasu wykonania odczytuje pliki osprzętu, które zasilają Funkcjonować/eval/napisz-następnie-ikra łańcuch.
- Poszukiwanie ścieżki zrzutu ~/.cache-db/.node-sync/ i dla jednostek trwałości WinNodeSync (zaplanowane zadanie) i com.apple.syncd (launchd) na komputerach deweloperskich, które pobrały te nazwy.
- Otrzymuj alerty dotyczące procesów węzła, które tworzą wpisy cron, zadania zaplanowane lub zadania launchd — legalne biblioteki rzadko robią to podczas importowania.
- Preferuj pliki blokady i wersje przypięte oraz przejrzyj różnice w każdej nowej małej zależności „narzędziowej”, zwłaszcza opublikowane pakiety o zerowej zależności przez niezweryfikowane konta bez powiązanego repozytorium.
Dla obrońców rejestru wniosek jest taki, że monitorowanie instalacji jest konieczne, ale niewystarczające: dropper opóźniony w czasie importu i umieszczony w pliku danych przejdzie kontrolę wyłącznie w czasie instalacji, a etap trwałości jest często najgłośniejszym obserwowalnym sygnałem, który pozostał do wychwycenia.




