BY OLIVER HIRST DATE: JANUARY 1, 2026
There is a certain vulgar arrogance to modern malware authors. They present you not with code, but with a challenge to your sanity. The file in question, w_source.lua, arrived on my desk not as a script, but as a monument to obscuration. It was a hideous thing to behold—a sprawling wasteland of single-letter variables (W, r, e), stripped of all semantic meaning, designed to look less like logic and more like the static of a dying television channel.
My initial reaction was one of weary familiarity. "Another packer," I thought, "another tedious exercise in unzipping." I was wrong. This was not merely packed; it was a fortress built of glass and malice.
The first tribulation is always the underestimation of the adversary. I assumed, with the hubris of the seasoned analyst, that standard deobfuscation tools would strip away the outer shell. They did nothing but choke.
I began to poke at the edges. I noticed a suspicious offset—0x913—referenced in the loader. Extracting the data at this location yielded jit.txt. A text file? Hardly. It was a binary blob, a serialized state of a custom Virtual Machine. This was the first blind alley I walked down: treating it as static data. I wasted hours staring at hex dumps, looking for patterns that weren't there, convinced I could brute-force the decryption with a simple RC4 script (try_rc4_brute.py). The results were a litany of false positives and gibberish.
The code mocked me. I attempted to instrument the execution flow directly, modifying the source to print its secrets. LuaJIT spat back a Control structure too long error. The malware was so convoluted, so cyclically dense, that the compiler itself refused to process it. I was fighting a hydra; cut off one head, and the compiler crashes.
It became clear that static analysis was a fool's errand. I had to build a cage and let the beast run inside it. I constructed mock_runner.lua, a synthetic reality where I controlled the horizontal and the vertical.
The breakthrough came when I stopped trying to read the code and started tracing the data. I identified the heart of the machine, a flattened dispatch loop:
V = J(x, w)Here, J was the instruction pointer mapping, and w was the memory state. But w was encrypted. The key wasn't in the code; it was in the air. The malware used a custom Pseudo-Random Number Generator (PRNG) properly seeded by the environment. If the seed was off by a single bit, the code decoded to garbage.
I spent a sleepless night wrestling with solve_lcg.py, trying to mathematically derive the parameters of the linear congruential generator. It failed. The math was sound, but the inputs were tainted. It was only when I looked at the entropy of the jit.txt header itself that I found the "divine" constant: 13578145.
When I fed this seed into the VM, the chaos ceased. The instructions aligned. The lock clicked open.
With the PRNG synchronized, I placed a hook on loadstring—the function used to execute dynamic code. I ran the machine. It stuttered, crashed on a nil upvalue h, and then, finally, it produced the goods.
We captured payload_loadstring.bin—214 kilobytes of pure, decrypted Lua source.
And what did it do, this sophisticated piece of engineering? It didn't call a server. It didn't reach out to a domain that could be seized by the FBI or sinkholed by a brightly-colored tech giant. No, it turned to the one infrastructure that is truly indifferent to human law: the Polygon Blockchain.
Deep within the decrypted payload, within a table simply labeled z[52], lay the true genius—and the true horror—of this operation:
{
"jsonrpc": "2.0",
"method": "eth_call",
"params": [
{
"to": "0xd68910ED4D4A5A9bAdF9ec95604CAE0f3378479B",
"data": "0xb68d1809"
},
"latest"
],
"id": 1
}The malware executes a "read-only" call (eth_call) to a smart contract on Polygon Mainnet. It uses an Alchemy API key (89pCO1NlLRkTZgb8DtZmKwC42AQcUeXF) to bridge the gap between the infected Windows machine and the blockchain.
I obtained the bytecode of the contract at 0xd68910ED4D4A5A9bAdF9ec95604CAE0f3378479B and disassembled it. The architecture was elegant in its simplicity:
| Function Selector | Name | Purpose |
|---|---|---|
0xb68d1809 |
retrieve() |
Returns C2 payload from storage slot 1 |
0xb249cd2d |
setPayload(string) |
Admin-only: Updates C2 payload |
0xf851a440 |
admin() |
Returns admin address from slot 0 |
The contract is nothing more than a key-value store. The admin writes the C2 address; the victim reads it. No transaction is created for the read. No gas is spent. The victim's query is invisible.
But the question remained: what was in storage slot 1?
I queried the contract directly using eth_call via a public Polygon RPC:
curl -X POST https://polygon-rpc.com -d '{
"jsonrpc":"2.0",
"method":"eth_call",
"params":[{
"to":"0xd68910ED4D4A5A9bAdF9ec95604CAE0f3378479B",
"data":"0xb68d1809"
},"latest"],
"id":1
}'The response:
0x...687474703a2f2f39332e3132332e33392e323436...
Decoded from hex:
http://93.123.39.246
There it was. The Command & Control server. An IP address, hidden in plain sight on a public blockchain, immutable and unkillable unless the attacker chooses to update it.
| Type | Value |
|---|---|
| C2 Server | http://93.123.39.246 |
| C2 IP | 93.123.39.246 |
| Polygon Contract | 0xd68910ED4D4A5A9bAdF9ec95604CAE0f3378479B |
| Function Selector | 0xb68d1809 |
| Alchemy API Key | 89pCO1NlLRkTZgb8DtZmKwC42AQcUeXF |
| DGA/Subdomain | gk7o1irwprznlvi3gzpqru4yq6dwnheq89hzeepkvvdrqoe1enf3hmps2lyyeljcgusxra6ekkkn02z3w7youq259yf86xqg3tthvcd76q9 |
| PRNG Seed | 13578145 |
| Payload Offset | 0x913 |
We have stripped this thing naked. We have seen its gears (VirtualAlloc, mocked and neutralized), we have stolen its keys, we have reverse-engineered its contract, and we have extracted its secrets from the blockchain.
It is a formidable piece of tradecraft, I will grant it that. It disguises itself as nothing, runs as a ghost in memory, and speaks only to the unkillable ledger of Polygon. But it is no longer magic. It is just code. And code, eventually, always yields to the determined mind.
The C2 is 93.123.39.246. Block it. Hunt it. The file is closed.
— Oliver Hirst