The Jerusalem PostLod mayor ‘seething’ after Jewish-Arab clashes, right-wing activist summoned for questioningInquirerIlocos Sur town sets P180 minimum farmgate price for live hogsPunchDon’t politicise sports, Presidency tells Obi over Flamingos greetingDaily MaverickRENEWABLES CRISIS : Pele’s governance test: Why a private boardroom matters to SA’s energy transitionZDF heuteEntdecken Sie das ZDF-NachrichtenstudioSRF NewsSchauspieler und Comedian – Über zehn Jahre Haft für Russell BrandIl Fatto QuotidianoLe renne che nuotano per la migrazione invernale: le incredibili immagini che arrivano dalla Norvegia – VIDEOCollider‘The Acolyte’ Director’s New Thriller Officially Scores ‘The Wheel of Time’ StarScreen RantPlayStation Officially Announces All-New ConsoleDigital Spy"What is going on?": Former Strictly star Oti Mabuse hits out at the judges' scoringCBS NewsHow to qualify for credit card debt forgiveness in 2027ABC NewsRussell Brand sentenced to 10 years 6 months in prison for rape, sexual assault
The Daily Newsstand · Free, Always
Monday, November 9, 2026

Как могли взломать Binance через собеседование: новая работа, DevOps и троян в архиве

Translate

Предложение новой работы, разговор с рекрутером, техническое собеседование и небольшой проект, который просят посмотреть перед следующим этапом. Для разработчика это привычная последовательность. Именно поэтому она так хорошо подходит для атаки: чужой код попадает на рабочий компьютер в момент, когда от кандидата ожидают, что он его изучит и запустит.

В истории со взломом Binance это не просто предположение из заголовка. В предварительном отчёте биржи от 3 ноября 2026 года описано фальшивое собеседование: сотруднику предложили новую работу, провели два этапа интервью и дали задание с вредоносным репозиторием. Дальше атака распространилась на другие устройства и затронула Wallet DevOps. Уточнение, которое легко потерять при пересказе: задание получил Software Engineer B, а доступ к DevOps-инженеру A и его токену развёртывания появился позже. По версии Binance, это была многоэтапная операция, а не один запуск архива, который мгновенно открыл доступ ко всем кошелькам.

Северокорейский след в этой истории тоже имеет источник: Binance ссылается на атрибуцию ФБР, а Chainalysis описывает использование поддельного найма в связанных с КНДР атаках. Но для меня эта тема перестала быть только новостью, когда похожая история случилась с товарищем.

Он попался на такую же по форме приманку: потенциальные «работодатели» прислали ему архив с проектом. Этот архив он передал мне на анализ. Возникло подозрение на ту самую схему с северокорейскими хакерами под видом нанимающей компании. Личность отправителей ещё предстояло установить. Я начал с того, что можно проверить непосредственно: содержимого архива и действий, которые код выполняет после команд из README.

Я разобрал архив в Codex с моделью Astra в режиме Extra High и подготовил этот отчёт. Задачу поставил жёстко: считать проект враждебным, не запускать приложение, hooks и установщики, а закодированные строки восстанавливать только как данные. Меня интересовало, где начинается исполнение, какие файлы пытаются забрать и каким образом «работодатель» может получить командную строку на чужом компьютере.

Результат оказался интереснее обычного подозрительного npm install: первая ловушка срабатывала уже при переключении Git-ветки. Дальше нашлись скрытый загрузчик, несколько мегабайт обфусцированного JavaScript, сбор браузерных данных и полноценная удалённая shell.

Сразу обозначу границу: связь именно этого архива с операторами атаки на Binance или с конкретной северокорейской группой не установлена. Сходство сценария — повод проверить образец, а не доказательство атрибуции. Ниже начинается технический разбор того, что удалось подтвердить по коду.

«Просто переключите ветку»

Архив с игровым проектом можно было начать проверять с ошибки: выполнить первую же инструкцию из README. Там предлагалось перед знакомством с кодом переключиться на ветку video. Зависимости ещё не установлены, приложение не запущено — кажется, ничего опасного пока не происходит. Однако вместе с исходниками в ZIP лежал каталог .git, а в нём — действующий post-checkout, который скачивал и исполнял внешний код.

