All incidents

HEALTH Zero-Transfer Price Manipulation

Share
Oct 20, 2022 11:08 UTCAttackLoss: 16.68 WBNBPending manual check1 exploit txWindow: Atomic
Estimated Impact
16.68 WBNB
Label
Attack
Exploit Tx
1
Addresses
2
Attack Window
Atomic
Oct 20, 2022 11:08 UTC → Oct 20, 2022 11:08 UTC

Exploit Transactions

TX 1BSC
0xae8ca9dc8258ae32899fe641985739c3fa53ab1f603973ac74b424e165c66ccf
Oct 20, 2022 11:08 UTCExplorer

Victim Addresses

0x32b166e082993af6598a89397e82e123ca44e74eBSC
0xf375709dbde84d800642168c2e8ba751368e8d32BSC

Loss Breakdown

16.68WBNB

Similar Incidents

Root Cause Analysis

HEALTH Zero-Transfer Price Manipulation

1. Incident Overview TL;DR

On BNB Chain transaction 0xae8ca9dc8258ae32899fe641985739c3fa53ab1f603973ac74b424e165c66ccf at block 22337426, an unprivileged attacker exploited a flaw in the HEALTH token at 0x32b166e082993af6598a89397e82e123ca44e74e to manipulate the Pancake HEALTH/WBNB pair at 0xf375709dbde84d800642168c2e8ba751368e8d32. The attacker bought HEALTH, executed 1000 zero-value HEALTH transfers, then sold back into the now-distorted pool price and exited with 16.641927146106351887 WBNB before gas and 16.289384036106351887 WBNB net after gas.

The root cause is a token-side accounting flaw inside HEALTH._transfer. The function permits value == 0, but still burns burnFee / 1000 of the pair's HEALTH balance and calls sync() whenever the sender is not the pair and the time gate is open. That lets any public caller repeatedly shrink the pair's HEALTH reserve without paying HEALTH into the pair, which directly inflates the apparent HEALTH price against WBNB.

2. Key Background

HEALTH is a BEP-20 style token that created a Pancake pair against WBNB through router 0x10ed43c718714eb63d5aa57b78b54704e256024e. In a Pancake-style AMM, the pair price is determined by reserves, so any external code path that changes pair balances and then calls sync() changes the trading price immediately.

The critical token configuration is public and readable on-chain:

  • uniswapV2Pair points to the HEALTH/WBNB pair.
  • pairStartTime and jgTime gate a periodic burn branch.
  • burnFee determines how much pair-held HEALTH is destroyed each time that branch runs.

The exploit does not require privileged roles, private keys, or attacker-specific artifacts. Once the burn window is active, any non-pair caller can hit the same public transfer path.

3. Vulnerability Analysis & Root Cause Summary

The vulnerability is an ATTACK-class token design flaw, not an oracle issue or router bug. HEALTH embeds market-moving logic inside the unrestricted _transfer path and does so independently of the user-supplied transfer amount. The function only checks value <= _balances[from], so value == 0 is accepted. After the burn window opens, any call where from != uniswapV2Pair computes a burn amount from the pair's current HEALTH balance, debits the pair, credits the dead address, and calls IPancakePair(uniswapV2Pair).sync(). Because this happens before the transfer's own value-dependent accounting matters economically, a zero-value transfer becomes a state-changing reserve manipulation primitive. Repeating that primitive steadily removes HEALTH from the pair while leaving WBNB in place, which raises the price at which the attacker can later sell HEALTH back to the pool.

The violated invariant is straightforward: a public token transfer, especially a zero-value transfer, must not arbitrarily reduce AMM reserves or reprice the pool unless the pair is directly participating and the reserve change is implied by the transferred amount. HEALTH breaks that invariant inside _transfer.

4. Detailed Root Cause Analysis

The verified HEALTH source shows the exact breakpoint:

function _transfer(address from, address to, uint256 value) private {
    require(value <= _balances[from]);
    require(to != address(0));
    ...
    if (block.timestamp >= pairStartTime.add(jgTime) && pairStartTime != 0) {
        if (from != uniswapV2Pair) {
            uint256 burnValue = _balances[uniswapV2Pair].mul(burnFee).div(1000);
            _balances[uniswapV2Pair] = _balances[uniswapV2Pair].sub(burnValue);
            _balances[_burnAddress] = _balances[_burnAddress].add(burnValue);
            ...
            emit Transfer(uniswapV2Pair,_burnAddress, burnValue);
            IPancakePair(uniswapV2Pair).sync();
        }
    }
    ...
}

