All incidents

Public Liquidity Trigger Drain

Share
Nov 29, 2022 13:01 UTCAttackLoss: 5,904.37 USDTPending manual check1 exploit txWindow: Atomic
Estimated Impact
5,904.37 USDT
Label
Attack
Exploit Tx
1
Addresses
2
Attack Window
Atomic
Nov 29, 2022 13:01 UTC → Nov 29, 2022 13:01 UTC

Exploit Transactions

TX 1BSC
0xdc53a6b5bf8e2962cf0e0eada6451f10956f4c0845a3ce134ddb050365f15c86
Nov 29, 2022 13:01 UTCExplorer

Victim Addresses

0x4e87880a72f6896e7e0a635a5838ffc89b13bd17BSC
0xee04a3f9795897fd74b7f04bb299ba25521606e6BSC

Loss Breakdown

5,904.37USDT

Similar Incidents

Root Cause Analysis

Public Liquidity Trigger Drain

1. Incident Overview TL;DR

On BSC block 23474461, an unprivileged attacker EOA 0x9cc3270de4a3948449c1a73eabff5d0275f60785 called helper contract 0x0b13d2b0d8571c3e8689158f6db1eedf6e9602d3, took a DODO USDT flash loan, traded through the MBC/USDT and ZZSH/USDT Pancake pairs, force-triggered each token's public liquidity-maintenance path, repaid the flash loan, and sent the remaining profit to 0xad2d2cb5f91e7adee7b029958a58fe6a38e282eb. The profit recipient finished with 5904366951329750368929 raw USDT units according to the collected balance diff.

The root cause is that both MBC and ZZSH expose public, price-moving maintenance routines over protocol-owned fee inventory. Because any external actor can decide when those routines execute, an attacker can first move the pool into a favorable state, trigger the maintenance conversion, and then unwind against the reserve shift created by protocol inventory that should not have been externally schedulable.

2. Key Background

MBC at 0x4e87880a72f6896e7e0a635a5838ffc89b13bd17 and ZZSH at 0xee04a3f9795897fd74b7f04bb299ba25521606e6 are fee-on-transfer tokens that accumulate inventory inside the token contract itself. Their liquidity-management path swaps part of that inventory into USDT and adds liquidity back into the corresponding Pancake pair.

The two affected pools are the MBC/USDT Pancake pair at 0x5b1bf836fba1836ca7ffce26f155c75dbfa4adf1 and the USDT/ZZSH Pancake pair at 0x33cca0e0cff617a2aef1397113e779e42a06a74a. DODO pool 0x9ad32e3054268b849b84a8dbcc7c8f7c52e4e69a supplied the transaction's temporary USDT working capital through a permissionless flash loan.

The relevant victim-side code is public. In the verified MBC source, the liquidity routine and its helper are externally callable and act on contract-held balances:

function swapAndLiquify() public {
    uint256 allAmount = balanceOf(address(this));
    if (allAmount > 10**18) {
        uint256 canswap = allAmount.div(2);
        uint256 otherAmount = allAmount.sub(canswap);
        swapTokensForOther(canswap);
        uint256 ethBalance = ETH.balanceOf(address(this));
        addLiquidityUsdt(ethBalance, otherAmount);
    }
}

function swapAndLiquifyStepv1() public {
    uint256 ethBalance = ETH.balanceOf(address(this));
    uint256 tokenBalance = balanceOf(address(this));
    addLiquidityUsdt(tokenBalance, ethBalance);
}

ZZSH exposes the same pattern with public swapAndLiquify, swapTokensForOther, and swapAndLiquifyStepv1 functions.

3. Vulnerability Analysis & Root Cause Summary

This is an ACT attack, not a privileged compromise. The exploited condition is that both token contracts delegated reserve-moving liquidity maintenance to unrestricted public entrypoints. Those functions consume protocol-owned balances already accumulated inside the token contracts and then call Pancake router liquidity operations without access control, price bounds, or timing restrictions. That means the attacker can choose the exact reserve state in which the maintenance runs. The attacker first buys into the target token, then forces the protocol inventory conversion while the pool is skewed, then sells back through the pair after the reserve transition. The flash loan only scales the trade size; it is not the code flaw. The code-level breakpoint is the public exposure of the swap-and-liquify path over protocol-owned funds.

4. Detailed Root Cause Analysis

The safety invariant is: protocol-owned fee inventory earmarked for liquidity management must not be executable at arbitrary external timing by an unprivileged trader who can position around the resulting reserve move. MBC and ZZSH violate that invariant by exposing their maintenance path publicly.

