Začelo se je s kodo, ki je ni bilo.
Med pregledom Git repozitorijev v okviru penetracijskega testiranja smo opazili nekaj nenavadnega: datoteke, ki so v urejevalniku izgledale povsem običajne, vendar so se ob izvajanju obnašale drugače. Edini vidni namig je bilo neskladje v številu znakov.
To odstopanje nas je pripeljalo do odkritja prikrite zlonamerne kampanje, ki jo danes spremljamo pod imenom GlassWorm. Gre za večstopenjski Node.js implant, ki se skriva na očeh z uporabo nevidnih Unicode znakov ter pridobiva naslov svojega nadzornega strežnika (C2) prek verige blokov Solana in BitTorrent DHT omrežja.
Kar se je sprva zdelo kot manjša anomalija, se je izkazalo za razširjen napad na dobavno verigo, ki je prizadel več kot 100 javnih repozitorijev.
V času objave tega članka smo kodo zaznali v skupno ~100 javnih repozitorijih na platformi GitHub.
“Nevidna” koda
Zlonamerna koda se skriva z nevidnimi Unicode znaki iz obsegov U+E0000–U+E007F in U+FE00–U+FE0F. Ti znaki se v večini urejevalnikov besedila ne prikažejo, zato jih ob ročnem pregledu težko zaznamo. V Visual Studio Code je prisotnost kode razvidna zgolj iz števila označenih znakov — vsebina sama ostane nevidna.


Ko je koda enkrat naložena, program dekodira skrite znake v veljavno JavaScript kodo in jo izvede z eval(). Rezultat je generator, ki najprej počaka 500 ms, nato pa s pomočjo algoritma AES-256-CBC dešifrira in izvede naslednjo stopnjo. Ključ in inicializacijski vektor sta kodirana neposredno v kodi.
Dinamična resolucija C2
Namesto da bi bil naslov C2 strežnika kodiran v kodi, ga GlassWorm pridobi dinamično — iz metapodatkov zadnje transakcije na Solana denarnici BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC. Vrednost polja link v metapodatkih je Base64-kodiran URL, ki po dekodiranju kaže na http://217.69.11.99/axtyituCswaN5AamyO692g==.
Strežnik vrne naslednjo stopnjo zlonamernega programa, prilagojenega operacijskemu sistemu žrtve (Windows, macOS, Linux). Pred izvajanjem preveri, ali je od prve okužbe minilo vsaj 2 dni, s čimer upočasni analizo v peskovnikih (angl. sandbox). Koda je dodatno zaščitena: Base64-kodirana in AES-256-CBC šifrirana, ključ in IV pa sta posredovana v HTTP-glavah odgovora (ivbase64 / secretkey). Navidezno se izvaja izolirano z uporabo Node.js vm modula. Nadaljnja analiza pokaže, da so reference do console in process omogočene z deljenim kontekstom — sistemski ukazi ostanejo dosegljivi.
Izvajanje se samodejno preneha, če program zazna, da teče na sistemu z ruskimi jezikovnimi nastavitvami ali časovnim pasom.
Kraja podatkov
Windows
»Payload« za Windows je obsežen in v veliki meri sestavljen iz legitimnih knjižnic. Zlonamerni del izvaja naslednje aktivnosti:
- Prenos in dešifriranje dodatnih modulov s strežnika 217.69.11.99
- Kraja npm in git poverilnic
- Kraja podatkov iz kripto-denarnic (namiznih in brskalniških razširitev)
- Kraja shranjenih gesel iz upravljalnikov gesel
- Prenos in zagon .NET binarne datoteke, če je na sistemu nameščen Ledger Live
Program z uporabo HTTP-protokola s strežnika 217.69.11.99 prenese ZIP arhiv iz poti /get_arhive_npm/1B89Udneo8JJwdQlbr1w6w==. Arhiv se shrani v %TMP%\kkXqPAFp in ekstrahira v %TMP%\rKeKMWikl. Iz njega se dešifrirata dve datoteki z AES-128-CBC:
- %TMP%\kkXqPAFp\w.node -> dešifriran in zapisan v %TMP%\cTdTMfItnz\tlESkme
- %TMP%\rKeKMWikl\f_ex86.node ->preverjena veljavnost SHA-256 zgoščene vrednosti, dešifriran in zapisan v %TMP%\cTdTMfItnz\WujOBGtse
Za obe dešifriranji se uporabita naslednja parametra (Base64-dekodirana pred uporabo):
Ključ: caTY0D6roK1LHa02cA80jA==
IV: /sXfxpU5Dr0kuXoihE6JFQ==
Ukradeni podatki se pošljejo na http://208.85.20.124/wall s POST-metodo.
.NET binarna datoteka(ggsuuck.exe)
Ob zaznani namestitvi Ledger Live se iz http://217.69.11.99/led-win32 prenese .NET binarna datoteka, shranjena kot %TMP%\ggsuuck.exe. Datoteka se doda med programe ob zagonu naprave preko PowerShell ukaza v registrski ključ HKCU\Software\Microsoft\Windows\CurrentVersion\Run z vrednostjo UpdateLedger.
Aplikacija se pretvarja, da je del programske opreme Ledger Live. Od uporabnika zahteva vnos 24-besedne obnovitvene fraze, ki jo verificira s pomočjo internega slovarja (Assaac.WordBookHealth.thisRectangleWord). Vnesene vrednosti se prenesejo na 45.150.34.158:8080 preko TCP-povezave.

