All incidents

PLTD reserve-burn exploit

Share
Oct 17, 2022 10:44 UTCAttackLoss: 24,497.86 USDTPending manual check1 exploit txWindow: Atomic
Estimated Impact
24,497.86 USDT
Label
Attack
Exploit Tx
1
Addresses
1
Attack Window
Atomic
Oct 17, 2022 10:44 UTC → Oct 17, 2022 10:44 UTC

Exploit Transactions

TX 1BSC
0x8385625e9d8011f4ad5d023d64dc7985f0315b6a4be37424c7212fe4c10dafe0
Oct 17, 2022 10:44 UTCExplorer

Victim Addresses

0x29b2525e11bc0b0e9e59f705f318601ea6756645BSC

Loss Breakdown

24,497.86USDT

Similar Incidents

Root Cause Analysis

PLTD reserve-burn exploit

1. Incident Overview TL;DR

On BSC, transaction 0x8385625e9d8011f4ad5d023d64dc7985f0315b6a4be37424c7212fe4c10dafe0 drained the PLTD/USDT PancakeSwap pair by manipulating PLTD's own transfer logic. The attacker borrowed USDT through nested DODO flash loans, bought PLTD, primed PLTD's global _bron counter with a sell, then used a later ordinary transfer to burn PLTD directly from the live pair and force sync(). That reserve update made the pair appear to hold only 1 raw PLTD unit against 690497862548928068832687 raw USDT units, so the attacker could dump PLTD back into the pool at an artificial price and withdraw essentially all USDT liquidity. The root cause is not a PancakeSwap bug; it is PLTD's design that lets an unrelated user transfer confiscate tokens from uniswapV2Pair and immediately update AMM reserves.

2. Key Background

PLTD (0x29b2525e11bc0b0e9e59f705f318601ea6756645) is a fee-on-transfer token with separate buy, sell, and ordinary-transfer paths. During sells, PLTD accumulates a global _bron amount equal to 50% of the sold amount. On a later ordinary transfer, if _bron > 0, the contract burns exactly _bron from uniswapV2Pair to 0x000000000000000000000000000000000000dEaD and then calls IPancakePair(uniswapV2Pair).sync(). PancakeSwap pricing depends on the pair's live balances after sync(), so directly mutating the pair's token balance outside normal swap or liquidity flows breaks the reserve assumptions the AMM relies on. DODO flash-loan pools supplied the temporary USDT capital needed to accumulate and later dump a large PLTD position in one transaction.

3. Vulnerability Analysis & Root Cause Summary

The vulnerability is an on-chain attack caused by unsafe token-side reserve mutation. In the PLTD sell path, _tokenTransferSell computes bronNum = tAmount * 50 / 100 and adds it to the global _bron state. In the ordinary-transfer path, _tokenTransfer checks _bron, zeroes it, calls _bronTransfer(uniswapV2Pair, _destroyAddress, brone, currentRate), and immediately calls IPancakePair(uniswapV2Pair).sync(). _bronTransfer debits reflected balance from the pair itself, so the transfer caller can destroy pair inventory that the pair never consented to send. Because the burn and reserve update happen inside PLTD, the attacker does not need special permissions from the pair or router. The invariant that unrelated users must not be able to arbitrarily reduce AMM reserves is violated at the PLTD token layer. Once the pair reserve is collapsed, a normal router sell extracts the paired USDT at a fabricated price.

4. Detailed Root Cause Analysis

The verified PLTD source shows the exploit mechanism directly:

function _tokenTransferSell(address sender, address recipient, uint256 tAmount, bool takeFee) private {
    ...
    uint256 bronNum = tAmount.mul(50).div(100);
    uint256 brone = _bron;
    uint256 brond = brone.add(bronNum);
    _bron = brond;
}

function _tokenTransfer(address sender, address recipient, uint256 tAmount, bool takeFee) private {
    ...
    uint256 brone = _bron;
    if(brone > 0){
       _bron = 0 ;
       _bronTransfer(uniswapV2Pair,_destroyAddress,brone, currentRate);
       IPancakePair(uniswapV2Pair).sync();
    }
}