The MBC source shows the first half of the bug directly. swapAndLiquify() computes allAmount = balanceOf(address(this)), swaps half through swapTokensForOther, and then calls addLiquidityUsdt using the contract's inventory. The function is public, so the scheduling right belongs to any caller, not the protocol. The same public exposure exists in ZZSH. In both contracts, swapAndLiquifyStepv1() is also public, which matters because the attack trace shows the helper calling that step directly.

The seed trace for transaction 0xdc53a6b5bf8e2962cf0e0eada6451f10956f4c0845a3ce134ddb050365f15c86 confirms the end-to-end mechanism:

0x9ad32e3054268B849b84a8dBcC7c8f7c52E4e69A::flashLoan(...)
MBC::swapAndLiquifyStepv1()
0x5b1Bf836fba1836Ca7ffCE26f155c75dBFa4aDF1::swap(0, 155602136642505248762174, ...)
ZZSH::swapAndLiquifyStepv1()
0x33CCA0E0CFf617a2aef1397113E779E42a06a74A::swap(150302230308824501608757, 0, ...)

The balance diff corroborates the reserve effects. Before the transaction, MBC held 8910121517374895634224 raw USDT and ZZSH held 1525554339326077833068 raw USDT. After the transaction, both token contracts' USDT balances were zero, showing that their protocol-owned inventory had been consumed by the attacker-timed maintenance call. The corresponding pair reserves moved in the attacker's favor: MBC pair USDT increased while MBC decreased, and ZZSH pair USDT increased while ZZSH decreased.

The pair contract source shows this was not an AMM invariant bypass. PancakePair swap() still enforces the standard constant-product check:

require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(10000**2), 'Pancake: K');

So the exploit is economic and timing-driven: the attacker used only legal pair interactions, but forced the protocol's own inventory-management trade to occur at an adversarially chosen point in the reserve curve.

5. Adversary Flow Analysis

The adversary flow is fully contained in one transaction on BSC.

  1. The EOA 0x9cc3... called helper contract 0x0b13....
  2. The helper borrowed 794994271783747229185351 raw USDT from DODO pool 0x9ad32....
  3. For the MBC cycle, the helper sent 150000000000000000000000 raw USDT to the MBC pair, bought MBC, called MBC::swapAndLiquifyStepv1(), synchronized the pair, then sold its MBC back to the pair and received 155602136642505248762174 raw USDT.
  4. For the ZZSH cycle, the helper repeated the same pattern: send 150000000000000000000000 raw USDT to the ZZSH pair, buy ZZSH, call ZZSH::swapAndLiquifyStepv1(), synchronize, then sell back and receive 150302230308824501608757 raw USDT.
  5. The helper repaid the flash-loan principal to DODO and transferred the residual USDT to 0xad2d....

The trace identifies the relevant calls and recipients, while the balance diff identifies the profit realization:

{
  "token": "0x55d398326f99059ff775485246999027b3197955",
  "holder": "0xad2d2cb5f91e7adee7b029958a58fe6a38e282eb",
  "before": "0",
  "after": "5904366951329750368929",
  "delta": "5904366951329750368929"
}

This sequence satisfies the ACT model because it needs only public liquidity, public contract code, public state, and an attacker-controlled helper contract.

6. Impact & Losses

The measurable loss is 5904366951329750368929 raw USDT units, with decimal=18, extracted as attacker profit in the incident transaction. The loss came from value embedded in protocol-owned token inventory and the reserve transition created when that inventory was forcibly deployed into the AMM at adversarial timing.

The immediate affected contracts are the MBC and ZZSH token contracts, because they exposed the price-sensitive maintenance functions over their own accumulated balances. The downstream market impact manifested in the two Pancake pairs whose reserves were shifted during the exploit.

7. References

  • Seed transaction: 0xdc53a6b5bf8e2962cf0e0eada6451f10956f4c0845a3ce134ddb050365f15c86
  • Attacker EOA: 0x9cc3270de4a3948449c1a73eabff5d0275f60785
  • Helper contract: 0x0b13d2b0d8571c3e8689158f6db1eedf6e9602d3
  • Profit recipient: 0xad2d2cb5f91e7adee7b029958a58fe6a38e282eb
  • DODO pool: 0x9ad32e3054268b849b84a8dbcc7c8f7c52e4e69a
  • MBC token: 0x4e87880a72f6896e7e0a635a5838ffc89b13bd17
  • ZZSH token: 0xee04a3f9795897fd74b7f04bb299ba25521606e6
  • MBC/USDT pair: 0x5b1bf836fba1836ca7ffce26f155c75dbfa4adf1
  • USDT/ZZSH pair: 0x33cca0e0cff617a2aef1397113e779e42a06a74a
  • Evidence artifacts used: seed transaction metadata, opcode trace, balance-diff report, verified MBC source, verified ZZSH source, and verified Pancake pair source under the collector seed artifacts.