LBP exploit on BSC ~145k drained
Root cause: token balanceOf(pair) was computed dynamically from pair.getReserves() instead of returning the raw balance.
Calling sync() overwrites reserve1 with that manipulated value.
Exec flow:
- flashloan 2.2M USDT from PCS V3
- buy 500k USDT -> VICTIM
- donate USDT + VICTIM equal to reserves to the pair
- skim() recovers USDT, VICTIM stays trapped (balanceOf lies)
- dust + raw pair.swap() pulls back the trapped VICTIM
- dust + sync() reserve1 collapses from 1,262 to 3.79 VICTIM
- dump at the inflated price -> ~645K USDT out
- repay flash, walk away with ~145K
TUB/BSC drained for ~45 BNB
single tx, zero capital
Pledge contract claim function (0xb45c9928) takes reward amount as user input.
no validation. no accounting.
attacker staked 1 LP token, claimed 4.77Γ10Β²β· TUB
basically: tranfer(msg.sender, userInput)
3 flaws stacked:
- claim amount is caller supplied (no internal accrual)
- parent chain registers on any transfer (free sybils via CREATE)
- 1 LP wei passes the pledge gate
Exec flow:
flashswap 1000 wei WBNB -> buy dust TUB -> deploy 5 sybil contracts -> chain transfer to build referral tree -> mint 1 LP -> pledge -> call b45c9928 with arbitrary amount
Pledge pays claimer + all 5 parents (0.7x decay/level) = 8.73e27 TUB = 8.73e27 TUB = 100% of reward pool
dump into pair -> ~45 BNB out, pair WBNB -98.3%
β‘οΈJUDAO Postmortem ~227k exploit
Root cause: AMM reserve desync via token-side burn hooks
_update() runs two βdrain hooksβ whenever a transfer touches PancakeV2 Pair.
isBurnPair -> burns JUDAO from pair
Mining hook -> burns again, then calls pair.sync()
sync() freezes a falsified reserve1 into pair storage while balance1 still carries the attacker just deposited JUDAO.
Exec flow:
- flashloan 2.29M USDT from Moolah
- router buy -> 5.47M JUDAO
- transfer() those JUDAO directly to the pair -> _update() burns ~3.02M out of reserves + sync()
- pair.swap(2.52M USDT, 0, β¦) directly
- repay Moolah, keep the delta
PancakeV2 computes amount1In = balance1 - reserve1
After the hook: reserve1 is artificialy low (post-burn) AND balance1 also dropped, but the swap K invariant is now solved against a fake skewed ratio.
Pair pays USDT that no longer exists in reserves.
NET: 36 BNB + 205,259 USDT to attacker EOA.
https://t.co/2JhMr35RXX
π¨π¨ The DeFi ecosystem is currently facing a serial attack, more than +6 protocols exploited in less than ~48 hours.
Losses are currently estimated at ~2.8m
Stay Safe.
π¨ Singularity Finance exploited ~$413k
We investigation, this seems to be due to an oracle configuration problem allowing an attack by share inflation.
Giddy Exploit - Full breakdown
3 vaults drained to 0 in a single tx.
~16.7 LP tokens stolen. (~$1.3m)
Root cause : EIP-712 signature only covers keccak256(data) in SwapInfo not fromToken, aggregator, toToken or amount.
https://t.co/SiVKgePB4t
vault has 2 post-swap checks
- fromToken balance must decrease (INVALID_SRC_BALANCE_CHANGE)
- toToken balance must increase (SWAP_NO_TOKENS_RECEIVED)
attacker contract handles both in its fallback.
_fakeBalances[msg.sender] += 1
LP.transferFrom(vault, self, 1)
inside swapRewardTokens(), vault executes
IERC20(fromToken).approve(aggregator, MAX)
since fromToken = LP token and Aggregator = attacker contract, the vault just approved attacker for unlimited LP spending. Game over.
attacker grabbed a valid signature from a pending/past compound() tx and replaced
- fromToken -> vault LP tokens
- aggregator -> their own contract
- toToken -> their own contract (fake ERC20)
- amount -> type(uint256).max
Signature still valid. the data blob is unchanged.
compound() function lets an authorized signer reinvest vault rewards via token swaps.
- fromToken
- aggregator
- amount
- toToken
- data <-- only this is signed !
We strike first. This time, weβre striking Monaco. β‘οΈ
Blockraider will be at the WAIB Summit, June 9-10.
Will you cross paths with us ? πΆοΈ
#WAIBSummit#Monaco