PermitKey wallet drainer
PermitKey is a wallet drainer on look-alike DeFi and reward-claim pages. It finds its proxy through a BNB Smart Chain contract, then loads an encrypted config and a drainer that runs inside a JavaScript virtual machine. We have matched it on 6 sites since 1 September.
PermitKey in numbers
Since 1 September, the first PermitKey match in our records, counted on 12 November 2026. How we count.
| Sites with PermitKey code | 6 |
|---|---|
| Sites with a match in the last 30 days | 2 |
| Latest match | 23 October |
Fake DeFi pages load a 5 KB script
PermitKey runs on copies of DeFi apps and reward-claim pages: DAO governance and ballot-vote pages numbered one after another, Curve, Velodrome, Pendle, Stargate, Aave, Hyperliquid, Uniswap and PancakeSwap swaps, OKX and Robinhood reward claims, and token allocation checks for named projects. Most sit on free hosting subdomains (Cloudflare Pages and Workers, Firebase, Vercel, Netlify, Render), and some on look-alike domains of their own. Each page loads one script of about 5 KB named with ten hex characters, such as /1d81dfa3aa.js.
The script quits at once in an automated browser. It checks navigator.webdriver, the Playwright, Nightmare and PhantomJS globals, a HeadlessChrome user agent and a window with no outer size.
A BNB Smart Chain contract names the proxy
The loader calls contract 0x1AC7d768E37B147c993dE943D704c4e92d7861A1 on BNB Smart Chain with selector 0xb68d1809 and decodes the string it returns: the proxy's URL. It asks four public RPC nodes in turn, so one blocked node changes nothing. The proxies are .xyz domains that start with zen, most of them four words joined by hyphens, and the operator moves every page to a new one with a single contract update. The contract tracker shows where it points today.
On 7 October 2026 DogeOS asked MetaMask to block a fake DogeOS holder-allocation page at doge-os[.]web[.]app and listed the proxies the contract had named since 1 October: notzen[.]xyz, zen-prepare-silk-chuckle[.]xyz, zen-onion-mouse-cave[.]xyz and zen-earth-pistol-alpha[.]xyz (MetaMask/eth-phishing-detect#300742). Later ones include zennnnnnn[.]xyz, zen-castle-depth-gas[.]xyz and zen-stand-blossom-hello[.]xyz.
The newest build races that read against a DNS TXT lookup for a second domain through Cloudflare's and Google's DNS-over-HTTPS resolvers. It keeps the winning proxy in localStorage under __c2 for six hours and drops it when /healthz on the proxy fails.
The permit key unlocks the config
Every build carries a 20-character key and posts it as permit_key to the proxy. From /x7k/gc comes the config: a JSON reply whose e field is a small obfuscated script that XORs a byte array with a 32-character hex key. The loader runs it with eval, parses the output and keeps it in window.__RC__. A second post with the same key, to /x7k/gf, returns the drainer. The newest build posts to /x7k/s1 instead, runs the code that comes back, and takes the config and the drainer's address from that stage.
The config sets the wallet kit (reown), a minimum of 10 USD in total and per chain, switches for Permit2 batch signatures, bundles and NFTs, and the prompt "Confirm the transaction in your wallet". In the newest config DOMAIN_SPOOF holds https://etherscan.io. TRAFFIC_NAME holds the affiliate's label.
The drainer runs inside a virtual machine
The drainer arrives as a 2.5 MB script. It decompresses an LZ-String payload to about 2 MB of JavaScript and runs it with new Function. That code is a bytecode interpreter: each function of the drainer is an encrypted string, and the interpreter hashes its own source before it starts and derives its decryption keys partly from that hash. The config names the file by its hash, at a path of the form /safe/reown/main.<sha256>.js.
On the page it draws a WalletConnect-style connect modal and a "Processing" popup. Earlier builds post a browser fingerprint to /api/fp on the proxy.
One connection tries four ways to take funds
On 12 November 2026 we opened a live governance-vote page with a wallet that records every request and signs nothing, and clicked its "Withdraw Now" button. The drainer ran in a frame on fxgy[.]xyz. Once the wallet connected, it asked for four kinds of request across Ethereum and BNB Smart Chain:
- Token approvals (
approve): USDT on Ethereum, then, after switching the wallet to BNB Smart Chain, BTCB. - Permit signatures (EIP-2612, signed with
eth_signTypedData_v4) on Ethereum, for Wrapped Binance Beacon ETH and United Stables. A signature costs no gas, so the wallet shows a message to sign rather than a transaction. - Transfer authorizations (EIP-3009
TransferWithAuthorization) on BNB Smart Chain, for United Stables and World Liberty Financial USD. Whoever holds the signature can submit the transfer. - Native-coin sweeps: a transaction with no recipient that creates a small contract whose code forwards the whole value it receives. The wallet shows "deploy a contract" instead of "send to address".
The approvals, permits and authorizations were all for unlimited amounts.
The session used these addresses, all from one affiliate's config (TRAFFIC_NAME "Zen"):
- approval spenders
0x8a42f5373a3716e84a05e49db25f25d4c0b516b3(Ethereum) and0xb28329d6c1b9a6f8e97cebbaf9707d691b5ace4e(BNB Smart Chain) - permit spender
0x0038dfb2bbc353daa6ad22cb11ee1d78f41b59cd(Ethereum) - transfer authorization recipient
0x4fe93ebc4ce6ae4f81601cc7ce7139023919e003(BNB Smart Chain) - sweep recipients
0x36c8c9162988da60c24a72d11c163fc64ba59a39(Ethereum) and0x5a6f25b0d1cd404f1a37004baf19e449def7d5dc(BNB Smart Chain)
An approval given to one of these spenders stays in force until the wallet revokes it.
What we see
| Month | New sites |
|---|---|
| September | 4 |
| October | 2 |
| November so far | 0 |
Checking a DeFi page
Open the page with the browser's network panel and look for:
- a script of about 5 KB with a ten-hex-character name that contains
HeadlessChrome|PhantomJSandpermit_key - an
eth_callto BNB Smart Chain RPC nodes with data0xb68d1809 POSTrequests to/x7k/gc,/x7k/gf,/x7k/s1or/x7k/s2on a host the page never names- DNS-over-HTTPS TXT lookups made by the page
window.__PROXY__andwindow.__RC__set after the page loads- a frame from a host the page never names, opened when the wallet connects
Any wallet request the page then makes comes from the drainer, whatever the button says.
Contracts we read
We read 1 PermitKey contract every day.
| Chain | Contract | Current destinations | Last changed |
|---|---|---|---|
| BNB Smart Chain | 0x1AC7d768E37B147c993dE943D704c4e92d7861A1 | zen-stand-blossom-hello[.]xyz |
The EtherHiding contract tracker keeps the full destination history.