November 5, 2026
The day FakeGit’s C2 wallet turned into a MetaMask smart account
How a malware operator learned to rotate infrastructure through the same delegation framework millions of wallets use — and the one signal…

By SOCO Null Labs
9 min read
How a malware operator learned to rotate infrastructure through the same delegation framework millions of wallets use — and the one signal that still sees it
On October 13, at 09:33 UTC, the smart contract that publishes command-and-control servers for one of the largest GitHub-abuse malware campaigns ever documented rotated to a new C2. Nothing unusual — it had done this twenty-one times before.
What was unusual is how it happened. The contract received no transaction.
Its transaction feed on Polygonscan simply stopped showing writes. The value inside changed anyway — from 185.10.68[.]110 to 144.31.108[.]193 — and the only way you'd notice is if you were already reading the contract's state every hour, or watching its event logs.
We were. This is what we found when we pulled the transaction apart — plus a zombie contract that still answers queries after its own destruction, a 32-month-old binary still being served, and the static contents of a payload nobody had cracked open.
TL;DR — A GitHub-abuse malware campaign rotates its C2 through a Polygon smart contract. On Oct 13 the operator's wallet became a MetaMask EIP-7702 smart account in the same transaction that rotated the C2, hiding from every explorer-based monitor. We found the signal that still sees it (the contract's own event log), cracked a payload nobody had statically decrypted, and caught a destroyed contract still answering C2 queries. The next rotation is statistically due ~Nov 10–11 — watch it land live.
A 60-second primer
The campaign — tracked as FakeGit, distributing the SmartLoader LuaJIT loader — operates fake GitHub repositories at industrial scale. Island Security counted 7,600+ malicious repos in July; Apiiro measured a 17,610-repo "re-aiming" wave in October. The lure is an AI-slop README funneling you to a ZIP: a .bat/.cmd launcher, a renamed LuaJIT interpreter, and one obfuscated .txt payload (OALABS documented the pattern in March 2024; derp.ca, Hexastrike, and others have tracked it since).
Since November 2025 the loader resolves its C2 through a Polygon smart contract — an EtherHiding-style dead drop that survives GitHub takedowns. derp.ca found the contracts in March. A researcher publishing as joshnck reconstructed the full write history through July 13 — transaction-level, nineteen updateData calls. We've extended that record through October and re-verified every earlier write. The relevant tail:
date C2 written delivered by
---------- -------------------------- ---------------------------
Jul 13 83.97.20[.]150 direct tx
Aug 11 194.48.248[.]94 direct tx
Sep 17 185.10.68[.]110 direct tx
Oct 13 144.31.108[.]193 MetaMask delegation redeem *date C2 written delivered by
---------- -------------------------- ---------------------------
Jul 13 83.97.20[.]150 direct tx
Aug 11 194.48.248[.]94 direct tx
Sep 17 185.10.68[.]110 direct tx
Oct 13 144.31.108[.]193 MetaMask delegation redeem *That last row is the story.
One transaction, three moves
The October 13 transaction (0x08233ee5…d53f) went to 0xdb9B1e94…47dB3 — MetaMask's public DelegationManager, a contract with roughly a million transactions — instead of the C2 contract. Inside it:
- An EIP-7702 authorization designating the operator's wallet to 0x63c0c19a282a1b52b07dD5a65b58948A07DAE32B — MetaMask's official EIP-7702 Delegator implementation, the address MetaMask's own docs and every major explorer tag as theirs.
- A
redeemDelegationcall carrying a self-delegation — delegator and delegate are both the operator's wallet — whose inner execution is a plain updateData("http://144.31.108.193"). - The rotation itself, executed through the contract's own logic, indistinguishable from a direct write once it lands.
One block before, the deployer wallet had no code at all — a plain EOA. One block after, its code was the canonical 0xef0100 + delegator-address designator. The wallet didn't just borrow MetaMask's infrastructure; it became a MetaMask smart account, in the same transaction that rotated the C2.
Nine blocks — about twenty seconds — later, a second redemption (0x3c47bc56…) fired the same value again. The contract's event log shows 144.31.108.193 → 144.31.108.193: old equals new. A double-submit, a retry, or a UI quirk — but it means the delegated-write path is now the operator's default, not a one-off.
As far as we can tell, this is the first public documentation of a malware operator routing C2 updates through MetaMask's delegation framework. (We'd genuinely like to be wrong — if you've seen it elsewhere, say so.)
Why this is clever
Etherscan-style monitoring watches a contract's transaction list. That list is now permanently dark for this contract — delegated writes never appear there. And the deployer's traffic now blends into one of the busiest legitimate contract families on the chain. They didn't pick an obscure relay; they picked the address nobody can afford to block.
The event they forgot to suppress
The V2 contract emits an event on every write — topic 0xee986145c636aca5c6c0b4ce0e9d371e77ab30b3e28fe5c3a8d977f05fc8cf24, carrying (old_value, new_value). It fires on the August and September direct writes and on both October delegated ones — the delivery mechanism doesn't matter; the event is emitted by the contract's own code:
aug-11 (direct) UpdateData: 'http://83.97.20.150' -> 'http://194.48.248.94'
sep-17 (direct) UpdateData: 'http://194.48.248.94' -> 'http://185.10.68.110'
oct-13 (delegated) UpdateData: 'http://185.10.68.110' -> 'http://144.31.108.193'
oct-13 (delegated) UpdateData: 'http://144.31.108.193' -> 'http://144.31.108.193' ← double-fireaug-11 (direct) UpdateData: 'http://83.97.20.150' -> 'http://194.48.248.94'
sep-17 (direct) UpdateData: 'http://194.48.248.94' -> 'http://185.10.68.110'
oct-13 (delegated) UpdateData: 'http://185.10.68.110' -> 'http://144.31.108.193'
oct-13 (delegated) UpdateData: 'http://144.31.108.193' -> 'http://144.31.108.193' ← double-fireNobody has published this event — it doesn't appear in joshnck's (excellent) transaction-enumeration work, presumably because they never needed it. Now everyone does. Tx-lists are dead; state-diff polling and log-watching both survive the pivot — and the log is strictly better, because it captures every write in order with old→new pairs, including rotations too short-lived for hourly polling. (Ask us how we know: our first pass at this timeline missed an eight-hour rotation that sat between samples. We learned that one the hard way.)
Honest caveat: free public RPCs are unreliable for historical eth_getLogs — drpc's free tier caps ranges at ~10k blocks, publicnode silently returns empty results for older windows. Forward watching works fine; deep history wants per-receipt lookups or an indexer. The sensor is published (tests/polygon_c2_sensor.py — watch-logs) — pin it to an endpoint you trust.
Dating the pivot — and being wrong on purpose
Our first hypothesis: the delegation pivot reacted to joshnck's July publication, which had exposed the contract's full write history. Nice story. The chain disagrees — the August 11 and September 17 writes were both plain direct calls, three-to-eight weeks after that repo went public. The pivot happened between September 17 and October 13, inside the October wave: fleet re-aim on the 4th, Apiiro's report on the 6th, delegated rotation on the 13th.
Correlation isn't motive. But if they were reacting to a publication, the only public analysis in that window is Apiiro's — which documented the fleet pivot, not the chain. Either way: they changed their write path within weeks of being written about.
What's actually being served — and what's inside it
Nobody had published a current-fleet census, so we took one: every distinct ZIP link from eight live lure repos (all from Island's July list, re-verified alive November 4), hashed and analyzed statically — nothing executed. Five build generations, served simultaneously:
build (ZIP stamp) served what it is
------------------ ------- ----------------------------------------
Jan 17, 2025 yes V1-era: conf.txt + lua.exe + lua51.dll
Feb 19, 2026 yes x3 Hexastrike-era batch + unpublished sibling
Feb 22, 2026 yes x3 "V2d" - byte-identical across three repos
Mar 25, 2026 yes newest build found anywhere in the samplebuild (ZIP stamp) served what it is
------------------ ------- ----------------------------------------
Jan 17, 2025 yes V1-era: conf.txt + lua.exe + lua51.dll
Feb 19, 2026 yes x3 Hexastrike-era batch + unpublished sibling
Feb 22, 2026 yes x3 "V2d" - byte-identical across three repos
Mar 25, 2026 yes newest build found anywhere in the sampleFirst implication: the October re-aim of 17,610 repos was served entirely with seven-to-eight-month-old artifacts. Apiiro's "nobody had to create a single new repo" extends — nobody had to build a new payload either.
Second: the lua.exe in the January-2025 ZIP hashes to 1cf20b84…c30c42a — byte-identical to AimmyLauncher.exe from OALABS' March 2024 report. lua51.dll matches too. A 32-month-old interpreter is still being distributed today.
Cracking the January build open
The January-2025 conf.txt is the oldest build in the fleet — and until now, an unread one. Chris Partridge (January 2025) and Centri (February 2025) ran sibling/identical samples dynamically and logged four C2 IPs between them. joshnck's Prometheus decryptor covers a different obfuscation generation — the inline-string scheme — so this build's base64-table variant (MoonSec-style: a shuffled 1,045-entry table, per-build alphabet, index accessor) had no published static extractor.
We wrote one. Running it against the still-served file recovered 315 unique plaintext strings — the full Win32 FFI surface, /api/ + /task/ endpoints, the multipart exfil boundary, the scheduled-task templates — and its embedded C2 list:
87.120.36[.]50 · 80.66.81[.]11 · 178.236.243[.]5
Three of the four IPs Partridge and Centri logged dynamically. The fourth — 150.241.105[.]82 — is not in the embedded list, even though Centri's sandbox run of this byte-identical file contacted it. Read that again, because it's the interesting part: the build ships a three-IP embedded pool, and at least one more C2 is delivered dynamically — pushed from the server side rather than baked in. Static lists and observed sets are different things; here's the proof. (The 150.241 u1host space resurfaces later in the chain era as 150.241.108.62 in derp's infrastructure table.)
Two more continuity details from the corpus. The build embeds the victim-ID constant a0,86,82,9d,a1,88,90,95,65,7d — which is exactly the base64 path Centri captured in February 2025 (/api/YTAsODYsODIsOWQs…) and consistent with the ID ASEC documented for the August-2025 chain generation. Same bot identity scheme across the pre-chain and Polygon eras. And its static profile — Defender tampering via Add-MpPreference -ExclusionPath $env:SystemDrive -ExclusionExtension .exe,.dll, a highest-privilege scheduled task on oobe\Setup.exe, five masquerade task names — confirms statically what Centri saw run.
The numbers GitHub keeps for us
Soft.zip — that January-2025 build, hosted on GitHub Releases by 0290192029/apartment-price-predictor — shows 3,647 recorded downloads since January 20, 2025, and counting as this publishes. Island's 14-million figure was aggregate and July-era; this is the first current, per-asset, generation-attributed count we know of: a 21-month-old lure, still live, still collecting.
The zombies
EIP-6780 says a SELFDESTRUCTed contract keeps its code and storage unless destroyed in its creation transaction. This campaign produces them accidentally, and it's beautiful:
- V1 (0xd68910ED…): destroyContract() called January 26 — still answers getDomain() with http://89.169.12[.]173 today. Any V1-era implant still resolves it.
- The test contract (found while enumerating the deployer's 27 transactions — all accounted: create-test → destroy → create-V2 → 21 updates → 2 redeems + 1 funding): 0xc224c02c…, created and destroyed within two hours on November 15, 2025, ~10 hours before V2 deployed. A V2 prototype — same getData() interface — still answering 127.0.0.1. A rehearsal, preserved on-chain forever.
- V1-alt (0x2cbd24…): never destroyed, frozen at 158.78.56[.]52.
# ask a destroyed contract what C2 it serves — it answers
curl -s https://polygon.drpc.org -X POST -H 'content-type: application/json' -d '{
"jsonrpc":"2.0","id":1,"method":"eth_call",
"params":[{"to":"0xc224c02cc0c5b5bf83538b3db0b6f5b10a2bf6d2",
"data":"0x3bc5de30"},"latest"]}'
# -> "127.0.0.1"# ask a destroyed contract what C2 it serves — it answers
curl -s https://polygon.drpc.org -X POST -H 'content-type: application/json' -d '{
"jsonrpc":"2.0","id":1,"method":"eth_call",
"params":[{"to":"0xc224c02cc0c5b5bf83538b3db0b6f5b10a2bf6d2",
"data":"0x3bc5de30"},"latest"]}'
# -> "127.0.0.1"
A falsifiable prediction
Rotation cadence in 2026 runs 7–37 days, mean ~28.5 since July. The current value went up October 13, putting the next write around November 10–11. If a new C2 lands in that window as a MetaMask redeemDelegation, that's the pattern confirmed twice. If it doesn't, that's information too — cadence regimes break when campaigns feel watched. Either way, check the chain, not us.
python3 polygon_c2_sensor.py # current value, all four contracts
python3 polygon_c2_sensor.py --timeline V2 # full write history
python3 polygon_c2_sensor.py --watch 3600 # state-diff alert
python3 polygon_c2_sensor.py --watch-logs 60 # event-based; survives delegation
python3 b64_table_adapter.py RAW.lua FOLDED.lua \
--mul45 241 --add45 34702913358907 # static strings, table generationpython3 polygon_c2_sensor.py # current value, all four contracts
python3 polygon_c2_sensor.py --timeline V2 # full write history
python3 polygon_c2_sensor.py --watch 3600 # state-diff alert
python3 polygon_c2_sensor.py --watch-logs 60 # event-based; survives delegation
python3 b64_table_adapter.py RAW.lua FOLDED.lua \
--mul45 241 --add45 34702913358907 # static strings, table generationWhat we don't know
- Whether the delegation pivot was reaction, routine upgrade, or coincidence. Motive isn't on-chain.
- Whether the 75% takedown-survival rate (30/40, re-verified sample) generalizes — it's a sample, and removal patterns shift.
- Who's behind 0xF9Bd8BAD…, the 133k-transaction wallet that funded the deployer. Unlabeled. If it's an exchange, its deposit history is the attribution thread — for someone with lawful access, not us.
- How many more embedded secrets sit in the other generations. The adapter covers the table scheme; the March-2026 build's pair-extraction is the next task.
- Whether 150.241.105.82 is delivered as a runtime fallback or hidden under a second encoding. The dynamic-vs-static question is open.
Prior art — read these, they're why this exists
- OALABS (Mar 2024) — the LuaJIT/GitHub-bug origin report, and the binary still in service.
- Chris Partridge (Jan 2025) — first to document the FortiGate-leak SmartLoader lures; four dynamic C2s.
- Centri (Feb 2025) — full dynamic analysis of this exact build; the /api/ exfil and the fourth C2.
- derp.ca (Mar 2026) — discovered the Polygon C2 contracts, the EIP-6780 mechanism, the 16-generation taxonomy, the infrastructure tables.
- joshnck/luaJit_malware_report (Jul 2026) — definitive pre-August write history and the inline-Prometheus decryptor we validated our miner against (it rediscovered their exact constants).
- kchitnis — static decryption of 996 strings in a V2-era build (inline generation).
- Apiiro (Oct 2026) — the 17,610-repo re-aim measurement.
- Island (Jul 2026) — the dataset our census samples.
- Hexastrike, Hakai, Straiker, Trend Micro, ASEC, Arctic Wolf, Netskope — campaign and payload tracking, 2025–26.
What we believe is new: the MetaMask delegation delivery path and self-delegation structure, the UpdateData event as a surviving monitor, the bounded pivot window, the live-fleet census, the static embedded C2 list and the embedded-vs-observed variance, the victim-ID continuity link, the second zombie resolver, per-asset download telemetry, and the wallet-level accounting. Everything else is synthesis — which is what most research is, when it's honest.
All samples were analyzed statically — nothing was executed, no C2 was contacted. IOCs, the full rotation timeline with transaction hashes, the fleet inventory, the 315-string corpus, and all tooling are in the companion repository. If you work takedown or attribution, take the IOC list; if you run the sensor, tell us when the next rotation lands.