Статический разбор этой цепочки привёл к JavaScript-файлу размером 4,5 МБ. За обфускацией обнаружились три программы: сборщик браузерных данных и кошельков, сборщик пользовательских файлов и RAT с интерактивной shell. Отдельная точка загрузки нашлась в истории репозитория — внутри модуля с неприметным именем server/config/.svn/env.module.

Ниже — разбор того, как устроена атака, какие выводы действительно следуют из кода и где легко переоценить возможности образца. Особенно интересны две детали: отключённая функция расшифровки паролей и файловый фильтр, который из-за слова user пропускает гораздо больше данных, чем можно предположить по его названию.

Что именно исследовалось

Исходный артефакт — game-demo.zip, 28 675 021 байт. Анализ выполнен 7 ноября 2026 года. На моём компьютере архив оставался объектом исследования: приложение, установки и команды из README не выполнялись. Наличие вредоносного проекта подтверждено, но этот разбор не устанавливает факт заражения компьютера товарища или состоявшейся утечки: для этого нужны отдельные данные с его устройства.

В работе были две части. Сначала весь архив, Git metadata и объекты истории исследовались локально. Затем были получены как данные доступные внешние стадии загрузки, на которые ссылался hook. Ни hook, ни bootstrap, ни восстановленные программы не запускались. К обнаруженному серверу управления не подключались, регистрацию «жертвы» не выполняли и команды у него не запрашивали.

Это важная граница интерпретации: фраза «код отправляет файл» ниже означает наличие соответствующего пути выполнения, а не наблюдавшийся в сети успешный upload. Срабатывание зависит от прав, среды, зависимостей и ответа сервера. Неизвестные действия оператора нельзя восстановить только по клиентскому коду.

Адреса инфраструктуры в статье обезврежены записью [.] и hxxp. Это индикаторы для сопоставления с журналами, а не ссылки для проверки браузером.

1. Архив содержал не только исходники

Оригинальный ZIP был сохранён, рядом создана побайтовая рабочая копия. SHA-256 исходника:

a82fa42e1d48fb20b5d534a4a3b62c5ade871a723a0b7107b550c25c40983164

До извлечения проверялись имена entries, выходы за рабочий каталог, символьные ссылки и другие особенности контейнера. В рабочей копии файлы получили неисполняемые режимы; исходные атрибуты сохранились в manifest. Сам оригинальный архив не изменялся.

В ZIP оказалось 558 entries, из них 346 файлов. Внутри лежала полноценная .git: конфигурация, hooks, refs, index и loose objects. Это существенно отличается от обычного архива tracked-файлов: вместе с исходниками получатель получает локальные механизмы поведения репозитория.

Git для чтения образца не запускался. Объекты распаковывались как данные: zlib, разбор заголовка типа и длины, проверка object ID, затем чтение commit/tree/blob. Были проверены все 268 объектов: 183 blob, 82 tree и три commit. Pack-файлов не было, поэтому для этой конкретной задачи не понадобился разбор pack/delta.

Содержимое трёх веток заметно различалось:

Ветка

Файлов

Пустых

Что оказалось внутри

main

51

48

Почти пустой проект и README с инструкцией

video

3

2

Один непустой MP4 и два пустых видеофайла

staging

189

1

Код приложения и второй загрузчик

Проверка только видимого рабочего дерева дала бы неполную картину. Полезный для атаки код лежал и в локальной .git, и в другом дереве истории. При этом timestamps и reflog из архива описывают историю самого артефакта — их нельзя автоматически считать следами действий на компьютере получателя.

2. Почему просьба переключить ветку оказалась точкой запуска

В .git/hooks/ обнаружился активный post-checkout, 826 байт. В ZIP у него были исполняемые Unix-атрибуты. Остальные проверенные hooks либо отсутствовали, либо имели суффикс .sample.

В обычном репозитории Git ищет hooks в $GIT_DIR/hooks; настройка core.hooksPath может изменить этот путь. post-checkout вызывается после обновления рабочего дерева при checkout или switch и получает прежний HEAD, новый HEAD и флаг переключения ветки либо извлечения файла. Это штатное поведение, описанное в документации Git.

В исследованном .git/config перенаправления core.hooksPath и init.templateDir не было. Оно и не требовалось: вредоносный файл уже лежал по стандартному пути. README предлагал выполнить git checkout video, а hook не проверял ни имя ветки, ни три переданных аргумента. Следовательно, конкретное слово video служило поводом для действия пользователя, но не ограничивало сам триггер.

