Skip to content
JKULDEV

A malware loader that hid its command server on a blockchain

Mohan Singh Thagunna8 min read
Illustration for “A malware loader that hid its command server on a blockchain”

A launch agent found on a working Mac read its command-and-control address out of a smart contract on Polygon, then piped the reply straight into osascript. Storing the address on-chain makes it impossible to take down by seizing a domain.

While testing a startup-item audit, we found a live one. It is worth describing, because the technique defeats the usual way this kind of thing is blocked.

What was on disk

A single file: ~/Library/LaunchAgents/com.wbrdlwfuqzzhjqtv.plist. A launch agent with RunAtLoad and KeepAlive set — it runs at every login and restarts if killed — whose entire job was:

/bin/bash -c "echo '<13 KB of base64>' | base64 -d | osascript"

Decoded, that blob was AppleScript with every string assembled character by character, so searching the file for a domain or a URL finds nothing.

What it did

  1. Called a public Polygon RPC endpoint — several, for redundancy.
  2. Performed an eth_call against a smart contract, reading a stored value.
  3. Decoded that value into a hostname: its command-and-control address, held on a public blockchain.
  4. POSTed to that address and piped the response directly into osascript.

That last step is arbitrary remote code execution, refreshed at every login.

Why the blockchain part matters

Normally you disrupt this by taking the server away: seize the domain, null-route the IP, block it at DNS. Every one of those assumes the address lives somewhere that can be seized.

Here it lives in a smart contract. Reading it needs no credentials and no special client — any public RPC endpoint will serve it. There is no registrar to contact and no host to notify. The operator updates the contract and every infected machine follows to the new server at the next login.

The technique is sometimes called EtherHiding.

How you find something like this without a signature

You cannot match on a name: the label was random and the strings were built at runtime. What you can match is the shape:

  • a startup item that decodes a blob and pipes it into an interpreter
  • one that downloads code at run time and executes it without writing it to disk
  • an oversized inline payload where configuration should be
  • a label that is keyboard mash dressed up as a bundle identifier

Any one of those alone is innocent — plenty of legitimate updaters use bash -c, and a locally built tool is often unsigned. Requiring two independent signals is what keeps Zoom's updater, Oracle's Java helper and Microsoft AutoUpdate out of the results while still catching this.

What to do if you find one

launchctl bootout gui/$UID/<label>
rm ~/Library/LaunchAgents/<label>.plist

Then restart — and treat every credential used on that Mac since the file appeared as exposed. A loader fetches whatever its operator chooses, so what it actually did cannot be read from the file.

Reclaim reports items like this. It does not remove them: an automated remover that guesses wrong breaks working machines, and the evidence is usually enough for you to decide in a few seconds.

Mentioned in this postReclaimFind what is actually taking your disk space on a Mac.

Read next