All incidents

MulticallWithETH Approval Drain

Share
Jul 26, 2025 10:41 UTCAttackLoss: 10,536.89 USDCPending manual check1 exploit txWindow: Atomic
Estimated Impact
10,536.89 USDC
Label
Attack
Exploit Tx
1
Addresses
2
Attack Window
Atomic
Jul 26, 2025 10:41 UTC → Jul 26, 2025 10:41 UTC

Exploit Transactions

TX 1BSC
0x6da7be6edf3176c7c4b15064937ee7148031f92a4b72043ae80a2b3403ab6302
Jul 26, 2025 10:41 UTCExplorer

Victim Addresses

0xfb0de204791110caa5535aedf4e71df5ba68a581BSC
0x3da0f00d5c4e544924bc7282e18497c4a4c92046BSC

Loss Breakdown

10,536.89USDC

Similar Incidents

Root Cause Analysis

MulticallWithETH Approval Drain

1. Incident Overview TL;DR

At BNB Smart Chain block 55371343, transaction 0x6da7be6edf3176c7c4b15064937ee7148031f92a4b72043ae80a2b3403ab6302 drained 10536885633853077370507 USDC from victim wallet 0xfb0de204791110caa5535aedf4e71df5ba68a581 to attacker EOA 0x726fb298168c89d5dce9a578668ab156c7e7be67. The attacker did not need a victim signature, privileged role, or prior setup beyond public chain state. The decisive pre-state was that the victim had already approved spender 0x3da0f00d5c4e544924bc7282e18497c4a4c92046 for an effectively unlimited USDC allowance.

The root cause is that MulticallWithETH.aggregate is a public arbitrary-call primitive that executes downstream calls from the spender contract's own address. Once a victim approves that contract as an ERC20 spender, any unprivileged caller can route a transferFrom(victim, attacker, amount) through aggregate and spend the victim's allowance.

2. Key Background

ERC20 transferFrom authorizes spending based on allowance(owner, msg.sender). The token does not evaluate the outer transaction sender; it only checks the direct caller of transferFrom.

That distinction matters here because the approved spender is not an EOA, but the contract MulticallWithETH at 0x3da0f00d5c4e544924bc7282e18497c4a4c92046. If that contract exposes arbitrary call execution without its own authorization checks, then every allowance granted to it becomes publicly exercisable.

The relevant on-chain components are:

  • Victim wallet: 0xfb0de204791110caa5535aedf4e71df5ba68a581
  • Public spender contract: 0x3da0f00d5c4e544924bc7282e18497c4a4c92046
  • USDC token proxy: 0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d
  • Attacker EOA: 0x726fb298168c89d5dce9a578668ab156c7e7be67
  • Attacker helper contract created in the same tx: 0x756d614e3d277baea260f64cc2ab9a3ac89877d3

3. Vulnerability Analysis & Root Cause Summary

The vulnerability class is an authorization failure in a spender contract. MulticallWithETH.aggregate accepts a user-supplied array of calls and forwards each one with target.call(...) from the contract's own address. The verified BscScan source shows no owner check, no caller allowlist, no selector filtering, and no binding between the external caller and any token owner whose allowance may be consumed.

Source excerpt from the verified MulticallWithETH code:

struct Call {
    address target;
    bytes callData;
    uint256 value;
    bool allowFailure;
}

function aggregate(Call[] calldata calls) external payable returns (Result[] memory returnData) {
    ...
    (bool success, bytes memory ret) = calls[i].target.call{value: calls[i].value}(calls[i].callData);
    if (!success && !calls[i].allowFailure) {
        ...
    }
}

The broken invariant is: a contract approved as an ERC20 spender must only exercise that spending authority under logic authorized by the token owner or by explicit protocol rules. The code-level breakpoint is the unchecked calls[i].target.call{value: calls[i].value}(calls[i].callData) inside aggregate, because it lets any caller make the spender contract invoke arbitrary token methods, including transferFrom.

4. Detailed Root Cause Analysis

The exploit is fully determined by public pre-state at block 55371342 (sigma_B). The seed trace shows the attacker-created helper first reading:

