HEALTH Zero-Transfer Price Manipulation
Exploit Transactions
0xae8ca9dc8258ae32899fe641985739c3fa53ab1f603973ac74b424e165c66ccfVictim Addresses
0x32b166e082993af6598a89397e82e123ca44e74eBSC0xf375709dbde84d800642168c2e8ba751368e8d32BSCLoss Breakdown
Similar Incidents
BBOX Pair Burn Price Manipulation
44%STO Pending-Sell Burn Reserve Manipulation
38%TiFi Oracle Manipulation
37%SellToken Reward Oracle Manipulation
37%STOToken Sell-Hook Reserve Manipulation Drains the STO/WBNB Pancake Pair
36%FortuneWheel BNBP/Link Price-Manipulation Fee Drain
36%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:
uniswapV2Pairpoints to the HEALTH/WBNB pair.pairStartTimeandjgTimegate a periodic burn branch.burnFeedetermines 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:
value == 0is legal because the only amount check isrequire(value <= _balances[from]).- The burn branch depends on time and
from != uniswapV2Pair, not onvalue > 0. - The branch burns from
_balances[uniswapV2Pair], not from the caller. 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:
- A flash-loan-like funding source at
0x0fe261aee0d1c4dfddee4102e82dd425999065f4transfers40WBNB to the helper. - The helper approves Pancake Router and swaps the full
40WBNB into HEALTH. - The helper then calls HEALTH
transfer(..., 0)1000 times from a non-pair address, repeatedly burning pair-held HEALTH and syncing reserves. - After the loop, the helper sells its HEALTH back into the manipulated pair.
- The helper repays
40WBNB 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.675217568865166316WBNB lost from the pair.21,941,109.637301307403350681HEALTH removed from the pair over the transaction.19,768,106.913152402811469624HEALTH 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
- Exploit transaction
0xae8ca9dc8258ae32899fe641985739c3fa53ab1f603973ac74b424e165c66ccfon BNB Chain. - Collected transaction metadata for the exploit transaction.
- Collected full transaction trace for the exploit transaction.
- Collected balance-diff artifact for the exploit transaction.
- Verified HEALTH token source for
0x32b166e082993af6598a89397e82e123ca44e74e. - BNB Chain block
22337426.