This code comes from the verified HEALTH token source collected for 0x32b166e082993af6598a89397e82e123ca44e74e. The key observations are:

  1. value == 0 is legal because the only amount check is require(value <= _balances[from]).
  2. The burn branch depends on time and from != uniswapV2Pair, not on value > 0.
  3. The branch burns from _balances[uniswapV2Pair], not from the caller.
  4. sync() is called immediately after the burn, so the AMM adopts the manipulated reserve state on each trigger.

The transaction trace for 0xae8ca9dc... confirms that the attacker exploited exactly this path. It contains 1000 repeated calls of the form:

token::transfer(0xDE78112FF006f166E4ccfe1dfE4181C9619D3b5D, 0)
PancakePair::sync()

The collected trace contains that zero-value transfer pattern exactly 1000 times. The balance-diff artifact independently confirms the reserve effect:

{
  "token": "0x32b166e082993af6598a89397e82e123ca44e74e",
  "holder": "0xf375709dbde84d800642168c2e8ba751368e8d32",
  "delta": "-21941109637301307403350681"
}

and the dead address gains burned HEALTH:

{
  "token": "0x32b166e082993af6598a89397e82e123ca44e74e",
  "holder": "0x000000000000000000000000000000000000dead",
  "delta": "20073813435839968368224860"
}

That evidence matches the stated exploit conditions in root_cause.json: the burn window had to be active, burnFee had to be positive, the configured pair had to hold liquidity, and the attacker needed enough WBNB to acquire HEALTH before triggering the loop.

5. Adversary Flow Analysis

The adversary cluster identified in the evidence is:

  • EOA 0xde78112ff006f166e4ccfe1dfe4181c9619d3b5d, which submitted the transaction and received the final WBNB profit.
  • Helper contract 0x80e5fc0d72e4814cb52c16a18c2f2b87ef1ea2d4, which performed the exploit steps on-chain.

The end-to-end sequence is deterministic:

  1. A flash-loan-like funding source at 0x0fe261aee0d1c4dfddee4102e82dd425999065f4 transfers 40 WBNB to the helper.
  2. The helper approves Pancake Router and swaps the full 40 WBNB into HEALTH.
  3. The helper then calls HEALTH transfer(..., 0) 1000 times from a non-pair address, repeatedly burning pair-held HEALTH and syncing reserves.
  4. After the loop, the helper sells its HEALTH back into the manipulated pair.
  5. The helper repays 40 WBNB to the funding source and forwards the residual WBNB to the submitting EOA.

The trace shows the profit leg directly:

WBNB::transfer(0x80e5FC0d72e4814cb52C16A18c2F2B87eF1Ea2d4, 56641927146106351887)
WBNB::transfer(0x0fe261aeE0d1C4DFdDee4102E82Dd425999065F4, 40000000000000000000)
WBNB::transfer(0xDE78112FF006f166E4ccfe1dfE4181C9619D3b5D, 16641927146106351887)

Those values line up with the narrative in root_cause.json: the manipulated exit yields 56.641927146106351887 WBNB, 40 WBNB is repaid, and 16.641927146106351887 WBNB remains for the attacker. The trace also shows a smaller WBNB movement to the configured dev address, which explains the token's incidental auto-swap side effect during the sell.

6. Impact & Losses

The HEALTH/WBNB pair is the directly harmed pool. The measurable reserve-value loss captured in the artifacts is:

  • 16.675217568865166316 WBNB lost from the pair.
  • 21,941,109.637301307403350681 HEALTH removed from the pair over the transaction.
  • 19,768,106.913152402811469624 HEALTH burned directly from the pair during the repeated zero-transfer loop.

The attacker received 16.641927146106351887 WBNB, while the submitting EOA paid 0.35254311 BNB in gas. The resulting net profit is therefore 16.289384036106351887 WBNB. The configured dev address also received 0.033290422758814429 BNB from HEALTH's sell-side fee swap, which is incidental to the exploit and not the core loss mechanism.

7. References

  1. Exploit transaction 0xae8ca9dc8258ae32899fe641985739c3fa53ab1f603973ac74b424e165c66ccf on BNB Chain.
  2. Collected transaction metadata for the exploit transaction.
  3. Collected full transaction trace for the exploit transaction.
  4. Collected balance-diff artifact for the exploit transaction.
  5. Verified HEALTH token source for 0x32b166e082993af6598a89397e82e123ca44e74e.
  6. BNB Chain block 22337426.