Логика hook была небольшой:

Результат uname -s

Что делает hook

Darwin

Получает bootstrap через curl и передаёт ответ Bash

Linux

Получает bootstrap через wget и передаёт ответ Bash

MINGW/MSYS/CYGWIN

Запускает PowerShell; ответ Invoke-RestMethod передаётся в Invoke-Expression

Прочее

Завершает работу

Источник доставки — nexorolabs[.]store, пути /api/terminal/bootstrap и /api/terminal/windows. Для Unix-платформ в query передаётся os=mac или os=linux, во всех трёх случаях — flag=3. Точный смысл этого флага по клиентскому коду не установлен.

Комментарии в файле создавали впечатление отключённого или фонового поведения, однако код был активен. В hook нет оператора &: сам он выполняет цепочку синхронно. Вывод интерпретатора перенаправлен, а платформенные ветки заканчиваются exit 0. Уже последующая стадия отделяет фоновые процессы от родителя.

Здесь полезно различать Git-команду и содержимое репозитория. Сам checkout не обязан быть вредоносным, но в переданной вместе с ZIP локальной конфигурации есть исполняемый обработчик его события. Обычный clone не переносит чужой .git/hooks как tracked-файл; архив готового рабочего каталога может его содержать.

Доказательства: README.md:13–25, .git/hooks/post-checkout:40–57, .git/config:1–11, атрибуты ZIP entry. Наличие и логика hook подтверждены кодом; его исполнение у получателя не наблюдалось.

Схема цепочки: ZIP с .git, checkout, post-checkout, bootstrap, Node, worker и три модуля

Схема цепочки: ZIP с .git, checkout, post-checkout, bootstrap, Node, worker и три модуля

Схема 1. Основная цепочка и независимая точка входа в staging. Стрелки восстановлены по коду; динамический запуск не выполнялся.

3. Второй загрузчик скрывался в серверной конфигурации

Параллельно обнаружилась независимая цепочка в staging:

package.json: start/dev/server
    → server/app.js
    → server/config/cookie.js
    → server/config/.svn/env.module
    → внешний JavaScript

В начале cookie.js сначала загружается dotenv, а затем скрытый модуль:

require('dotenv').config();
require('./.svn/env.module');

В env.module URL представлен Base64-строкой. Декодирование как данных дало hxxps://vantaralabs[.]online/api/terminal/script?flag=3. После получения текста loader передаёт его в new Function("require", response)(require). Переданный require позволяет загруженному коду пользоваться Node-модулями с правами текущего процесса; sandbox в этой конструкции нет.

Имя .svn здесь — способ спрятать файл в неприметном каталоге. Оно не доказывает работу с Subversion. Имя cookie.js тоже не делает весь файл cookie-stealer: основная конфигурация cookies приложения и вредоносный require — разные участки кода.

Во включённых в архив package.json не было lifecycle scripts preinstall, install, postinstall и prepare. Это не делало проект безопасным: скрытый loader вызывался при старте сервера. Кроме того, lockfiles и содержимое зависимостей отсутствовали, поэтому их транзитивные install scripts в этом исследовании не оценивались.

Тело payload с vantaralabs[.]online получить не удалось в рамках разрешённого сетевого этапа. Считать его копией payload с первого домена было бы предположением. Подтверждён сам второй механизм удалённого исполнения и то, что dotenv вызывается раньше него: секреты конфигурации потенциально доступны загруженному модулю в этом процессе.

Доказательства: server/app.js:5, server/config/cookie.js:1–2, server/config/.svn/env.module:3–6 из дерева staging.

4. Что было за первым HTTP-запросом

Для первой цепочки были сохранены три платформенных bootstrap, общий connect-runner и основной JavaScript. Это были запросы на получение кода, а не запуск образца. HTTP redirects проверялись отдельно, bodies и headers сохранялись вместе с хешами. Такой запрос всё равно раскрывает серверу сетевые метаданные аналитического соединения; бесконтактным исследованием его называть нельзя.

Bootstrap проверяет наличие Node.js версии не ниже 19. Если подходящего runtime нет, Unix-варианты предусматривают загрузку Node v22.14.0 с официального nodejs.org в каталог вида:

~/.local/share/daam-demo/node/v22.14.0-<platform>-<arch>/

Windows-вариант читает список релизов, выбирает подходящий LTS и предусматривает административную распаковку MSI в %LOCALAPPDATA%/daam-demo/node/. Использование официального дистрибутива Node не делает цепочку легитимной: доверенный интерпретатор нужен для запуска недоверенного кода. В ходе анализа эти дистрибутивы не скачивались и не устанавливались.

Затем bootstrap сохраняет временный daam-connect-runner…js и запускает его через Node. Runner создаёт каталог daam-extra-* внутри os.tmpdir(), пишет worker.js и несколько логов. Новый процесс запускается с detached: true, отдельным рабочим каталогом и unref().

Сам worker получает текст с /api/terminal/script?flag=3 на www.nexorolabs[.]store и вызывает eval(code). Это свойство исследуемого кода: аналитик eval не использовал. После отделения worker закрытие исходного терминала уже не является надёжным способом остановить цепочку.

Полученный основной файл имел размер 4 508 584 байта и SHA-256:

51cc310df71e7d3461cb081ddea81b302a0cf97ea5b1cbf53a998e2b27c84c73

Это снимок ответа от 7 ноября 2026 года. Сервер мог выдавать другой код раньше, позже или в зависимости от параметров клиента. Обнаруженные в комментариях слова demo и DAAM не использовались как доказательство назначения или происхождения образца.

5. Деобфускация без выполнения декодера образца

Большой размер файла объяснялся в том числе таблицами строк, индексными обёртками, hex-представлением литералов и запутанным потоком управления. Для разбора использовался установленный TypeScript parser: файл читался как AST, а не импортировался как Node-модуль. Сворачивались только проверенные операции над литералами и разрешённые обращения к таблицам.

В строковом декодере обнаружились Base64 с нестандартным алфавитом и RC4. Эти преобразования были реализованы отдельно как операции над данными. Таблица содержала 26 315 строк; найденный сдвиг — 291, контрольное числовое значение — 574 728. На первом этапе удалось выполнить 29 402 статические замены.

Затем из конкатенаций строк были восстановлены программы, которые orchestrator передавал дочерним Node-процессам через stdin:

Аналитическое имя

Размер

Назначение

01-x.js.txt

55 841 байт

Браузеры, кошельки, Keychain, условный CDP-этап

02-N.js.txt

30 765 байт

Обход файловой системы и передача файлов

03-o.js.txt

96 195 байт

Socket.IO RAT, shell, дополнительный поиск файлов, clipboard

Эти имена назначены при извлечении. На заражённом компьютере отдельных файлов с такими именами может не быть. Три программы прошли синтаксический разбор без ошибок, но успешный parse не равен успешному запуску и не подтверждает все ветви поведения.

Для каждого преобразования сохранялись исходный offset, правило декодирования, полученный артефакт и его хеш. Неразрешённые выражения во вспомогательных шаблонах оставлялись явными placeholders. Такой подход позволяет проверить происхождение вывода и не перепутать восстановленный код с удобной, но уже изменённой аналитиком версией.

Orchestrator запускает три дочерних процесса с аргументами --max-old-space-size=4096 --no-warnings -. Для них используются pid.5.1.lock, pid.5.2.lock, pid.5.3.lock в os.tmpdir(). В файлах хранятся PID и время старта. Это учёт процессов и защита от дублирования, а не механизм запуска после перезагрузки.

6. Браузерные данные: что забирается, а что осталось недоказанным

Первый модуль ищет профили Chromium-браузеров на Windows, macOS и Linux, отдельно учитывая WSL. В активной логике есть чтение Login Data, Login Data For Account, Web Data, каталогов Local Extension Settings для 41 extension ID и Brave Local Storage/leveldb. На macOS предусмотрена также передача ~/Library/Keychains/login.keychain-db.

Файлы копируются во временные каталоги, после чего отправляются multipart-запросами. Для содержимого расширений сохраняются метаданные: идентификатор браузера, профиль, extension ID и имя файла. Это позволяет принимающей стороне связать разрозненные базы и файлы с их источником.