Pred prikazom okna program preveri lokacijo žrtve preko https://ipapi.co/xml. Izvajanje se prekine za sisteme, ki se nahajajo v: Armeniji, Rusiji, Kirgizistanu, Moldaviji, Tadžikistanu, Uzbekistanu ali Kazahstanu.

macOS
Različica za macOS je po strukturi podobna različici za Windows. Zlonamerna koda se začne v vrstici 1380, pred njo je legitimna knjižnica yauzl za upravljanje z ZIP arhivi.
Funkcija FileGrabber.run() zbere:
- Keychain datoteko
- Piškote in gesla iz brskalnikov Safari, Firefox in Chrome
- Uporabnikove zapiske (Notes)
- SSH in AWS konfiguracijske datoteke (.ssh, .aws)
- Dokumente iz mape Documents
- Podatke iz 150+ brskalniških razširitev (MetaMask, Phantom, Exodus, Ledger …)

Zbrane datoteke se kopirajo v /tmp/ijewf/. Po izvajanju FileGrabber.run() se zažene AppleScript preko osascript podprocesa. Skripta poskuša pridobiti geslo Chrome preko ukaza security find-generic-password -ga “Chrome”. Gesla se shranijo v /tmp/ijewf/pwd. Če pridobivanje ni uspešno, se prikaže lažno sistemsko okno za ročni vnos.
Zbrani podatki se arhivirajo in pošljejo na http://208.76.223.59/p2p s POST metodo, z nestandardnimi HTTP-glavami:
- uuid: 57f432c1-c5b7-4b51-8667-044847c63470
- buildid: 2026-03-06T07:35:05.258Z
- uuid_machine: dinamično pridobljeno z ukazom ioreg

Za zagotavljanje obstojnosti skripta namesti izvajalno okolje Node.js v23.5.0 v ~/.config/system/.data/.nodejs/ in ustvari LaunchAgent v ~/Library/LaunchAgents/com.user.nodestart.plist, ki ob zagonu sistema izvaja Base64-kodirano JavaScript kodo.

Na koncu se preko funkcij ZOZgdRcMWJ(), GgFYYlKGIY() in envExfiltration() ukradejo git poverilnice, npm ključi, SSH ključi in datoteke iz .vscode map. Zbrani podatki se arhivirajo v /tmp/h.zip in pošljejo na http://208.85.20.124/wall.
Indikator ruskega izvora: komentar v kodi za krajo GitHub SSH ključev je v ruščini.
ZOMBI — Vztrajna C2 komponenta
ZOMBI je JavaScript komponenta, ki deluje neodvisno od fiksnega C2 naslova in implementira dva mehanizma za dinamično resolucijo IP-ja napadalčevega strežnika.
Resolucija C2 naslova
Primarni mehanizem je BitTorrent DHT protokol. ZOMBI se poveže na tri javne strežnike in poizveduje po vrednosti, podpisani z vgrajenim ed25519 javnim ključem. Podpis se verificira s funkcijo crypto_sign_verify_detached, kar zagotavlja, da se naprava poveže le na napadalčev strežnik. IP-naslov se osvežuje na vsakih 50 sekund.
- libtorrent.org:25401
- bittorrent.com:6881
- utorrent.com:6881

Sekundarni mehanizem izhaja iz zadnje transakcije Solana denarnice BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC. Pridobljena vrednost je vozlišče, ki se doda DHT klientu za novo poizvedbo. Za dostop do Solana omrežja se uporabi seznam javnih RPC končnih točk:
- https://go.getblock.us/86aac42ad4484f3c813079afc201451c
- https://solana-mainnet.gateway.tatum.io/
- https://solana-rpc.publicnode.com
- https://api.blockeden.xyz/solana/KeCh6p22EX5AeRHxMSmc
- https://sol-protect.rpc.blxrbdn.com/
- https://solana.drpc.org/
- https://solana.leorpc.com/?api_key=FREE
- https://solana.api.onfinality.io/public
- https://solana.api.pocket.network/
- https://api.mainnet-beta.solana.com
- https://public.rpc.solanavibestation.com/

