Введение
16 июня 2026 года исследователи Checkmarx изучили кампанию ChainVeil, использовавшую npm-пакеты для распространения многоступенчатых JavaScript-загрузчиков. И как раз недавно одному из моих знакомых (не)посчастливилось получить один из ранее неизвестных экземпляров.
Репозиторий с AI-агентом ему посоветовал... AI-агент. Да, отправной точкой заражения стала рекомендация от AI-помощника, а после клонирования репозитория и локального запуска антивирус прервал выполнение и классифицировал обнаруженный объект как:
Trojan.Script.Agent.mlgj
На первый взгляд ситуация выглядела довольно банально: ложное срабатывание, случайно попавший в репозиторий артефакт или очередной обфусцированный JavaScript-файл. Но я решил, что было бы интересно изучить вредоносный код, учитывая то, что Kaspersky не присвоил ему конкретное семейство.
Начало расследования
Первым делом я решил заглянуть в сам репозиторий: узнать кто автор, когда и кем был внедрен код. Но оказалось, что автор не новичок на платформе: 320 репозиториев, 50 подписчиков и много данных в поле О себе. Но об истории Git'а и самом репозитории чуть позднее. Перейдем непосредственно к коду!
Анализ JavaScript-кода
В самом начале я упомянул кампанию Chainveil не просто так. Если присмотреться к коду, можно заметить первые совпадения артефактов кампании.
Во-первых, идентификатор: у кампании в начале обфусцированного вредоносного блока кода присутствовало объявление глобального значения:
global['!']='9-0554-3'
В ходе анализа, эта строка приняла следующий вид:
A9-0554-3
Если вы прочитаете статью Checkmarx указанную выше, то в самом конце сможете найти обнаруженные ими идентификаторы кампании, но все они имеют первый блок A6, в то время как я наткнулся на A9. Также стоит обратить внимание: источник не является npm-пакетом, это самостоятельный ИИ-инструмент на Python использующий JS для Web части приложения.
Продолжив анализ я еще больше убедился в сходстве с ChainVeil, образец использовал абсолютно те же самые техники, что указаны в статье от Checkmarx.
-
Многоступенчатая обфускация JS-кода.
-
Использование данных TRON, Aptos и BSC транзакций в качестве источника полезной нагрузки.
-
Адреса C2-инфраструктуры полностью совпали с теми, что опубликовали Checkmarx.
Код является нестандартным по архитектуре работы loader'ом. Он использует несколько blockchain сервисов для получения полезной нагрузки.
-
Через TRON получает последнюю исходящую транзакцию аккаунта злоумышленника.
-
Использует Aptos, если через TRON получить данные не удалось
-
Извлеченное значение после UTF-8 декодирования и разворота строки используется как идентификатор транзакции BSC, откуда, из поля input, извлекается закодированная строка.
Извлеченная из данных BSC транзакции строка проходит через несколько операций:
-
Извлечение необходимых частей по разделителю
?.? -
XOR декодирование с ключом, находящимся в коде
Для деобфускации строк код использует функцию перестановки _$af163278 с фиксированным значением seed, так что мне удалось полностью восстановить используемые строки:
[0]: "r"
[1]: "_V"
[2]: "A"
[3]: "!"
[4]: "end"
[5]: "error"
[6]: "on"
[7]: ""
[8]: "data"
[9]: "parse"
[10]: "JSON"
[11]: "get"
[12]: "https"
[13]: "Promise"
[14]: "2.0"
[15]: "stringify"
[16]: "POST"
[17]: "request"
[18]: "write"
[19]: "join"
[20]: "reverse"
[21]: "split"
[22]: "utf8"
[23]: "toString"
[24]: "raw_data"
[25]: "/transactions?only_confirmed=true&only_from=true&limit=1"
[26]: "hex"
[27]: "from"
[28]: "Buffer"
[29]: "arguments"
[30]: "payload"
[31]: "/transactions?limit=1"
[32]: "?.?"
[33]: "substring"
[34]: "input"
[35]: "result"
[36]: "eth_getTransactionByHash"
[37]: "bsc-dataseed[.]binance[.]org"
[38]: "bsc-rpc[.]publicnode[.]com"
[39]: "length"
[40]: "charCodeAt"
[41]: "fromCharCode"
[42]: "String"
[43]: "getTime"
[44]: "Date"
[45]: "_p_t"
[46]: "2[...C"
[47]: "TMfK...7mAP"
[48]: "0xbe03...11e"
[49]: "m...]"
[50]: "TXf...zcG"
[51]: "0x3f0...ce3"
[52]: "node"
[53]: "-e"
[54]: "';"
[55]: "ignore"
[56]: "spawn"
[57]: "child_process"
В конечном итоге вся цепочка сводится к коду с RAT-функционалом, а поскольку структура загрузчика, механизм получения полезной нагрузки и конечный RAT-компонент соответствуют описанию Checkmarx, я не стал повторно подробно разбирать уже опубликованную ими финальную стадию. Поэтому сразу перейдем к главному вопросу статьи.
Как вредоносный код оказался в репозитории
Чтобы понять, как вредоносный файл попал к конечному пользователю, пришлось исследовать историю Git-коммитов зараженного репозитория inbox-ai.
На первый взгляд, владелец аккаунта выглядит как обычный разработчик: более 300 репозиториев и активный профиль. Однако история изменений ключевого файла navigation.js раскрывает то, как именно вредоносный код попал в кодовую базу.
Первое появление файла зафиксировано в коммите a3940ca (ноябрь 2025 года):
commit a3940ca
Author: Lite Object <...@gmail.com>
Date: Sat Nov 8 09:26:42 2025 -0600
feat: Implement user preferences management with CRUD operations
and integrate into settings page
В этот момент navigation.js был добавлен как чистый, легитимный скрипт. Вредоносный код появился значительно позже - 29 марта 2026 года в процессе слияния веток в коммите a40301a:
Commit: a40301a8a5b3a53d13c6c784b72ef5e1a192669e
Parents: 080ef0f, 5499bda
Message: Merge pull request #20 from .../ui-ux-improvements
В хвост файла был добавлен обфусцированный JS-однострочник, причем при осмотре кода, я заметил, что изначально его не видно из-за большого количества пробелов.
Но если мы повнимательнее присмотримся к ползунку то заметим, что длина строк основного кода не занимает столько места, и найдем вредоносный блок:
Самое интересное, что код как был добавлен автором в марте, так до сих пор там и лежит. А из истории коммитов мы можем выстроить цепочку изменений файла.
Таким же образом вредоносный код был внедрен в файл calendar-rail.js 30 марта 2026 года 03:44:18 UTC.
Можно предположить несколько сценариев: компрометацию учетной записи разработчика, компрометацию рабочего окружения или использование техники, напоминающей EvilMerge. Однако остается открытым вопрос: почему вредоносный код оставался в репозитории более пяти месяцев и не был удален?
Анализ инфраструктуры кампании
При самостоятельном анализе связанного BSC-адреса мне удалось обнаружить транзакцию от 7 февраля 2025 года, содержащую данные, которые по структуре соответствуют наблюдаемой цепочке доставки. Если данная транзакция действительно относится к той же инфраструктуре, это сдвигает предполагаемую дату начала ее активности еще дальше - за пределы периода, описанного в публичном исследовании Checkmarx.
Анализ временных интервалов между транзакциями показал, что полезная нагрузка обновлялась регулярно - примерно каждые 2-3 дня. При этом в отдельные периоды наблюдались серии быстрых обновлений, когда несколько новых версий публиковались в течение нескольких минут. Подобное поведение может свидетельствовать об использовании автоматизированного механизма публикации полезной нагрузки.
Весной 2026 года активность инфраструктуры резко возрастает. Появляются новые кошельки, увеличивается частота ротации полезной нагрузки, а вскоре после этого начинается распространение вредоносных npm-пакетов.
Наиболее интересным выглядит временное совпадение между расширением инфраструктуры кампании и заражением исследуемого репозитория.
Если проследить TRON и BSC транзакции, можно заметить следующее:
|
Событие |
Время |
|---|---|
|
Merge-коммит |
29.03.2026 |
|
Коммит |
30.03.26 |
|
TRON-транзакция |
31.03.2026 14:20:57 UTC |
|
BSC-транзакция |
31.03.2026 14:20:42 UTC |
|
Появление первых npm-пакетов |
18.05.26 |
Важной особенностью является то, что BSC-транзакция произошла на 15 секунд раньше TRON-транзакции. Это объясняется архитектурой загрузчика. Из данных TRON-транзакции извлекается идентификатор транзакции BSC, содержащей следующую стадию полезной нагрузки.
Фактически злоумышленники использовали TRON в качестве промежуточного уровня, а BSC - в качестве хранилища следующего этапа загрузчика.
Вредоносный код был внедрен в репозиторий 29 марта 2026 года, тогда как первые npm-пакеты, описанные Checkmarx, появились только 18 мая 2026 года. Это может свидетельствовать о том, что npm никогда не был единственным каналом распространения ChainVeil.
Заключение
Таким образом, исследуемый образец демонстрирует практически полное совпадение с ранее описанной архитектурой ChainVeil: от многоступенчатой обфускации и блокчейн-механизма доставки полезной нагрузки до используемой C2-инфраструктуры и конечного RAT-компонента. Checkmarx также описывает именно трехблокчейнную схему TRON/Aptos - BSC и связывает ее с кампанией SuccessKey.
При этом исследованный экземпляр отличается от известных npm-образцов как минимум каналом доставки и идентификатором кампании: вместо A6-* используется A9-0554-3. В публичном исследовании Checkmarx именно A6-* используется как общий маркер семейства известных npm-пакетов.
История Git позволяет установить точку появления вредоносного кода в исследуемом проекте и проследить его дальнейшее распространение в другие JavaScript-файлы. Однако она не позволяет установить, кто именно контролировал учетную запись разработчика в момент внесения изменений.
Автор: ChikoiSan
