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 install ничего не происходит; программа ожидает импорта и выполнения кода, что происходит тише, чем установка.
Полезная нагрузка не является непрозрачной. Она поставляется в виде обычного base64, поэтому декодирование "тестового образца" позволяет полностью восстановить второй этап: криптокошелек и похититель секретов Эта программа ожидает, пока машина перестанет работать, извлекает закрытые ключи и сид-фразы, шифрует каждую найденную фразу с помощью жестко закодированного ключа RSA-4096 и извлекает данные, закрепляя их в общедоступном хранилище IPFS и отправляя ответные сигналы каждые 12 часов.
Мы отслеживаем кластер следующим образом: PhantomSyncНа момент анализа все восемь пакетов были активны в реестре npm и опубликованы под одной учетной записью.
| Экосистема | НПМ |
| Packages | 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, не зависящего ни от чего — именно то, что обещает название. Соответствующий код находится в... после модуль модуль.экспорт, куда читатель, бегло просматривающий начало файла, вряд ли заглянет: самовызывающаяся функция, которая планирует свою работу с помощью таймера.
Последовательность операций, восстановленная из исходного кода пакета, выглядит следующим образом:
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, имя которого сливается с названием пакета, который, как утверждается, обрабатывает пары ключей. Для человека, бегло просматривающего архив, это выглядит как пример данных; для прилагаемого кода это скрипт для декодирования и запуска. Сам дроппер не содержит сетевых адресов — они находятся на втором этапе — но дополнительной обфускации нет: фикстура представляет собой один слой base64, поэтому декодирование его (base64 -d) восстанавливает полный syncd.js и его сетевое поведение. В следующем разделе рассматривается, что происходит на этапе восстановления.
Детонация происходит с задержкой и связана с импортом, а не с установкой. Потому что спусковой крючок требуют () Благодаря использованию таймера примерно на 37 секунд вместо механизма установки, такое поведение обходит проверки, которые отслеживают только... Установка npm На этом этапе задержка длится дольше, чем множество кратковременных запусков в песочнице и CI. К тому времени, как что-либо начнет выполняться, установка, которая загрузила пакет, уже давно завершится.
После того, как расшифрованный скрипт окажется на диске, ~/.cache-db/.node-sync/syncd.js — путь, выбранный таким образом, чтобы читаться как обычный каталог кэша, — позволяет программе-дропперу сохранять данные после перезагрузки на всех трех основных платформах:
- Linux: Запись cron, которая перезапускает скрипт. Запись устанавливается путем фильтрации существующего crontab. grep -v syncd, что приводит к побочному эффекту: новая запись не попадает в обычный список, который ищет то же имя.
- Windows: запланированная задача с именем WinNodeSyncПрограмма будет повторяться с интервалом в 12 минут.
- MacOS: задание launchd с меткой com.apple.syncd, присутствующий в нескольких пакетах — метка, имитирующая легитимную системную службу Apple.
Затем скрипт запускается немедленно как отдельный процесс, поэтому он продолжает работу после завершения программы импорта.
Что делает второй этап?
Поскольку фикстура представляет собой один слой base64, второй этап декодируется без проблем и может быть прочитан полностью. Все восемь пакетов содержат один из трех вариантов одного и того же скрипта, который идентифицирует себя в заголовочном комментарии как фантомная синхронизация v3 — топо durmiente («спящий крот»). Его задача — красть материалы криптокошельков и секреты разработчиков, а затем выводить их с машины. Процесс работы состоит из следующих шагов:
- 1. Подождите, пока машина не перейдет в режим ожидания. Прежде чем что-либо делать, syncd.js Проверяет, как долго пользователь был неактивен, и переходит к следующему шагу только после превышения порогового значения (около 15 минут). xprintidle в Linux, Йорег HIDIdleTime на macOS и запрос PowerShell для отслеживания времени простоя на Windows. Поэтому сбор данных обычно происходит, когда за клавиатурой никого нет.
- 2. Достаньте дистанционно управляемый переключатель активации.Скрипт загружает небольшой конфигурационный файл из тайника перед выполнением действий. Основной источник — это необработанная ссылка на фрагмент кода на GitHub (gist.githubusercontent.com/juang55/…/cfg.txtСкрипт активируется только в том случае, если в этой конфигурации указано следующее: активный=1Если основной код недоступен, используется три жестко закодированных идентификатора содержимого IPFS, получаемых через общедоступные шлюзы. шлюз.пината.облако, ipfs.io и cloudflare-ipfs.comЭто обеспечивает оператору возможность постфактумного управления постановкой/снятием с охраны, а также резервный вариант, устойчивый к разборке. (Самый маленький вариант, поставляемый в crypto-validate-lib, eth-wallet-helpers и solana-key-utils(В этой версии удален основной слой, и она полагается исключительно на идентификаторы IPFS.)
- 3. Добыча материалов для кошелька и раскрытие секретов. Скрипт проходит по домашнему каталогу пользователя — ~/.config/solana, ~/.ethereum/keystore, ~/.foundry, ~/.hardhat, ~ / .ssh и Рабочий стол/Документы/Скачать (включая названия папок на испанском языке), а также ~/.env и файлы rc оболочки, а также эквивалентные им. AppData расположений в Windows. Он нацелен на закрытые ключи Ethereum, ключи Bitcoin WIF, сид-фразы BIP-39, пары ключей Solana, JSON-хранилище ключей Ethereum, ключи SSH и переменные среды, содержащие секреты. Более крупный вариант (в abi-encode, base58-utils, eth-dev) содержит полный словарь BIP-39, состоящий из 2048 слов, и использует регулярные выражения для извлечение отдельные ключи и проверенные начальные фразы из любого прочитанного текста; два меньших варианта вместо этого сопоставляют файлы по ключевому слову (семя, мнемонический, бумажник, metamask, фантом, бухгалтерская книга, сейф, …) и загрузить целые файлы.
- 4. Шифрование и передача данных через общедоступный сервис закрепления. Каждое обнаруженное явление сопровождается отпечатком пальца хозяина (имя пользователя@имя хоста(платформа, метка времени) и шифруется с помощью жестко закодированного открытого ключа RSA-4096, встроенного в скрипт. Затем зашифрованная запись загружается путем ее закрепления в IPFS через api.pinata.cloud/pinning/pinJSONToIPFSАутентификация осуществляется с помощью жестко закодированных учетных данных API Pinata. Специального сервера управления и контроля для захвата нет: украденные данные хранятся в общедоступном децентрализованном хранилище, доступном оператору с помощью полученных хешей контента. Загрузки происходят с интервалом в несколько секунд случайных колебаний, и ведется локальный журнал. метка времени | тип ключа | возвращаемый хеш хранится в ~/.cache-db/.node-sync/.sl.
- 5. Настойчиво и постоянно поддерживайте связь в течение 12-часового цикла. На втором этапе восстанавливается собственное постоянное состояние — запись cron в Linux, а com.apple.syncd Запущена задача на macOS, и запланирована задача с именем WindowsNodeSync В Windows — настроено на повторный запуск каждые 12 часов. Обратите внимание, что это различный Название задачи Windows из той, которую устанавливает программа-дроппер (WinNodeSyncОба варианта заслуживают внимания.
Лента
Восемь пакетов были опубликованы в сжатые сроки 13 и 14 июля 2026 года. Некоторые из них имеют более одной версии; загрузчик идентичен во всех версиях, меняются только смещения строк в зависимости от размера расположенного над ним полезного кода.
| Время | События |
|---|---|
| 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, размещена внутри архива tarball в качестве "тестового образца". |
Сетевая инфраструктура второго этапа (восстановленная из 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 для пиньяты | 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, хранилище ключей Ethereum в формате JSON, ключи SSH, секретные переменные окружения. |
артефакты сохранения
| Платформа | Индикаторные |
|---|---|
| Linux | запуск записи cron syncd.js; установлено через crontab, отфильтровано через grep -v syncd |
| Windows | запланированная задача WinNodeSync (капельница) и WindowsNodeSync (второй этап) |
| MacOS | запущенная метка com.apple.syncd |
| Все | Вторая стадия возобновляется с 12-часовым циклом. |
Поведенческий
- Функция, вызываемая автоматически, добавляется после модуль.экспорт, планирование setTimeout Время импорта составляет примерно 37 000 мс.
- дочерний_процесс spawn(“node”, ) с установленным параметром отсоединения.
- Активация в режиме ожидания на втором этапе (xprintidle / Йорег HIDIdleTime / Время простоя PowerShell; пороговое значение ~15 минут).
- После обнаружения шифрования RSA-4096, затем HTTPS POST к общедоступному API для закрепления файлов IPFS.
Пакеты и версии (npm, publisher 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 содержит встроенные комментарии, описывающие добавленный блок простыми словами — один из них помечает его. ФАНТОМ: невидимая настойчивость, еще один (на испанском) гласит: Ejecutar topo en background«Запустить крота в фоновом режиме». Это собственные аннотации кода, описывающие его работу; название кампании в этом посте взято из этой первой метки вместе с фиктивным узлом «демон синхронизации» (синхронизировать) который имитирует механизм обеспечения устойчивости.
Мы описываем только то, что код делает наглядно. Названия пакетов — все термины, связанные с криптовалютами, кошельками и инструментами для блокчейна — указывают на аудиторию разработчиков, которые, скорее всего, будут их использовать по имени; сами по себе они не определяют издателя. Второй этап был полностью восстановлен путем декодирования base64-файла, и его поведение описано выше: он собирает ключи кошельков, сид-фразы и секреты и передает их, зашифрованные RSA, в общедоступное хранилище IPFS через Pinata. Примечательным дизайнерским решением является отсутствие частного C2-сервера — конфигурация поступает из GitHub gist и IPFS, а украденные данные хранятся в общедоступном децентрализованном хранилище, ключом которого является хеш контента, и то, и другое сложнее захватить, чем один хост, контролируемый злоумышленником. На момент написания статьи не было обнаружено никаких ранее опубликованных сообщений, соответствующих этому кластеру.
Влияние, тенденции и рекомендации для защитников
Кто подвергся опасности. Любой, кто добавил один из этих пакетов в проект Node, а затем запустил код, который его импортирует. Поскольку запуск происходит во время импорта, а не установки, простого наличия пакета недостаточно — но любое обычное использование, загружающее модуль, достигает полезной нагрузки. Названия приманок нацелены на разработчиков, создающих приложения на Ethereum, Solana, Arbitrum, Layer-2 и общих инструментах для работы с кошельками/кодированием — на ту группу пользователей, которая с наибольшей вероятностью имеет именно те активы, которые ищет второй этап. Любой, кто запустил один из этих модулей на машине, хранящей ключи кошелька, сид-фразы, хранилища ключей, SSH-ключи или .env Сотрудники, ответственные за обработку секретной информации, должны рассматривать эти учетные данные как скомпрометированные и регулярно их обновлять.
Две модели поведения, которые стоит усвоить. Первое полезная нагрузка как приспособление: отправка второго этапа в формате base64 внутри файла данных с правдоподобным названием (test/fixtures/keypairs.datВо-первых, ) обеспечивает чистоту видимого исходного кода пакета и помещает вредоносное содержимое в файл, который инструменты проверки и люди, просматривающие его вручную, часто рассматривают как инертные данные. Во-вторых, Отложенное выполнение во время импорта с кроссплатформенным сохранением данныхПеремещение триггера с точки зрения установки, добавление таймера и его сохранение в cron, запланированных задачах и launchd — это преднамеренный шаг в сторону от более шумных методов установки, которые наиболее тщательно отслеживаются автоматическим сканированием реестра.
Руководство для защитников и обслуживающего персонала:
- Обработайте добавленный код после модуль.экспорт Это приоритетная задача при проверке — логика загрузчика часто скрыта под «реальной» поверхностью модуля.
- Не следует предполагать, что файлы данных инертны. Блок base64 находится под тестовые приспособления/оборудование/ Файлы, которые считываются во время выполнения и декодируются, находятся рядом с исполняемым файлом; помечайте чтение файлов фикстуры во время выполнения, которые используются для передачи данных. Функция/Eval/write-then-порождать цепь.
- Найдите путь к обрыву. ~/.cache-db/.node-sync/ и для блоков обеспечения устойчивости WinNodeSync (запланированная задача) и com.apple.syncd (launchd) на машинах разработчиков, которые получили эти имена.
- Предупреждение для процессов Node, создающих записи cron, запланированные задачи или задания launchd — легитимные библиотеки редко делают это при импорте.
- Отдавайте предпочтение файлам блокировки и закрепленным версиям, а также просматривайте изменения в любых новых небольших «вспомогательных» зависимостях, особенно... опубликованы пакеты без зависимостей с неподтвержденных аккаунтов, не имеющих связанных репозиториев.
Для защитников реестра вывод таков: мониторинг установки через хуки необходим, но недостаточен: загрузчик, размещаемый внутри файла данных с задержкой по таймеру во время импорта, пройдет проверку только во время установки, а этап сохранения данных часто является самым заметным сигналом, который еще предстоит перехватить.