Модуль также пытается получить browser encryption key из Local State: на Windows через DPAPI, на Linux через secret-tool или Python secretstorage, на macOS через security и Safe Storage. У каждой ветки есть условия и возможные ошибки. Сам факт наличия таких вызовов не означает, что ключ будет получен на любой системе.

Самая показательная находка — начало функции расшифровки паролей:

function decryptPassword(encryptedPassword, masterKey = null) {
  return "";
  // Дальше в образце находится недостижимая реализация.
}

Если остановиться на длинной реализации AES/DPAPI ниже, легко написать в отчёте о подтверждённой расшифровке всех паролей. Но первый return полностью меняет вывод: этим методом открытые пароли не извлекаются. При этом код продолжает собирать исходные базы, usernames, URL, зашифрованные значения в Base64 и master keys, если другие функции сумели их получить.

Поэтому корректная формулировка — «подтверждена отправка браузерных данных и попытка получения ключей; успешная расшифровка паролей данным методом не подтверждена». Возможность расшифровки на сервере отдельно не исследовалась: серверного кода нет. Аналогично копирование Keychain database не доказывает чтение всех её секретов в открытом виде.

В активных путях не найден отдельный дамп Cookies или Network/Cookies. Это не исключает попадания иных данных авторизации из extension storage, выбранных файлов или буфера обмена, но утверждение «ворует все cookies» выходило бы за пределы доказательств.

Доказательства: 01-x.js.txt:210–325,524–832,1453–1511. Номера строк относятся к восстановленному модулю, а не к исходному однострочному payload.

7. Файловый фильтр, который оказался почти универсальным

Второй модуль обходится без сложного парсинга форматов: он выбирает файлы по спискам исключений и совпадениям строк. Приоритет отдан Desktop, Documents, Downloads, каталогам разработки и облачной синхронизации. Затем обходятся HOME либо доступные Windows drives; есть обработка WSL. Ограничение на один файл — 5 MiB, глубина рекурсии ограничена.

В список «чувствительных» входят ожидаемые слова: credential, token, wallet, seed, private, key, .env, а также расширения исходников, документов и баз данных. Но проверяется не только имя файла — используется весь путь:

const lowerPath = filePath.toLowerCase();
return SENSITIVE_FILE_PATTERNS.some(pattern =>
  lowerPath.includes(pattern.toLowerCase())
);

Среди patterns есть user. Поэтому обычный путь /Users/alice/Documents/report.txt или C:\Users\alice\Documents\report.txt уже подходит по каталогу пользователя. Секретное слово в имени самого файла больше не нужно. Продолжают действовать исключённые каталоги и расширения, лимит размера, глубина и права доступа, однако фильтр «чувствительности» на таких путях становится очень широким.

У этого же collector есть исключения .ssh, .gnupg, .aws, .docker, .kube, .config и ряда других каталогов. Здесь нельзя сделать ни вывод «он автоматически крадёт все стандартные SSH/cloud stores», ни противоположный вывод «эти ключи защищены». Исключения относятся к одному обходчику. Копия ключа в Documents, значение в clipboard или файл, прочитанный оператором через RAT, остаются за их пределами. Кроме того, обработка symlink и проверка имени entry не образуют строгой границы доступа к данным.

Третий модуль после подключения отдельно ищет .env* и keypair.json, с другим лимитом — 10 MiB. Эти файлы уходят через отдельный upload endpoint. Имена и ограничения автоматических сборщиков важны для оценки вероятной утечки, но не описывают всё, что доступно оператору при наличии командной строки.

Доказательства: 02-N.js.txt:45–85,89–152,209–344,430–610; 03-o.js.txt:927–1145,1199–1225.

8. C2: три порта и несколько форматов данных

В восстановленных модулях непосредственно задан IP 144[.]172[.]118[.]245. Его роль определяется не репутацией адреса, а использованием в коде: туда отправляются файлы, регистрационные данные и журналы; с ним устанавливается управляющее соединение.

Порт

Endpoint

Назначение

8085

/upload

Браузерные базы, extension storage, Keychain и сопутствующие файлы

8085

/api/upload-file

Файлы по запросу и результаты дополнительного поиска

8086

/upload

Файлы из широкого автоматического обхода

8087

/api/notify

Регистрация host/user/OS

8087

/api/log

События и содержимое буфера обмена

8087

Socket.IO

Команды, shell и ответы

