By Jess
Yesterday I wrote about my friend getting caught out by a fake Minecraft mod. That post was about the scam. This one is about the malware, because I couldn't leave it alone.
Fair warning, this one gets technical. If you just want to know if you're affected and what to do, skip to Detection and clean-up near the bottom.
The short version
- The lure: fake Minecraft Fabric mods ("CuteCraft", "DonutSMP Dupe Mod") wrapped in a commercial obfuscator called qProtect.
- The trick: the malware doesn't have a server address baked in. It looks it up from encrypted records on the Ethereum blockchain, using ENS (Ethereum Name Service) names. This is called EtherHiding, and it's very hard to take down.
- The payload: a Java stealer and remote access tool that calls itself "ewa", with 67 commands. Think keylogging, webcam, hidden desktops, DDoS, the works.
- The scale: the blockchain history shows 53 C2 domains since July, 313 Discord webhooks, and somewhere around 240–300 customer builds. It's still growing by about four a day.
- How: I never ran it. I asked Claude to write little Java bytecode interpreter and we read the malware like a book instead.
A clean sandbox report that wasn't
It started with a public sandbox run (CAPE) of one of the mods. Java started, two icacls calls touched a file called .oracle_jre_usage, and nothing hit the network. Those icacls calls are just Oracle's Java doing its thing. Nothing to see here.
Except there was. A Fabric mod only runs when Minecraft starts it through the Fabric loader. A sandbox that just double-clicks the JAR never gets anywhere near the nasty bit. So "the sandbox says it's clean" means nothing for mods. Remember that one.
Stage 1: the "CuteCraft" loader
The sample my friend got (Smp-fabric.jar) describes itself as "CuteCraft, enhanced visual and performance optimizations" by an author called "ira".
The mod's entry point is a tiny 512-byte class that does exactly one thing: call into a package named mojang/… so it looks official. The real code is three chunky classes (82 KB, 234 KB and 959 KB) with a qProtect 2.0.0 watermark.
qProtect throws the whole kitchen sink at you. Encrypted strings, scrambled control flow, simple maths disguised as horrible maths, and almost every method call hidden behind an invokedynamic that only works out what it's calling at run time, from ten encrypted arguments.
Peeling back the maths disguises ((a|b)-(a&b) is just a^b, that sort of thing) left three little string decryptors:
class A: out[i] = (enc[i] ^ K) + iclass B: out[i] = (enc[i] + i) ^ Kclass C: out[i] = enc[i] - K - iThat gives you the boring strings, the API names. The juicy stuff, URLs and keys, sits behind a second layer of AES. To get those you'd have to actually run the thing. Or…
Running it without running it
I didn't want this anywhere near a real JVM. So Claude wrote a small bytecode interpreter instead. The malware's classes are read as data and never loaded into Java. My code steps through each instruction itself, and anything that tries to reach outside goes through a deny-by-default rulebook:
- Allowed for real: harmless stuff like strings, lists, Base64 and crypto.
- Faked and logged: files (a pretend in-memory file system), network (fake HTTP and WebSockets), environment, threads. Starting a process is always blocked.
- The hidden calls: rather than fight qProtect's invokedynamic machinery, at each call site I ran just its resolver, noted what it would have called, and made that call through the rulebook.
The whole thing ran with no network interfaces at all, just to be sure. By the end it had grown lambdas, fake Windows API calls, and hooks into the malware's HTTP and WebSocket library. That was enough to let the malware's own code do the decrypting for me, using inputs I controlled.
EtherHiding: a C2 address on the blockchain
This is where it got interesting. The loader made no normal web requests at all. Instead it talked to public Ethereum nodes (eth[.]drpc[.]org, ethereum-rpc[.]publicnode[.]com, eth-mainnet[.]public[.]blastapi[.]io) and read text records off an ENS name: el.turko.eth.
ENS is basically DNS on the blockchain. You can't seize it, you can't suspend it, and anyone can read it. Which is lovely for an attacker. Update a record and every infected PC gets the new address. No rebuild, no new upload.
The chain goes like this:
- Minecraft loads the fake mod.
- The mod reads an encrypted record off
el.turko.ethand decrypts it (AES-GCM) into a download link. - It downloads an 80 MB encrypted bundle (it starts with
IRAENC1D), usually from a Discord CDN attachment that gets rotated about every 24 hours. Fallbacks have included Dropbox, GitHub and pixeldrain. - It unpacks its own Java runtime and a 35.6 MB JAR called
modules.cacheinto%APPDATA%\discord\Local Storage\leveldb\bin, a folder picked to look like Discord's own data. - It launches stage 2 with the customer's build ID (ours was
ira-82b52885). - Stage 2 goes back to the blockchain for its C2 server, Discord webhook and Telegram bot, then connects over an encrypted WebSocket.
Record names and keys come from a 32-byte secret built into every copy plus the customer's build ID, all hashed with Keccak (written by hand inside the obfuscated code, for extra fun). The C2 and webhook records use one fixed key shared by every build. I've deliberately not published the full secret and keys here. They'd unlock every customer's Discord webhooks and Telegram bots, and those are full of other people's stolen data.
Stage 2: meet "ewa"
The payload calls itself "ewa", logs in Turkish, and is about 200 obfuscated classes wrapped around some normal libraries (JNA, sqlite, OkHttp, Kotlin). Same qProtect scheme, so the interpreter carried straight over.
Its config doesn't even live in Java though. Hidden inside is a small Windows DLL (strings.dll, 13.8 KB) that stage 2 loads straight into memory. It has one job: hand back config strings from a little XOR'd table. If you only look at the Java, the config fields are empty.
Once we faked that DLL with the decoded table, the malware happily decrypted its own C2 record for me: wss://ewababa[.]sbs/ws.
Talking to it
Each WebSocket message is AES-256-CTR encrypted with a static key: the SHA-256 of a hard-coded (and explicit) Turkish phrase. Static means any captured traffic from any victim can be decrypted, which is good news for anyone doing incident response. The handshake is CONNECT_REQUEST → CHALLENGE → CONNECTED, then the operator sends dot-commands.
So I played the operator against my emulated victim:
>>> CONNECT_REQUEST|null|UNKNOWN-HWID-836C9E<<< .cmd whoami [blocked: cmd.exe /c whoami]>>> RESULT|[+] Success<<< .l7 http://target.invalid/>>> OK|L7 dispatch accepted: target.invalid<<< .infect>>> OK|Infection sequence started... Scanning directories.<<< .winre disable [blocked: cmd.exe /c reagentc /disable]>>> RESULT|[+] SuccessIt reports success even when it's been blocked. Nice to know the malware lies to its own operator too.
What it can do
67 commands. The highlights:
- Remote shell: run commands, as admin if it can, and download and run more stuff with hidden PowerShell.
- Files: a full file manager.
- Spying: screenshots, webcam, live screen streaming, keylogger, clipboard.
- Hidden desktop (HVNC): a second, invisible desktop running cloned copies of your browsers, logged in as you.
- Credential theft on demand: re-grab browser and Discord logins whenever the operator likes.
- DDoS: your PC becomes part of someone's flood.
- Tampering: disable Task Manager and Windows Recovery.
- Harassment: lock screen, pop-ups, text-to-speech, wallpaper changes, flipping your screen upside down. There's even a chat window titled "IRA HOOK" so the operator can talk to the victim directly. Which lines up nicely with the extortion my friend got.
".infect" isn't what it sounds like
Despite the name, .infect doesn't touch your files, documents or drives. It rewrites your shortcuts. Every .lnk on your Desktop and Start Menu that points to a program gets changed to open the real app and quietly restart the malware alongside it. Same icon, app still works, no Run key or scheduled task to spot. You open Chrome, you reinfect yourself. Lovely.
The blockchain never forgets
Here's the irony. The thing that makes EtherHiding hard to take down also means every update is a permanent public record. The operator has been writing a diary and leaving it open on the table.
The current wallet (0x8B6155c4…5e7b0) has made 2,207 transactions since 29 August. 2,181 of those are record updates, about 30 a day. The one before it (0xBE92A7d0…c35D) registered irahook.eth in July. Going back through that history gave me:
- 53 C2 domains and IPs since July. 15 of them hadn't been publicly reported before.
- 313 different Discord webhooks used to collect stolen data.
- 138 payload links on Discord, Dropbox, GitHub (already dead) and pixeldrain.
- 240–300 estimated customer builds, with about 4 new ones a day.
The rough timeline:
- 2 Jul: first public sighting (
irahook[.]org), C2s on raw IPs, port 1337. - 15 Jul:
irahook.ethregistered. The first test records point at example.com and youtube.com. We all start somewhere. - 14–26 Aug: the C2 record cycles through 14
.lol/.indomains, sometimes several a day. - 27 Aug: a wave of public reports from researchers.
- 29 Aug: a brand new wallet registers
turko.eth, and within two days about 60 customers' records are moved over in bulk. - 13 Sep:
el.turko.ethcreated. Everything moves again. - Late Sep – mid Oct: payloads also served from GitHub releases and pixeldrain.
- 28 Oct: C2 switches to
ewababa[.]sbs, still current as I write this.
A service, not a campaign
This is the bit that ties back to yesterday's post. Alongside the shared records there are 349 per-customer records. I didn't decrypt those. Just looking at when they were written, how often, and how big they are tells the story:
- 303 are exactly webhook-sized. Most customers just get their own Discord webhook and share everything else.
- 117 webhook records have been rewritten five or more times. That's what you'd see when Discord deletes reported webhooks and the operator quietly pops a new one on-chain. No new malware needed.
- About 160–170 were updated in the last 30 days, and new ones keep turning up.
So yeah. One person builds and runs the tool, and a few hundred script kiddies rent it. Exactly what it looked like from the outside. Take the 240–300 as an estimate though, not a headcount.
Both wallets were funded through a small cluster of in-between addresses that got their ETH straight from big exchange wallets. That's a thread only law enforcement can pull, so I'm leaving it there for now.
Detection and clean-up
If you've run one of these mods: from a clean device, change your Minecraft/Microsoft, Discord and browser-saved passwords. Sign out of all sessions, turn on 2FA, and treat any crypto wallet on that PC as gone. Then wipe the PC.
What to look for
- Mods: entrypoint
ira.mod.easysleep.X, amojang/package inside a mod, or a qProtect watermark. Real mods almost never use commercial obfuscators. - Files:
%APPDATA%\discord\Local Storage\leveldb\bin\containingjre\,modules.cacheorbundle.tmp. Discord doesn't put a Java runtime there. - Processes:
javaw.exerunning from that folder, especially if it's spawningreagentc /disable,DisableTaskMgrregistry edits, or hidden PowerShell. - Network: Ethereum
eth_calltraffic from Java or the game, User-Agentsirahook-client/1.0orewahook-client/1.0, and WebSockets to/wson recent.sbsand.loldomains.
Fixing hijacked shortcuts
After removing the malware folder, run this in PowerShell as each affected user. It only touches shortcuts that point at cmd.exe and mention modules.cache:
$w = New-Object -ComObject WScript.Shellforeach ($d in @([Environment]::GetFolderPath('Desktop'), [Environment]::GetFolderPath('Programs'))) { Get-ChildItem $d -Filter *.lnk -Recurse -ErrorAction SilentlyContinue | ForEach-Object { $l = $w.CreateShortcut($_.FullName) if ($l.TargetPath -match 'cmd\.exe$' -and $l.Arguments -match 'modules\.cache') { if ($l.Arguments -match '/c start "" "([^"]+)"') { $l.TargetPath = $Matches[1]; $l.Arguments = ''; $l.WindowStyle = 1; $l.Save() "Restored: $($_.FullName)"} } } }Indicators of compromise
For the defenders. Everything is defanged.
Samples (SHA-256)
- Loader, Smp-fabric.jar:
66cd0c8614ada8ef7600c0903ca4795b2f97162c6ef1c4718b1a26879865aab3 - Loader, DonutSMP-Dupe-Mod-fabric.jar:
f05a098fa2e1899797e42ebc396932e05ee40adb4454de37f760d87784a4b17c - Stage 2, modules.cache:
80a4f9250bf9a8dca131a0a2b1b9525e9338aa79c8d9d295bc61dd61db965ddf - Encrypted bundle:
a15182c257f029648b43addf6023f6b0b70cc038271991b82894b0e2be433305 - Embedded strings.dll:
54b0590d63670dff87513de332647db1a0b396b0617801eee7e1ce730e47b3bb
On-chain
- ENS names:
irahook.eth(Jul–Aug),turko.eth,el.turko.eth(current) - Operator wallets:
0x8B6155c4fd0B10507EE9659099F74A25Be55e7b0,0xBE92A7d0E6b13eD0b9843958735aC6c24147c35D - The resolver contract is the official ENS PublicResolver. It isn't attacker infrastructure, so please don't block it.
C2 domains and IPs (53)
3131[.]lol 3169[.]lol 7dak[.]lol acilicak[.]lol ahmetaga[.]lolataturk[.]sbs babacan[.]lol bendesin[.]com bomboclat[.]sbs botdev[.]loldumbaero[.]lol erdogan[.]lol essek[.]lol ewababa[.]sbs gelmedi[.]lolhadiordan[.]lol helelele[.]lol hulusi[.]lol irahook[.]baby irahook[.]bestirahook[.]com irahook[.]lol irahook[.]org kabab[.]lol kaybol[.]lolkeke[.]lol kestane[.]lol kingo[.]lol kittiesmc[.]com kiyamet[.]lolkolpa[.]lol lastabuse[.]lol manita[.]lol maymun[.]lol mibombo[.]innoob[.]baby ocalp[.]lol orospucocu[.]sbs pancar[.]lol professional[.]babyselamselam[.]lol seversin[.]lol shadowcape[.]online windowskernel[.]lolwuhhuu[.]lol yeni[.]lol yeter[.]lol zbabuvj[.]lol zibidik[.]sbs44.223.110[.]247:1337 144.202.28[.]97:133745.91.52[.]45:443 143.246.213[.]74:443Other bits
- Bundle magic
IRAENC1D(older buildsIRAENC1A). Build IDsira-82b52885,ira-5fc7411c - User-Agents
irahook-client/1.0,ewahook-client/1.0. Headerx-ira-id - Named pipe
\\.\pipe\WMI_*. Files%TEMP%\dl_*.exe,%TEMP%\wp_*.jpg - C2 paths
/ws,/get/config,/mods/api/log-download
Live webhook URLs and bot tokens are left out on purpose. They've gone to the platforms.
Disclosure
The samples are up on MalwareBazaar, and the C2 indicators are on ThreatFox, including the 15 domains from the blockchain that nobody had reported before. I've also reported the Discord side (the payload attachments and the exfil webhooks) to Discord Trust & Safety, and the Telegram bots used to collect stolen data to Telegram's abuse team.
Big thanks to the researchers who were reporting IRAHook infrastructure long before I turned up, including lucibee, Whanos, karou and Alexander74. Their reports matched what the blockchain later confirmed, almost to the day.
Everything here was static or emulated. The samples were never run for real, and nothing controlled by the attacker was contacted. The blockchain data was read from public Ethereum nodes and a block explorer.
Yesterday I said the scary bit wasn't clever tech, it was trust. That's still true. But it's worth knowing what's on the other end of that trust: a properly built, actively maintained tool, rented out to anyone with a Discord account, hiding behind a blockchain that can't be switched off.
Still, the same blockchain keeps a perfect record of everything they've done. Hope it was worth it.
Leave a Reply