Zmogljivosti
Ko je C2 naslov razrešen, se ZOMBI poveže na WebSocket na vratih 4789 in ob vzpostavitvi posreduje vrednost _partner: “mulKRsVtolooY8S”. Napadalcem omogoča izvajanje treh vrst ukazov:
- Izvajanje poljubne JavaScript kode z uporabo eval().
- Prenos binarnih datotek na okuženo napravo.
- Delovanje kot SOCKS proxy strežnik — okužena naprava postane del napadalčeve infrastrukture.

Indikatorji kompromitacije (IOC)
Omrežni IOC
| Tip | Vrednost |
| IP | 217.69.11.99 |
| URL | http://217.69.11.99/axtyituCswaN5AamyO692g== |
| URL | http://217.69.11.99/get_arhive_npm/1B89Udneo8JJwdQlbr1w6w== — ZIP arhiv (Win) |
| URL | http://217.69.11.99/led-win32 |
| IP | 208.85.20.124 — eksfiltracija /wall |
| IP | 208.76.223.59 — eksfiltracija /p2p |
| IP:port | 45.150.34.158:8080 |
| WebSocket | [C2]:4789 — ZOMBI C2 kanal |
Blockchain / DHT
| Tip | Vrednost |
| Solana denarnica | BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC |
| DHT | dht.libtorrent.org:25401 |
| DHT | router.bittorrent.com:6881 |
| DHT | router.utorrent.com:6881 |
Datoteke in poti — Windows
| Tip | Vrednost |
| Arhiv | %TMP%\kkXqPAFp |
| Imenik | %TMP%\rKeKMWikl |
| Modul | %TMP%\kkXqPAFp\w.node |
| Modul | %TMP%\rKeKMWikl\f_ex86.node |
| Modul | %TMP%\cTdTMfItnz\tlESkme |
| Modul | %TMP%\cTdTMfItnz\WujOBGtse |
| Datoteka | %TMP%\ggsuuck.exe |
| Datoteka | %HOME%\init.json |
| Reg. ključ | HKCU\Software\Microsoft\Windows\CurrentVersion\Run → UpdateLedger |
Datoteke in poti — macOS
| Tip | Vrednost |
| Direktorij | /tmp/ijewf/ |
| Datoteka | /tmp/ijewf/pwd |
| Datoteka | /tmp/h.zip |
| Direktorij | ~/.config/system/.data/.nodejs/ |
| Datoteka | ~/Library/LaunchAgents/com.user.nodestart.plist |
| Datoteka | ~/init.json |
Kriptografski artefakti
| Tip | Vrednost |
| AES-256-CBC key (S1) | zetqHyfDfod88zloncfnOaS9gGs90ONX |
| AES-256-CBC IV | a041fdaa0521fb5c3e26b217aaf24115 (hex) |
| AES-128-CBC key (S3, B64) | caTY0D6roK1LHa02cA80jA== |
| AES-128-CBC IV | /sXfxpU5Dr0kuXoihE6JFQ== |
| SHA-256 (f_ex86.node) | a89dd9e5c813bf66d0738a70a616d97f6438917c423de347560e5ed2db3df5ff |
Ostali artefakti
| Tip | Vrednost |
| HTTP glava | uuid: 57f432c1-c5b7-4b51-8667-044847c63470 |
| HTTP glava | buildid: 2026-03-06T07:35:05.258Z |
| HTTP glava | uuid_machine: dinamično (ioreg) |
| WebSocket token | _partner: mulKRsVtolooY8S |
Zaključek
GlassWorm je večstopenjska kampanja, ki izkorišča zaupanje razvijalcev v odprtokodne ekosisteme. Posebej zaskrbljujoča je kombinacija naprednih tehnik – prikrivanje z nevidnimi Unicode znaki, C2 resolucija prek blockchaina in DHT, ter modularna arhitektura, ki omogoča napadalcem daljinsko nadgradnjo zmogljivosti. Prisotnost v ~100 javnih repozitorijih kaže na sistematično kampanjo, ne na izolirani incident.
Organizacijam priporočamo pregled npm odvisnosti in git zgodovine na prisotnost nevidnih Unicode znakov, monitoring omrežnih klicev na identificirane IP-naslove ter preverjanje obstoja datoteke init.json v domačem imeniku na napravah razvijalcev.