Загрузчики получают код по HTTPS, но показанные C2/exfil URLs используют HTTP и ws://. В запросах встречаются userkey или ukey со значением 503, t со значением 5, а также hostname, path, timestamp и validation. Эти константы зафиксированы как признаки образца; их значение в операторской системе не установлено.

Обезличенная структура регистрации выглядит так:

{
  "ukey": 503,
  "t": 5,
  "host": "503_<hostname>",
  "os": "<OS>",
  "username": "<username>",
  "timestamp": 0
}

Здесь 0 заменяет реальный timestamp. Заголовок validation вычисляется через HMAC-SHA256 с постоянным ключом из образца; сам ключ в публикацию не включён. Для разных endpoints подписываются разные строки: username, путь, имя файла либо часть metadata/message вместе со временем. HMAC не скрывает содержимое и не превращает HTTP в шифрованный канал.

Для управления используется библиотека Socket.IO. Это не произвольная строка JSON поверх любого WebSocket: у Socket.IO есть собственные события и framing поверх Engine.IO, который поддерживает WebSocket и HTTP long-polling. Описание протокола важно при подготовке лабораторного приёмника. В образце версия npm-зависимости не закреплена, поэтому точную wire version без проверки среды нельзя считать установленной.

Отдельно обнаружены delivery domains nexorolabs[.]store и vantaralabs[.]online. Наблюдавшийся при получении кода адрес shared hosting не включался в список «выделенных серверов оператора»: совместное размещение не доказывает владение IP и не оправдывает блокировку всего хостинга.

Доказательства: 01-x.js.txt:740–787, 02-N.js.txt:129–147, 03-o.js.txt:56–160,857–924,1152–1158.

Схема сетевых каналов: браузерные данные на 8085, файлы на 8086, команды и clipboard на 8087

Схема сетевых каналов: браузерные данные на 8085, файлы на 8086, команды и clipboard на 8087

Схема 2. Назначение каналов одного образца. HMAC подписывает данные, но не скрывает их содержимое.

9. Почему это полноценный удалённый доступ

У RAT есть handler события command. Он извлекает из сообщения поле message и передаёт его в child_process.exec. Allowlist команд в этом месте не найден. Выполнение происходит с правами текущего пользователя, результат возвращается событием message.

Некоторые значения code включают дополнительную обработку: 102 — перечисление каталога, 107 — отправка выбранного файла после выполнения команды, 108 — передача файлов из указанного каталога. Для последней операции установлен лимит 25 MiB на файл. Это отдельные ветки, а не ограничение общего exec тремя безопасными командами.

Помимо разовых команд, реализованы интерактивные сессии:

Событие

Действие

shellOpen

Создать или возобновить shell с указанным cwd и размером терминала

shellInput

Передать ввод процессу

shellResize

Изменить размер PTY, если backend это поддерживает

shellDetach

Отсоединить отображение, сохранив процесс

shellClose

Завершить сессию

shellOutput

Вернуть оператору вывод

Код предусматривает несколько PTY/backend вариантов и обычные pipes как fallback. Сессии и ограниченный scrollback хранятся в памяти. Таким образом, слово «persistent» в комментарии к shell sessions означает сохранение живой сессии при отсоединении, а не автозапуск после перезагрузки.

Здесь не нужен установленный SSH-сервер или открытый входящий порт. Узел сам устанавливает исходящее соединение, получает события и отдаёт результат. Интерфейс оператора и серверная авторизация в исследованном наборе отсутствуют; восстановлены клиентские возможности, а не устройство всей инфраструктуры.

В том же модуле есть polling clipboard примерно раз в секунду. На macOS используется pbpaste, на Windows — PowerShell и .NET Clipboard, на Linux — доступные утилиты либо WSL-вариант. Изменившийся непустой текст передаётся как сообщение в /api/log. Это подтверждённый сбор буфера обмена, но не keylogging: перехвата нажатий клавиш в найденной реализации нет.

Доказательства: 03-o.js.txt:1244–1486,1561–2335,2366–2450. Специальные функции снятия скриншотов, записи экрана, RDP/VNC или установки SSH-backdoor не подтверждены. Произвольная shell позволяет запускать дополнительные программы, но будущие действия оператора нельзя выдавать за уже найденные модули.

