GlassWorm – Malware Analysis

It started with code that wasn’t there.

While reviewing a set of Git repositories during a penetration test, we noticed something unusual: files that appeared clean in the editor but behaved differently at runtime. The only visible clue was a mismatch in character counts.

That discrepancy led to the discovery of a stealthy malware campaign we now track as GlassWorm. A multi-stage Node.js implant that hides in plain sight using invisible Unicode characters and retrieves its command-and-control infrastructure via the Solana blockchain and BitTorrent DHT.

What initially looked like a minor anomaly turned out to be a widespread supply chain attack affecting over 100 public repositories.

At the time of publication, we detected the code in approximately ~100 public repositories on GitHub.

“Invisible” Code

The malicious code hides itself using invisible Unicode characters from the ranges U+E0000–U+E007F and U+FE00–U+FE0F. These characters are not rendered by most text editors, making them difficult to spot during manual review. In Visual Studio Code, the presence of the code is only visible through the character selection count — the actual content remains invisible.

Malicious code injected into a git repository
Invisible Unicode characters

Once loaded, the program decodes the hidden characters into valid JavaScript and executes it using the eval() function. The result is a generator that first waits 500 ms, then decrypts and executes the next stage using AES-256-CBC. The key and initialization vector are hardcoded directly in the code.

Dynamic C2 Resolution

Rather than hardcoding a C2 server address, GlassWorm retrieves it dynamically — from the metadata of the latest transaction on the Solana wallet BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC. The value of the link field in the metadata is a Base64-encoded URL that, once decoded, points to http://217.69.11.99/axtyituCswaN5AamyO692g==.

The server returns the next-stage payload tailored to the victim’s operating system (Windows, macOS, or Linux). Before execution, it checks whether at least 2 days have passed since the initial infection, which slows down sandbox analysis. The code is additionally protected: Base64-encoded and AES-256-CBC encrypted, with the key and IV passed in the HTTP response headers (ivbase64 / secretkey). It appears to execute in isolation using Node.js’s vm module. Further analysis reveals, however, that references to console and process are passed via context — system execution remains.

Execution automatically terminates if the program detects it is running on a system with Russian language settings or timezone.

Data Theft

Windows

The Windows payload is extensive and largely composed of legitimate libraries. The malicious portion performs the following activities:

  • Downloading and decrypting additional modules from server 217.69.11.99
  • Theft of npm and git credentials
  • Data theft from crypto wallets (desktop apps and browser extensions)
  • Theft of saved passwords from password managers
  • Downloading and executing a .NET binary, if Ledger Live is installed on the system

The program downloads a ZIP archive from the server 217.69.11.99 using the following path: /get_arhive_npm/1B89Udneo8JJwdQlbr1w6w==. The archive is saved to %TMP%\kkXqPAFp and extracted to %TMP%\rKeKMWikl. Two files are then decrypted using AES-128-CBC:

  • %TMP%\kkXqPAFp\w.node -> decrypted and written to %TMP%\cTdTMfItnz\tlESkme
  • %TMP%\rKeKMWikl\f_ex86.node ->hash is verified using SHA-256, decrypted and written to %TMP%\cTdTMfItnz\WujOBGtse

Both decryption operations use the following parameters (Base64-decoded before use):

Key: caTY0D6roK1LHa02cA80jA==
IV:     /sXfxpU5Dr0kuXoihE6JFQ==

Stolen data is exfiltrated to http://208.85.20.124/wall via POST.

.NET Binary (ggsuuck.exe)

If a Ledger Live installation is detected, a .NET binary is downloaded from http://217.69.11.99/led-win32 and saved as %TMP%\ggsuuck.exe. The file is added to the startup programs via a PowerShell command writing to the registry key HKCU\Software\Microsoft\Windows\CurrentVersion\Run with the value UpdateLedger.

The application pretends to be part of Ledger Live software suite and prompts the user to enter their 24-word recovery phrase, which it validates against an internal dictionary (Assaac.WordBookHealth.thisRectangleWord). The entered values are transmitted to 45.150.34.158:8080 over a TCP connection.

Validation and transmission of the 24-word phrase

Before displaying the window, the program checks the victim’s location via https://ipapi.co/xml. Execution is aborted for systems located in: Armenia, Russia, Kyrgyzstan, Moldova, Tajikistan, Uzbekistan, or Kazakhstan.

Victim location retrieval and comparison

macOS

The macOS variant is structurally similar to the Windows version. The malicious code begins at line 1380, preceded by the legitimate yauzl library for ZIP archive handling.

