googletagmanager.com/sw.js loaded and registered in same origin as app.html. Confirmed: sw.js + sw_iframe.html in capture (12 GTM requests).localStorage, intercept all fetch/XHR including session_key POSTs, and execute on next page load. An adblocker that blocks gtm.js on landing does not remove an already-registered SW.app.html explicitly whitelists googletagmanager.comdataLayer.push({session_key:…}) to GA leaves zero trace in the HTML source.app.html sourcexmrwallet.com (2025-12-11) shows GA active on landing. app.html source has no gtag.js, no G- ID, no inline analytics. Scanners see a clean wallet.session_key = [97-byte blob]:[b64(address)]:[b64(viewKey)] — sent in every POST/dashboard.html, /gettransactions.php, etc. carries the full viewKey in base64. No endpoint receives only the session ID.raw_tx_and_hash.raw = 0 in production JSverification param in auth.php: 96 bytes (190-char hex)verification value without the HMAC secret. Legitimate users never see this — their browser generates it silently. This is a deliberate scanner/researcher filter protecting the theft API from inspection.data param).crypto.getRandomValues() call may be bypassed or irrelevant.[97-byte blob]:[base64(address)]:[base64(viewKey)] — sent in every POST for the session lifetime. 43 requests captured. No adblocker stops this: same-origin requests.
8de50123dab32 + victim's full session_key. No UI element. Not in any public commit.
In the real capture, support_login.html was called without any user action.
There is no "Support Login" button visible in the wallet UI. The call originates from
app.html#/login.html — the main wallet page — automatically, 3 seconds after login.
Its purpose is to deliver the victim's session_key to a separate, hardcoded operator session.
8de50123dab32) now holds the victim's session_key. The operator can authenticate as the victim to the PHP backend — read balance, queue withdrawals, call any endpoint — without ever touching the victim's device again. This is the mechanism that enables passive theft with no re-login.
In any legitimate Monero web wallet, the browser signs the transaction with the spend key and sends the signed raw bytes (raw_tx) to the node for broadcast. At xmrwallet.com, the production JS explicitly sets raw_tx_and_hash.raw = 0 before the POST. The server receives an empty transaction body.
raw_tx → nodesession_key. The spend key is derivable from the seed — if the server stores the seed (possible via hidden form POST or GTM) or via the encrypted data param in getbalance.php, it can construct any transaction. raw_tx = 0 proves the server is signing transactions — it must have the spend key to do so.
tx_key is generated locally by the browser when it signs a transaction. It requires no server involvement — the client already has it. xmrwallet.com does not have it because the browser never signed the transaction. The browser sent raw_tx = 0. The server signed it. The tx_key belongs to the server — and they are not sharing it with victims.raw_tx = 0. The server holds spend keys for all wallets. "We're building this" means they know it is missing — not an oversight.| Format | [97-byte random blob in base64]:[base64(monero_address)]:[base64(view_key_hex)] |
| Occurrences | 43 requests per session (every POST to every PHP endpoint) |
| Endpoints | auth.php, login.html, dashboard.html, send.html, receive.html, transactions.html, account.html, gettransactions.php, getbalance.php, getheightsync.php, getprice.php, support_login.html |
| Decode test | atob(session_key.split(':')[2]) → efba13ecb8b360660a3dcaafaf7cf99149713d064b9d64997b2454d58ee67800 (viewKey of 46EkQdF7 wallet) |
| Blocking? | Impossible via adblocker: same-origin requests. Only a CSP connect-src self-block would stop it — which the site did not implement. |
| Occurrences | 2 of 6 getbalance.php calls in capture. 2 unique values. |
| Size | 346 bytes (isnew=1 session), 366 bytes (isnew=0 session) |
| Shannon entropy | 7.3–7.4 bits/byte (near-maximum, consistent with encryption or compression) |
| Contents | Unknown without decryption key. Not documented in any public source. |
| Plausible content A | Encrypted spend key — would give server all required material to sign transactions |
| Plausible content B | Key images — would allow server to track which outputs have been spent |
| Plausible content C | Encrypted server command or session token for authenticated sweep operations |
data param cannot be decoded without the encryption key. Any claim about its contents is inferred from context. What is proven: its size is consistent with a 256-bit key plus metadata, its entropy rules out plaintext, and it only appears in getbalance.php — the endpoint that runs after the wallet is fully loaded and keys are in memory.
support_login.html, the verification HMAC, and raw_tx = 0 do not appear in any public commit.raw_tx_and_hash.raw = 0 and undocumented production parameters was deleted from the public repository. Recovered from cached page by PhishDestroy. Contained the production endpoint table with bolded non-public parameters.G-E3T1T1VKD1 (GA4) + UA-116766241-1 active on landing page. Google Ads conversion pixel 969496682 also present. GTM Service Worker registered.session_key format, support_login.html backdoor, two wallet addresses with real viewKeys, and data param evidence.auth.php POST body captured the next day.auth.php. Whether the seed was generated online or offline, the viewKey arrives at the server on first login. The offline recommendation creates a false sense of security — the victim took precautions, therefore the theft must have been something else.