All incidents

ZOOM Reserve Injection Drain

Share
Sep 05, 2022 03:28 UTCAttackLoss: 70,160.28 USDTPending manual check1 exploit txWindow: Atomic
Estimated Impact
70,160.28 USDT
Label
Attack
Exploit Tx
1
Addresses
4
Attack Window
Atomic
Sep 05, 2022 03:28 UTC → Sep 05, 2022 03:28 UTC

Exploit Transactions

TX 1BSC
0xe176bd9cfefd40dc03508e91d856bd1fe72ffc1e9260cd63502db68962b4de1a
Sep 05, 2022 03:28 UTCExplorer

Victim Addresses

0x47391071824569f29381dfeaf2f1b47a4004933bBSC
0x5a9846062524631c01ec11684539623dab1fae58BSC
0x62d51aacb079e882b1cb7877438de485cba0dd3fBSC
0x9ce084c378b3e65a164aeba12015ef3881e0f853BSC

Loss Breakdown

70,160.28USDT

Similar Incidents

Root Cause Analysis

ZOOM Reserve Injection Drain

1. Incident Overview TL;DR

An attacker on BSC used a flashloan to buy ZOOM, then exploited a publicly callable distributor contract to push protocol-held fake USDT into the fake-USDT/ZOOM Pancake pair. After calling sync(), the manipulated reserve became the AMM price basis for ZOOM exits, letting the attacker sell ZOOM back for real USDT, repay the flashloan, and keep profit. The core root cause was that 0x47391071824569f29381dfeaf2f1b47a4004933b::batchToken(address[],uint256[],address) let any caller move protocol inventory into a pricing-critical pool. Because the protocol also accepted that fake USDT reserve as the ZOOM pricing leg, AMM pricing no longer reflected economically backed liquidity.

2. Key Background

The exploited market was built around verified token ProToken.sol, deployed at 0x9ce084c378b3e65a164aeba12015ef3881e0f853 and branded as ZOOM NFT. The routing path used contract 0x5a9846062524631c01ec11684539623dab1fae58 to buy and sell ZOOM across two pairs: real USDT/fake USDT at 0xf72fd2a9cdf1db6d000a6181655e0f072fc47208 and fake USDT/ZOOM at 0x1c7ecbfc48ed0b34aad4a9f338050685e66235c5.

The relevant fake USDT token was 0x62d51aacb079e882b1cb7877438de485cba0dd3f. ZOOM’s code shows nonstandard balance behavior for pair-facing accounting:

function balanceOf(address account) public view returns (uint256) {
    if(pairView == account) return _balances[pairAdd];
    return _balances[account] == 0 ? 1 : _balances[account];
}

That behavior matters because the exploit drains the batch contract’s underlying fake-USDT storage balance to zero while its displayed balance becomes 1, which matches the observed post-state.

3. Vulnerability Analysis & Root Cause Summary

The vulnerability was a permissionless reserve injection into a pricing-critical pool. The batch distributor at 0x47391071824569f29381dfeaf2f1b47a4004933b held fake USDT inventory and exposed batchToken(address[],uint256[],address) without an effective privilege boundary. The attacker used that function to transfer exactly 1000000000000000000000000 fake USDT into the fake-USDT/ZOOM pair. Immediately after, the attacker called sync() on the pair so Pancake updated reserves to include the injected balance. Because the router priced ZOOM exits from those manipulated reserves, the attacker could sell previously purchased ZOOM into the distorted route for more real USDT than the purchase and flashloan repayment cost. The broken invariant was that reserve growth in the pricing leg should only happen from economically backed or authorized liquidity additions.

4. Detailed Root Cause Analysis

The seed trace trace.cast.log shows the attacker helper 0xb8d700f30d93fab242429245e892600dcc03935d borrowing 3000000000000000000000000 real USDT from flashloan pair 0x7efaef62fddcca950418312c6c91aef321375a00, then calling the ZOOM router’s buy(uint256) path. That path routed real USDT through the real/fake pair and then the fake/ZOOM pair, yielding 424381681323020083568022798677 ZOOM to the helper.

The decisive breakpoint came next:

0x47391071824569F29381DFEaf2f1b47A4004933B::batchToken(
  [0x1c7ecBfc48eD0B34AAd4a9F338050685E66235C5],
  [1000000000000000000000000],
  0x62D51AACb079e882b1cb7877438de485Cba0dD3f
)
...
0x1c7ecBfc48eD0B34AAd4a9F338050685E66235C5::sync()
emit Sync(: 19198754777163623656927698, : 2189362840297458758244002644819)

Before the injection, the fake-USDT/ZOOM pair reserve was 18198754777163623656927698 fake USDT. After batchToken and sync(), it became 19198754777163623656927698, an increase of exactly 1000000000000000000000000. That is the exact semantic reserve injection the attack needed.

The resale path then used the manipulated reserve state:

swapExactTokensForTokens(
  417591574421851762230934433898,
  0,
  [ZOOM, fake USDT, real USDT],
  helper,
  ...
)
...
real USDT::transfer(helper, 3070160283128930726498344)

The balance-diff artifact balance_diff.json confirms that the real-USDT/fake-USDT pair lost 70160283128930726498344 real USDT, while attacker EOA 0xc578d755cd56255d3ff6e92e1b6371ba945e3984 finished with 61160283128930726498344 real USDT profit and paid 10679340000000000 wei in gas.

5. Adversary Flow Analysis

The exploit was a single transaction, 0xe176bd9cfefd40dc03508e91d856bd1fe72ffc1e9260cd63502db68962b4de1a, sent by EOA 0xc578d755cd56255d3ff6e92e1b6371ba945e3984 to attacker helper 0xb8d700f30d93fab242429245e892600dcc03935d. First, the helper flash-borrowed 3,000,000 real USDT from pair 0x7efaef62fddcca950418312c6c91aef321375a00. Second, it approved router 0x5a9846062524631c01ec11684539623dab1fae58 and bought ZOOM via the public USDT -> fake USDT -> ZOOM path. Third, it called batchToken on 0x47391071824569f29381dfeaf2f1b47a4004933b to move 1,000,000 fake USDT from protocol inventory into pair 0x1c7ecbfc48ed0b34aad4a9f338050685e66235c5, then called sync() so the manipulated balance became live reserve state. Fourth, it approved the router to spend the acquired ZOOM and sold back through ZOOM -> fake USDT -> real USDT. Finally, it repaid 3009000000000000000000000 real USDT to the flashloan pair and transferred the remaining 61160283128930726498344 real USDT to the attacker EOA.

6. Impact & Losses

The direct liquidity source drained by the exploit was pair 0xf72fd2a9cdf1db6d000a6181655e0f072fc47208, which lost 70160283128930726498344 real USDT according to the balance-diff artifact. After repaying the flashloan principal plus fee, the attacker retained 61160283128930726498344 real USDT. The exploit therefore converted protocol-controlled fake inventory into a deterministic drain of real USDT liquidity from the market path that priced ZOOM.

7. References