The FileGrabber.run() function collects:

  • Keychain file
  • Cookies and passwords from Safari, Firefox, and Chrome
  • User notes (Notes app)
  • SSH and AWS configuration files (.ssh, .aws)
  • Documents from the Documents folder
  • Data from browser extensions (MetaMask, Phantom, Exodus, Ledger …)
    FileGrabber.run() function

    Collected files are copied to /tmp/ijewf/. After FileGrabber.run() completes, an AppleScript script is launched via an osascript subprocess. The script attempts to extract the Chrome password executing the command security find-generic-password -ga “Chrome”. Passwords are saved to /tmp/ijewf/pwd. If extraction fails, a fake system dialog is displayed prompting the user for manual authentication.

    Collected data is archived and exfiltrated to http://208.76.223.59/p2p via POST, using non-standard HTTP headers:

    • uuid: 57f432c1-c5b7-4b51-8667-044847c63470
    • buildid: 2026-03-06T07:35:05.258Z
    • uuid_machine: dynamically obtained via the ioreg command
    Data exfiltration (POST with non-standard HTTP headers)

    For persistence, the script installs the Node.js v23.5.0 runtime to ~/.config/system/.data/.nodejs/ and creates a LaunchAgent at ~/Library/LaunchAgents/com.user.nodestart.plist, which executes Base64-encoded JavaScript on system startup.

    Persistence — LaunchAgent and Node.js installation

    Finally, via the functions ZOZgdRcMWJ(), GgFYYlKGIY(), and envExfiltration(), git credentials, npm keys, SSH keys, and files from .vscode directories are stolen. The collected data is archived to /tmp/h.zip and sent to http://208.85.20.124/wall.

    Possible indicator of Russian origin: a comment in the code responsible for stealing GitHub SSH keys is written in Russian.

    ZOMBI — Persistent C2 Component

    ZOMBI is a JavaScript component that operates independently of any fixed C2 address. It implements two mechanisms for dynamically resolving the IP address of the attacker’s server.

    C2 Address Resolution

    The primary mechanism is the BitTorrent DHT protocol. ZOMBI connects to three public servers and queries for a value signed with a hardcoded ed25519 public key. The signature is verified using the crypto_sign_verify_detached function, ensuring the device only connects to the attacker’s server. The IP address is refreshed every 50 seconds.

    • libtorrent.org:25401
    • bittorrent.com:6881
    • utorrent.com:6881
    DHT client initialization with public key

    The secondary mechanism falls back to the latest transaction on Solana wallet BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC. The retrieved value is a DHT node, which is added to the client for a new query. A following list of public Solana RPC endpoints is used to access the network:

    • 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/
    Interval-based C2 IP resolution

    Capabilities

    Once the C2 address is resolved, ZOMBI connects to a WebSocket on port 4789. The program includes the value _partner: “mulKRsVtolooY8S” in outgoing requests. Attackers can execute the following commands:

    • Execution of arbitrary JavaScript code via eval().
    • Uploading binary files to the infected device.
    • SOCKS Proxy Server functionality—the infected device effectively becomes part of the attack infrastructre.
    WebSocket message handlers

    Indicators of Compromise (IOCs)

    Network IOCs

    Type Value
    IP 217.69.11.99
    URL http://217.69.11.99/axtyituCswaN5AamyO692g==
    URL http://217.69.11.99/get_arhive_npm/1B89Udneo8JJwdQlbr1w6w== — ZIP archive (Win)
    URL http://217.69.11.99/led-win32
    IP 208.85.20.124 — exfiltration /wall
    IP 208.76.223.59 — exfiltration /p2p
    IP:port 45.150.34.158:8080
    WebSocket [C2]:4789 — ZOMBI C2 channel

    Blockchain / DHT

    Type Value
    Solana wallet BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC
    DHT dht.libtorrent.org:25401
    DHT router.bittorrent.com:6881
    DHT router.utorrent.com:6881

    Files and Paths — Windows

    Type Value
    Directory %TMP%\kkXqPAFp
    Directory %TMP%\rKeKMWikl
    File %TMP%\kkXqPAFp\w.node
    File %TMP%\rKeKMWikl\f_ex86.node
    File %TMP%\cTdTMfItnz\tlESkme
    File %TMP%\cTdTMfItnz\WujOBGtse
    File %TMP%\ggsuuck.exe
    File %HOME%\init.json
    Reg. key HKCU\Software\Microsoft\Windows\CurrentVersion\Run → UpdateLedger

     Files and Paths — macOS

    Type Value
    Directory /tmp/ijewf/
    File /tmp/ijewf/pwd
    File /tmp/h.zip
    Directory ~/.config/system/.data/.nodejs/
    File ~/Library/LaunchAgents/com.user.nodestart.plist
    File ~/init.json

    Cryptographic Artifacts

    Type Value
    AES-256-CBC key zetqHyfDfod88zloncfnOaS9gGs90ONX
    AES-256-CBC IV a041fdaa0521fb5c3e26b217aaf24115 (hex)
    AES-128-CBC key caTY0D6roK1LHa02cA80jA==
    AES-128-CBC IV /sXfxpU5Dr0kuXoihE6JFQ==
    SHA-256 (f_ex86.node) a89dd9e5c813bf66d0738a70a616d97f6438917c423de347560e5ed2db3df5ff

    Other Artifacts

    Type Value
    HTTP header uuid: 57f432c1-c5b7-4b51-8667-044847c63470
    HTTP header buildid: 2026-03-06T07:35:05.258Z
    HTTP header uuid_machine: dynamic (ioreg)
    WebSocket token _partner: mulKRsVtolooY8S

    Conclusion

    GlassWorm is a multi-stage campaign that exploits developer trust in open-source ecosystems. What makes it particularly concerning is the combination of advanced techniques — steganographic hiding using invisible Unicode characters, C2 resolution via blockchain and DHT, and a modular architecture that allows attackers to remotely upgrade capabilities. Its presence across ~100 public repositories points to a systematic campaign rather than an isolated incident.

    We recommend organizations audit their npm dependencies and git history for invisible Unicode characters, monitor network traffic to the identified IP addresses, and check for the presence of an init.json file in the home directory on developer machines.