10. Persistence: три разных механизма, которые нельзя смешивать

Первый механизм повторного запуска — сам Git hook. Он находится вне tracked tree и остаётся при переключении ветки. Второй — заражённый server loader, который снова активируется при старте соответствующей версии проекта. Фоновые Node-процессы дают ещё одно свойство: они могут пережить завершение исходного терминала. Ни один из этих фактов сам по себе не доказывает старт RAT после reboot.

В браузерном модуле есть отдельная ветка macOS. При Darwin kernel release не ниже 27.0.0 код пытается создать:

~/Library/LaunchAgents/com.user.chrome.cdp.plist

Plist содержит RunAtLoad=true, KeepAlive=false и аргументы headless-браузера, включая --remote-debugging-port=9222. Используются временный профиль и локальный Chrome DevTools Protocol, через который браузер участвует в чтении файлов. Условие сравнивает именно Darwin release, а не маркетинговый номер macOS.

Функция остановки вызывает launchctl bootout, но удаления plist в ней нет. Поэтому после успешной установки может остаться запись для последующего запуска браузера при входе пользователя. При этом ProgramArguments указывают на браузер, а не на Node RAT: утверждение «найден полноценный автозапуск трояна после перезагрузки» было бы чрезмерным.

Есть и практические ограничения ветки: наличие подходящего браузера, зависимости, права и успешность доступа. Например, код требует puppeteer-core, а fallback-команда установки упоминает puppeteer; последствия зависят от фактического окружения и разрешения модулей. В этой работе ветка не исполнялась.

Установки cron, systemd, SSH authorized_keys, Windows Run/RunOnce, Scheduled Tasks или служб в полученных активных стадиях не обнаружены. Это отрицательный результат по конкретному снимку кода, а не гарантия для машины, на которой оператор уже получил shell.

Доказательства: 01-x.js.txt:833–1080,1414–1445; orchestrator и runner для detached Node-процессов.

11. Что искать на других машинах

Наиболее сильный признак — сочетание файловых артефактов, происхождения процессов и сетевых событий. Один скачанный архив или одно имя node не доказывает активную инфекцию. Аналогично пустой результат поиска по хешу не исключает другую версию удалённого payload.

Признак

Где искать

Ограничение

Hook с nexorolabs и передачей ответа интерпретатору

.git/hooks/post-checkout, effective hooksPath

Наличие в карантине не означает запуск

cookie.js → .svn/env.module

Рабочее дерево и история проекта

Проверка только main пропустит staging

daam-extra-*, worker.js, worker.log, worker.out.log, worker.err.log

Пользовательский временный каталог

Следы могут быть частично удалены

pid.5.1.lock, pid.5.2.lock, pid.5.3.lock

os.tmpdir()

PID мог устареть или быть переиспользован

Node с --max-old-space-size=4096 --no-warnings -

Process telemetry, parent/cwd

Аргументы сами по себе не уникальны

com.user.chrome.cdp.plist и CDP-профиль

LaunchAgents, временные каталоги, browser arguments

Требуется сопоставление содержимого

144[.]172[.]118[.]245:8085–8087 и описанные endpoints

EDR, proxy, firewall, pcap

Отсутствие соединения сейчас не исключает прошлую утечку

Для этого снимка полезны следующие SHA-256. Первые два относятся к локальным точкам запуска, третий — к полученному внешнему коду:

post-checkout
bfc3ef0167e1c66f77c0117c1fe13def2c364bfa078392b042284746762cacb6

staging: server/config/.svn/env.module
c44786363e351de613b01437d930d8a08162f6f2cd18588b2026afa099dfdbd7

полученный основной JavaScript
51cc310df71e7d3461cb081ddea81b302a0cf97ea5b1cbf53a998e2b27c84c73

При проверке других repositories нужно учитывать .git/config, user/system configs, includes, core.hooksPath и используемые templates. Отсутствие перенаправления hooks в одном архиве ничего не говорит о глобальной настройке другой машины. Делать checkout подозрительного проекта ради поиска другого branch опасно: для этого образца такая «проверка» совпадает с триггером.

В терминах ATT&CK здесь уместны Event Triggered Execution, JavaScript execution, Ingress Tool Transfer, Credentials from Web Browsers, Clipboard Data и условный Launch Agent. Такое сопоставление описывает поведение; оно не является атрибуцией группе или семейству.

