TL, д-р
Один видавець npm випустив вісім невеликих пакетів, назви яких читаються як повсякденні будівельні блоки для розробки блокчейну та гаманців, base58-utils, abi-encode, eth-dev, arb-kit, layer2-sdk, solana-key-utils, eth-wallet-helpers та crypto-validate-libКожен з них містить робочу утиліту. Однак після цієї утиліти додається самозапускаючий блок коду, який запускається в момент імпорту модуля.
Приблизно через 37 секунд після імпорту, цей блок декодує корисне навантаження, що постачається всередині пакета, замаскованого під тестовий фікстур, записує його в прихований файл у домашньому каталозі користувача, реєструється для перезапуску кожного login у Windows, macOS та Linux, а також запускає декодований скрипт як окремий процес. Під час встановлення npm нічого не спрацьовує; він очікує імпорту та запуску коду, що є більш спокійним моментом, ніж встановлення.
Корисне навантаження не є непрозорим. Воно постачається як звичайний base64, тому декодування "тестового пристрою" повністю відновлює другий етап: a криптогаманець та викрадач секретів який чекає, поки машина простоює, збирає закриті ключі та початкові фрази, шифрує кожну знахідку за допомогою жорстко закодованого ключа RSA-4096 та вилучає її, закріплюючи у публічному сховищі IPFS, надсилаючи сигнал назад кожні 12 годин.
Ми відстежуємо кластер як PhantomSyncНа момент аналізу всі вісім пакетів були активні в реєстрі npm та опубліковані під одним обліковим записом.
| Екосистема | нмм |
| Пакети | base58-utils, abi-encode, eth-dev, arb-kit, layer2-sdk, solana-key-utils, eth-wallet-helpers, crypto-validate-lib |
| Цільові платформи | Windows, macOS, Linux |
| Основна поведінка | Затримка дроппера під час імпорту, прихованого як тестовий фікстур; встановлює кросплатформну персистенцію |
| корисне навантаження | Викрадення криптогаманців та секретів, шифрування RSA-4096, витік через публічне закріплення IPFS |
Анатомія атаки
На перший погляд кожен пакет виглядає нічим не примітним. base58-utils, наприклад, це кілька кілобайт допоміжного коду Base58 / Bitcoin-WIF з нульовою залежністю — саме те, що обіцяє назва. Відповідний код знаходиться після модуля module.exports, куди читач, що переглядає початок файлу, навряд чи загляне: функція, що самовикликається та планує свою роботу за допомогою таймера.
Ланцюг операцій, реконструйований з вихідного коду пакета, виглядає так:
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Виділяються два варіанти дизайну.
Корисне навантаження переміщується як випробувальне пристосування. Другий етап не пишеться як очевидний код. Він існує в test/fixtures/keypairs.dat, файл base64, назва якого вписується в пакет, який нібито обробляє пари ключів. Для людини, яка переглядає tar-архів, він виглядає як зразок даних; для доданого коду це скрипт для декодування та запуску. Сам дроппер не містить мережевої адреси — вони знаходяться на другому етапі — але немає додаткового обфускації: фікстура є одношаровою base64, тому його декодування (base64 -d) повністю відновлює syncd.js та його мережеву поведінку. У наступному розділі розглядається, що робить цей відновлений етап.
Детонація затримується та прив'язана до імпорту, а не до встановлення. Тому що тригером є вимагати () плюс таймер тривалістю ~37 секунд замість перехоплювача встановлення, ця поведінка обходить перевірки, які спостерігають лише за npm встановити крок, а затримка триває довше, ніж багато короткочасних запусків у пісочниці та неперервній інтеграції. На той час, як щось виконується, інсталяція, яка завантажила пакет, вже давно завершена.
Як тільки декодований сценарій буде на диску за адресою ~/.cache-db/.node-sync/syncd.js — шлях, обраний для читання як звичайний каталог кешу — дроппер забезпечує його переживання після перезавантажень на всіх трьох основних платформах:
- Linux: запис cron, який перезапускає скрипт. Запис встановлюється шляхом фільтрації існуючого crontab через grep -v syncd, що має побічний ефект – новий запис не враховується у звичайному списку, який використовує grep для того ж імені.
- Windows: заплановане завдання з назвою WinNodeSync, налаштований на повторний запуск з 12-хвилинним інтервалом.
- macOS: завдання launchd з позначкою com.apple.syncd, присутній у кількох упаковках — мітка, що імітує легітимну системну службу Apple.
Після цього скрипт запускається негайно як окремий процес, тому він продовжує виконуватися після завершення програми імпорту.
Що робить другий етап
Оскільки фіксуюча система має один шар base64, другий каскад декодується чисто та може бути прочитаний повністю. Усі вісім пакетів містять один із трьох варіантів одного й того ж скрипта, який ідентифікує себе в коментарі заголовка як phantom syncd v3 — topo durmiente («сплячий кріт»). Його завдання полягає у крадіжці матеріалів криптовалютних гаманців та секретів розробників і вилученні їх з машини. Він працює таким чином:
- 1. Зачекайте, поки машина перейде в режим очікування. Перш ніж щось робити, syncd.js перевіряє, як довго користувач був неактивним, і продовжує лише після певного порогу (близько 15 хвилин) — xprintidle на Linux, іорег HIDIdleTime на macOS та запит PowerShell під час простою на Windows. Тому збір даних зазвичай відбувається, коли нікого немає за клавіатурою.
- 2. Дістаньте дистанційно керований перемикач активаціїСкрипт отримує невеликий конфігураційний файл з мертвої папки, перш ніж виконати дію. Основним джерелом є необроблена URL-адреса gist з GitHub (gist.githubusercontent.com/juang55/…/cfg.txt); скрипт активується лише тоді, коли ця конфігурація читається активний=1Якщо суть коду недоступна, використовується три жорстко закодовані ідентифікатори контенту IPFS, що отримуються через публічні шлюзи. gateway.pinata.cloud, ipfs.io та cloudflare-ipfs.comЦе надає оператору можливість контролювати вмикання/зняття з охорони після ввімкнення/вимкнення та резервний варіант, стійкий до зняття. (Найменший варіант, що постачається в crypto-validate-lib, помічники-гаманців-eth та solana-key-utils, має видалений шар gist та покладається лише на ідентифікатори IPFS.)
- 3. Зберіть матеріал для гаманця та секрети. Скрипт переглядає домашній каталог користувача — ~/.config/solana, ~/.ethereum/сховище ключів, ~/.foundry, ~/.hardhat, ~ / .ssh та робочий стіл/документи/Завантаження (включно з іменами папок іспанською мовою), а також ~/.env та файли оболонки rc, а також еквівалент AppData місця у Windows. Він націлений на закриті ключі Ethereum, ключі Bitcoin WIF, початкові фрази BIP-39, пари ключів Solana, JSON сховища ключів Ethereum, ключі SSH та змінні середовища, що містять секрет. Більший варіант (у abi-кодування, base58-utils, eth-dev) містить повний словник BIP-39 з 2048 слів і використовує регулярні вирази для витяг окремі ключі та перевірені початкові фрази з будь-якого тексту, який він зчитує; два менші варіанти натомість зіставляють файли за ключовим словом (насіння, мнемонічний, кошелек, metamask, фантом, бухгалтерська книга, Трезор, …) та завантажувати цілі файли.
- 4. Зашифруйте та витягніть дані через публічний сервіс закріплення. Кожна знахідка пов'язана з відбитком пальця хазяїна (ім'я_користувача@ім'я_хостa, платформа, позначка часу) та зашифровано за допомогою жорстко закодованого відкритого ключа RSA-4096, вбудованого в скрипт. Зашифрований запис потім завантажується шляхом закріплення його на IPFS через api.pinata.cloud/pinning/pinJSONToIPFS, автентифікований за допомогою жорстко закодованих облікових даних Pinata API. Немає спеціального сервера C2 для захоплення: викрадені дані зберігаються в публічному децентралізованому сховищі, яке оператор може отримати за допомогою отриманих хешів контенту. Завантаження здійснюються з інтервалом у кілька секунд випадкового коливання, а локальний журнал позначка-часу | тип-ключа | повернутий-хеш зберігається в ~/.cache-db/.node-sync/.sl.
- 5. Наполегливо продовжуйте та використовуйте маячок протягом 12-годинного циклу. Другий етап відновлює власну персистенцію — запис cron у Linux, com.apple.syncd завдання launchd на macOS та заплановане завдання з назвою Синхронізація WindowsNode у Windows — налаштовано на повторний запуск кожні 12 годин. Зверніть увагу, що це різний Назва завдання Windows з тієї, яку встановлює дроппер (WinNodeSync); за обома варто полювати.
Хронологія
Вісім пакетів було опубліковано стрімким серійним випуском 13 та 14 липня 2026 року. Деякі з них мають більше однієї версії; дроппер ідентичний у всіх версіях, лише зміщення рядків змінюються залежно від розміру коду зручної утиліти над ним.
| Дата | Event |
|---|---|
| 2026-07-13 | Перші пакети в кластері з'являються під одним видавцем (solana-key-utils, eth-wallet-helpers, crypto-validate-lib та ранні версії решти) |
| 2026-07-13 → 07-14 | Опубліковано решту назв та наступні версії; ті самі додані кораблі-підсадники в кожному |
| 2026-07-14 | Усі вісім позначено та проаналізовано; кожен пакет все ще можна встановити з реєстру |
Протягом усього періоду спалаху репутація видавця в реєстрі змінилася з приблизно нейтральної на початку кластера до різко негативної в міру накопичення виявлень — це спостережуваний побічний ефект позначення пакетів, а не розроблена функція.
Показники компромісу
Усі наведені нижче показники були підтверджені на момент аналізу — ті, що в таблицях «Другий етап», шляхом декодування test/fixtures/keypairs.dat та читання відновлених syncd.js.
Файли та шляхи
| індикатор | Роль |
|---|---|
~/.cache-db/.node-sync/syncd.js | Декодований другий етап, записаний у режимі 0o700 |
~/.cache-db/.node-sync/.sl | Локальний журнал вилучення (мітка часу | тип ключа | хеш IPFS) |
test/fixtures/keypairs.dat | Корисне навантаження, закодоване в Base64, розміщене всередині tar-архіву як "тестовий пристрій" |
Мережева інфраструктура другого етапу (відновлена з syncd.js)
| індикатор | Роль |
|---|---|
gist.githubusercontent.com/juang55/b298754cb72942b1cdcf02ccd45cde2f/raw/cfg.txt | Замок активації; скрипт запускається, лише якщо конфігурація читається active=1 |
Qmcqz3w8j4qFQXDAXAxnrdc2oSX3nzBT4NqtpTqL8mr1ga | Резервна конфігурація IPFS (CID) |
QmdTXoqVmTHY1i4ZWLdLkoQ9YChp5TXPh5cWXwnAYZt5iF | Резервна конфігурація IPFS (CID) |
QmfJkLU5gdCpqbbqEjWYC2anXW9FmuEeSLLeLiHVJKYUjp | Резервна конфігурація IPFS (CID) |
gateway.pinata.cloud, ipfs.io, cloudflare-ipfs.com | Шлюзи IPFS, що використовуються для отримання резервної конфігурації |
api.pinata.cloud/pinning/pinJSONToIPFS | Кінцева точка витоку, викрадені дані закріплені на публічній IPFS |
| Ключ API Pinata | 13c766575b9270a9825d, жорстко закодовані облікові дані для ексфільтрації |
Цілі колекції другого етапу (відновлені з syncd.js)
| індикатор | Роль |
|---|---|
~/.config/solana, ~/.ethereum/keystore, ~/.foundry, ~/.hardhat, ~/.ssh, ~/.env, файли оболонки rc | Пошук ключів та секретів у каталогах/файлах |
AppData\Roaming\Solana, AppData\Local\ethereum\keystore | Пошук еквівалентів Windows |
| Типи зібраних артефактів | Приватні ключі ETH, Bitcoin WIF, початкові фрази BIP-39, пари ключів Solana, JSON сховища ключів Ethereum, ключі SSH, секретні змінні середовища |
Артефакти стійкості
| платформа | індикатор |
|---|---|
| Linux | запуск запису cron syncd.js; встановлено через crontab, відфільтровано grep -v syncd |
| Windows | заплановане завдання WinNodeSync (крапельниця) та WindowsNodeSync (другий етап) |
| MacOS | мітка launchd com.apple.syncd |
| ВСІ | Друга стадія повторюється протягом 12-годинного циклу. |
Поведінкові
- Самовикликаюча функція додана після module.exports, планування setTimeout ~37 000 мс під час імпорту.
- дочірній_процес spawn("вузол", ) з відокремленим набором опцій.
- Активація в режимі холостого ходу на другому етапі (xprintidle / іорег Час простою HIDIdleTime / PowerShell; поріг ~15 хвилин).
- Шифрування RSA-4096 на основі знахідки, а потім HTTPS POST до публічного API закріплення IPFS.
Пакети та версії (npm, видавець solbuilder_io)
| пакет | версії |
|---|---|
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 |
видавець
- solbuilder_io - angel_lopez89[@]proton[.]me, електронна адреса не перевірена, обліковий запис системи контролю версій не перевірено, немає підтвердженого репозиторію.
Атрибуція та спостережувана поведінка
Вісім пакетів мають один обліковий запис видавця та одне корисне навантаження. Кожен з них є тривіальним пакетом — кілька кілобайт реального утилітного коду з тим самим доданим дроппером — опублікованим без пов’язаного репозиторію та під неперевіреною електронною адресою одноразового використання. Ця однорідність, спільний шлях дропування, спільні мітки персистентності та спільні keypairs.dat проміжний файл – це те, що об'єднує кластер.
Пакунки надзвичайно відверті щодо власної поведінки. solana-key-utils містить вбудовані коментарі, які описують доданий блок простими словами — його позначають ФАНТОМ: невидима наполегливість, інший (іспанською) читає Видалити топографію на фоні, «запустити крота у фоновому режимі». Це власні анотації коду щодо його дій; назва кампанії в цій публікації взята з першої мітки разом із фальшивим вузлом «демоном синхронізації» (синхронізовано), що імітує механізм наполегливості.
Ми описуємо лише те, що робить код, що можна спостерігати. Назви пакетів — усі вони стосуються криптовалют, гаманців та інструментів блокчейну — вказують на аудиторію розробників, яка, найімовірніше, витягне їх за назвою; самі по собі вони не встановлюють, хто є видавцем. Другий етап можна було повністю відновити шляхом декодування фікстури base64, і її поведінка описана вище: вона збирає ключі гаманців, основну фразу та секрети та витягує їх, зашифровані за допомогою RSA, до публічного сховища IPFS через Pinata. Примітним вибором дизайну є відсутність приватного сервера C2 — конфігурація надходить з gist GitHub та IPFS, а викрадені дані зберігаються у публічному децентралізованому сховищі, зашифрованому за допомогою хешу контенту, обидва з яких важче захопити, ніж окремий хост, контрольований зловмисником. На момент написання статті попередніх публічних звітів, що відповідають цьому кластеру, не було знайдено.
Вплив, тенденції та рекомендації для захисників
Хто викривається. Будь-хто, хто додав один із цих пакетів до проекту Node, а потім запустив код, який його імпортує. Оскільки детонація відбувається під час імпорту, а не встановлення, простої присутності пакета недостатньо, але будь-яке звичайне використання, яке завантажує модуль, досягає корисного навантаження. Ці назви-приманки спрямовані на розробників, які працюють на Ethereum, Solana, Arbitrum, Layer-2 та загальних інструментах для гаманців/кодування — популяцію, яка, найімовірніше, матиме саме ті активи, які шукає другий етап. Будь-хто, хто запустив один із цих модулів на машині, що містить ключі гаманця, основну фразу, сховища ключів, ключі SSH або .env секрети повинні розглядати ці облікові дані як скомпрометовані та передавати їх.
Дві закономірності, які варто засвоїти. По-перше, корисне навантаження як пристосування: доставка другого етапу як base64 у файлі даних з правдоподібною назвою (test/fixtures/keypairs.dat) зберігає видиме джерело пакета чистим і переміщує шкідливий вміст у файл, який інструменти перевірки та людські сканери часто обробляють як інертні дані. По-друге, виконання із затримкою часу імпорту та кросплатформною персистенцієюПеренесення тригера з гачка встановлення, додавання таймера, а потім його збереження в cron, запланованих завданнях та launchd – це навмисний крок від шумніших методів встановлення скриптів, за якими найпильніше стежить автоматичне сканування реєстру.
Керівництво для захисників та тих, хто підтримує:
- Обробляти доданий код після module.exports як пріоритет перевірки — логіка дроппера часто ховається під поверхнею «справжнього» модуля.
- Не вважайте файли даних інертними. Base64 blob під тест/прилади/ що зчитується під час виконання та декодується, є суміжним з виконуваним файлом; позначає зчитування під час виконання файлів фікстур, які передають функція/евал/напиши-тоді-ікру ланцюжок.
- Шукайте шлях падіння ~/.cache-db/.node-sync/ і для одиниць персистенції WinNodeSync (заплановане завдання) та com.apple.syncd (launchd) на машинах розробників, які використовували ці імена.
- Попереджати про процеси Node, які створюють записи cron, заплановані завдання або завдання launchd — легітимні бібліотеки рідко роблять це під час імпорту.
- Надавайте перевагу файлам блокування та закріпленим версіям, а також переглядайте різницю будь-якої нової невеликої залежності «утиліт», особливо опубліковано пакети з нульовою залежністю неперевіреними обліковими записами без підключеного репозиторію.
Для захисників реєстру висновок полягає в тому, що моніторинг перехоплювачів встановлення є необхідним, але недостатнім: дроппер із затримкою часу імпорту, розміщений у файлі даних, пройде перевірку лише під час встановлення, а крок персистенції часто є найгучнішим спостережуваним сигналом, який залишається для перехоплення.