BEP20TokenImplementation::allowance(
  0xfb0De204791110Caa5535aeDf4E71dF5bA68A581,
  MulticallWithETH: [0x3DA0F00d5c4E544924bC7282E18497C4A4c92046]
) -> 115792089237316195423570985008687907853269984665640564024757584007913129639935

BEP20TokenImplementation::balanceOf(
  0xfb0De204791110Caa5535aeDf4E71dF5bA68A581
) -> 10536885633853077370507

Those two public values were enough to construct the drain. The same trace then shows the helper calling MulticallWithETH.aggregate with one item whose calldata is USDC.transferFrom(victim, attacker, victimBalance):

MulticallWithETH::aggregate([Call({
  target: 0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d,
  callData: 0x23b872dd...fb0de204...726fb298...23b34a9b92d395cb68b,
  value: 0,
  allowFailure: true
})])

Inside that call, USDC sees msg.sender == 0x3da0f00d5c4e544924bc7282e18497c4a4c92046, which is the approved spender. The downstream token call therefore succeeds:

BEP20TokenImplementation::transferFrom(
  0xfb0De204791110Caa5535aeDf4E71dF5bA68A581,
  0x726FB298168c89D5DCe9A578668ab156C7e7Be67,
  10536885633853077370507
)
emit Transfer(
  from: 0xfb0De204791110Caa5535aeDf4E71dF5bA68A581,
  to: 0x726FB298168c89D5DCe9A578668ab156C7e7Be67,
  value: 10536885633853077370507
)
emit Approval(
  owner: 0xfb0De204791110Caa5535aeDf4E71dF5bA68A581,
  spender: MulticallWithETH: [0x3DA0F00d5c4E544924bC7282E18497C4A4c92046],
  value: 115792089237316195423570985008687907853269984665640564014220698374060052269428
)

The Approval event confirms the allowance was consumed by exactly the transferred amount. No victim-originated transaction appears in the exploit sequence. The spender contract itself is the mechanism that converts a normal ERC20 allowance into a permissionless ACT drain.

5. Adversary Flow Analysis

The attacker flow is a single transaction:

  1. EOA 0x726fb298168c89d5dce9a578668ab156c7e7be67 sends a contract-creation transaction with nonce 0.
  2. The constructor deploys helper contract 0x756d614e3d277baea260f64cc2ab9a3ac89877d3.
  3. The helper reads allowance(victim, multicall) and balanceOf(victim) from USDC.
  4. The helper calls MulticallWithETH.aggregate.
  5. aggregate makes the spender contract call USDC.transferFrom(victim, attacker, victimBalance).
  6. USDC transfers the full victim balance to the attacker EOA and decrements the victim's allowance to the spender.

The attacker helper is incidental, not essential. Any unprivileged EOA could have called aggregate directly with the same calldata. The exploit is ACT because the required information and execution path were entirely public and permissionless.

6. Impact & Losses

The victim wallet 0xfb0de204791110caa5535aedf4e71df5ba68a581 lost its full recorded USDC balance at sigma_B, amount 10536885633853077370507 in raw on-chain units with 18 decimals as emitted by the token on BNB Smart Chain. The receiving attacker EOA in the traced incident was 0x726fb298168c89d5dce9a578668ab156c7e7be67.

The broader impact extends beyond this one wallet. Any address that approves 0x3da0f00d5c4e544924bc7282e18497c4a4c92046 for ERC20 spending and later holds spendable token balance is exposed to the same drain pattern, because the spender contract imposes no authorization over downstream calls.

7. References

  • Seed transaction: 0x6da7be6edf3176c7c4b15064937ee7148031f92a4b72043ae80a2b3403ab6302
  • Seed metadata: /workspace/session/artifacts/collector/seed/56/0x6da7be6edf3176c7c4b15064937ee7148031f92a4b72043ae80a2b3403ab6302/metadata.json
  • Seed trace: /workspace/session/artifacts/collector/seed/56/0x6da7be6edf3176c7c4b15064937ee7148031f92a4b72043ae80a2b3403ab6302/trace.cast.log
  • Balance diff artifact: /workspace/session/artifacts/collector/seed/56/0x6da7be6edf3176c7c4b15064937ee7148031f92a4b72043ae80a2b3403ab6302/balance_diff.json
  • Verified spender source: https://bscscan.com/address/0x3da0f00d5c4e544924bc7282e18497c4a4c92046#code