Web threats / Wallet drainers

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 code6
Sites with a match in the last 30 days2
Latest match23 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) and 0xb28329d6c1b9a6f8e97cebbaf9707d691b5ace4e (BNB Smart Chain)
  • permit spender 0x0038dfb2bbc353daa6ad22cb11ee1d78f41b59cd (Ethereum)
  • transfer authorization recipient 0x4fe93ebc4ce6ae4f81601cc7ce7139023919e003 (BNB Smart Chain)
  • sweep recipients 0x36c8c9162988da60c24a72d11c163fc64ba59a39 (Ethereum) and 0x5a6f25b0d1cd404f1a37004baf19e449def7d5dc (BNB Smart Chain)

An approval given to one of these spenders stays in force until the wallet revokes it.

What we see

MonthNew sites
September4
October2
November so far0

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|PhantomJS and permit_key
  • an eth_call to BNB Smart Chain RPC nodes with data 0xb68d1809
  • POST requests to /x7k/gc, /x7k/gf, /x7k/s1 or /x7k/s2 on a host the page never names
  • DNS-over-HTTPS TXT lookups made by the page
  • window.__PROXY__ and window.__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.

ChainContractCurrent destinationsLast changed
BNB Smart Chain0x1AC7d768E37B147c993dE943D704c4e92d7861A1zen-stand-blossom-hello[.]xyz

The EtherHiding contract tracker keeps the full destination history.

Try the feed for 14 days.

Request a trial and we'll send you a live API key, with the same feed, removals and reasons a customer gets.