12. Как продолжать исследование, не отправляя данные оператору

После статического этапа был подготовлен локальный mock C2: приёмник HTTP-запросов и минимальных служебных сообщений Engine.IO/Socket.IO. Он сохраняет timestamps, headers и raw payload, но не выдаёт command, shellInput или другие задания. Проверка проводилась синтетическими вызовами обработчиков внутри процесса — без запуска malware и без открытия listener.

Для настоящего динамического опыта необходима отдельная disposable VM со snapshot. У неё не должно быть реальных браузерных профилей, SSH/API/cloud credentials, shared folders, clipboard sharing, доступа к host filesystem или маршрутов в production/LAN. Наружный IPv4/IPv6 egress блокируется независимо от того, насколько правильно в лабораторной копии заменили URL.

В этой цепочке C2 задан literal IP, поэтому одной DNS-подмены недостаточно. Лабораторная копия должна обращаться к контролируемому локальному transport; исходник сохраняется отдельно, а изменения фиксируются diff и хешами. Наблюдение включает process tree, filesystem diff, DNS/HTTP logs и pcap. Тестовые файлы с уникальными маркерами позволяют проверить сбор, не используя настоящие секреты.

Минимальный mock не следует выдавать за полную эмуляцию сервера. Поддержка polling handshake ничего не доказывает о совместимости всех версий, binary frames или поведения при обрыве связи. Здесь также не проверялись runtime-эффекты TCC, DPAPI, Keychain и macOS LaunchAgent. Эти вопросы остались в плане лаборатории, а не были заменены предположениями.

13. Что делать, если проект уже запускали

Для случая «только скачал ZIP» найденная цепочка не даёт основания утверждать заражение или требовать немедленной ротации всех секретов. Достаточно не активировать проект и сохранять артефакт в карантине до решения, нужен ли он для расследования. Это относится именно к разобранным механизмам и не заменяет отдельный аудит компьютера.

Если checkout, server или предложенные installers выполнялись, порядок действий другой:

  1. Изолировать host от внешней сети и LAN, сохранить volatile evidence и копию диска/репозитория. Новые credentials вводить с чистой станции.

  2. Сопоставить процессы с worker paths, PID locks и сетевыми IOC. Не завершать все Node или браузеры только по имени: важны родитель, cwd, время старта и соединения.

  3. Остановить подтверждённую цепочку и дочерние shells. У file collector есть обработчики SIGTERM/SIGINT без обычного завершения, поэтому graceful stop может оказаться недостаточным.

  4. Закрыть повторный запуск: malicious hook, скрытый server loader, а при наличии — проверенный CDP LaunchAgent. Сначала сохранить доказательства; одного bootout без удаления подтверждённого plist недостаточно.

  5. Проверить остальные persistence locations и восстановить рабочий проект из доверенного источника. Удаление строки в одной ветке не очищает заражённую историю и не отменяет уже выполненные команды оператора.

  6. С чистого устройства отозвать сессии и заменить секреты, которые были доступны host в период риска: browser accounts, tokens в конфигурации и clipboard, ключи и сертификаты. Доступ к wallet seed/private material требует отдельных действий с новыми ключами; смена пароля браузера старый seed не меняет.

После реально работавшего RAT исключение .ssh из одного автоматического collector не позволяет признать SSH-ключи безопасными. Если нет достоверной истории команд и невозможно восстановить все изменения, переустановка из доверенного baseline даёт более сильную гарантию, чем удаление нескольких известных файлов.

Где заканчиваются выводы этого разбора

Удалось установить две точки запуска, получить и разобрать одну цепочку внешних стадий, восстановить три основных модуля и описать их C2, сбор данных и удалённую shell. Не получены серверная часть, история команд оператора и тело второго payload с vantaralabs[.]online; не выполнялся динамический анализ. Нет оснований приписывать этот образец конкретной компании, группе или известному семейству только по именам в README, Git remote или комментариях.

Практический урок этого случая — проверять весь полученный артефакт до первого «служебного» действия. Исполняемая поверхность проекта начинается не обязательно с npm install или кнопки Run. Если вместе с исходниками приехала чужая .git, инструкция «просто переключите ветку» уже может пересечь границу доверия.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.