This means a sell does not immediately charge the seller's balance for the later burn. Instead, it stores debt globally and pushes that debt onto uniswapV2Pair during a later ordinary transfer. The seed trace confirms the exploit sequence inside one attacker-crafted transaction. First, the helper contract took nested flash loans from DPPAdvanced and DPPOracle. It then swapped borrowed USDT for PLTD, sold enough PLTD to populate _bron, and called PancakePair::skim to recover PLTD that had been transferred into the pair. Next, the attacker sent a 1e18 PLTD ordinary transfer to 0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE. That transfer caused PLTD to emit a burn from PancakePair to the dead address for 58021399470815555197897 raw PLTD units and immediately invoke PancakePair::sync().

The balance diff independently confirms the economic result. Before the transaction, the PLTD/USDT pair held 24497862548928068832687 raw USDT units; after the transaction it held only 14. The final profit recipient 0x083c057221e95d45655489fb01b05c4806387c19 gained 24497862548928068832673 raw USDT units. The attacker could realize this because after sync(), the pair reserve state no longer reflected a legitimate market trade; it reflected an arbitrary token-side debit from the pair's PLTD balance.

5. Adversary Flow Analysis

The submitting EOA was 0x6ded5927f2408a8d115da389b3fe538990e93c5b, and the helper contract executing the exploit was 0x83797825f6020a443b95fa3932ab13dd61d48b49. The transaction input was a simple 0x3ccfd60b call into that helper. The trace shows the helper borrowing 222000000000000000000000 raw USDT from DPPAdvanced and 444000000000000000000000 raw USDT from DPPOracle, for 666000000000000000000000 raw USDT total. With that liquidity, the attacker bought PLTD from the Pancake router, then sold 116042798941631110395795 raw PLTD to set _bron = 58021399470815555197897. After reclaiming excess PLTD with skim, the attacker executed the ordinary transfer that burned pair inventory and synchronized reserves. The final router sell dumped the remaining PLTD into the distorted pair, nearly zeroed the pair's USDT, repaid both flash-loan pools, and transferred 24497862548928068832673 raw USDT to 0x083c057221e95d45655489fb01b05c4806387c19.

Representative trace evidence:

DPPAdvanced::flashLoan(... 222000000000000000000000 ...)
DPPOracle::flashLoan(... 444000000000000000000000 ...)
PancakePair::skim(0x83797825f6020A443B95Fa3932ab13Dd61d48b49)
PLTD::transfer(0x16b9a82891338f9bA80E2D6970FddA79D1eb0daE, 1000000000000000000)
emit Transfer(from: PancakePair, to: 0x000000000000000000000000000000000000dEaD, value: 58021399470815555197897)
PancakePair::sync()
BEP20USDT::transfer(0x083c057221e95D45655489Fb01b05C4806387C19, 24497862548928068832673)

6. Impact & Losses

The immediate victimized asset was the USDT liquidity held by the PLTD/USDT Pancake pair at 0x4397c76088db8f16c15455eb943dd11f2df56545. The pair lost 24497862548928068832673 raw USDT units, leaving only 14 raw units after the exploit transaction. The token logic also burned 58021399470815555197897 raw PLTD units from the pair to the dead address during the reserve-collapse step. The exploit was fully permissionless under the ACT model because it relied only on public BSC state, public contract code, and permissionless flash-loan and router access.

7. References

  • Seed transaction: 0x8385625e9d8011f4ad5d023d64dc7985f0315b6a4be37424c7212fe4c10dafe0
  • Victim token: 0x29b2525e11bc0b0e9e59f705f318601ea6756645 (PLTD)
  • Victim pair: 0x4397c76088db8f16c15455eb943dd11f2df56545 (PLTD/USDT Pancake pair)
  • Router: 0x10ed43c718714eb63d5aa57b78b54704e256024e
  • Flash-loan pools: 0xd7b7218d778338ea05f5ecce82f86d365e25dbce and 0x9ad32e3054268b849b84a8dbcc7c8f7c52e4e69a
  • Verified PLTD source used for validation: seed/56/0x29b2525e11bc0b0e9e59f705f318601ea6756645/src/Contract.sol
  • Trace used for validation: seed/56/0x8385625e9d8011f4ad5d023d64dc7985f0315b6a4be37424c7212fe4c10dafe0/trace.cast.log
  • Balance diff used for validation: seed/56/0x8385625e9d8011f4ad5d023d64dc7985f0315b6a4be37424c7212fe4c10dafe0/balance_diff.json