0x5fbbb391d54f4FB1d1CF18310c93d400BC80042EBSCAn unprivileged BSC attacker exploited the unverified sale contract at 0x5fbbb391d54f4FB1d1CF18310c93d400BC80042E in block 44348367. The attacker deployed helper contract 0x6def9e4a6bb9c3bfe0648a11d3fff14447079e78, flash-borrowed USDT, repeatedly invoked a public victim function, repaid the flash loan, and retained 10044.490614704315622688 USDT. The root cause was a public payout path that treated a token-sale conversion output as a USDT payout amount, so each call withdrew far more USDT than the caller supplied.
The victim is an unverified sale-style contract created by 0x76eBdad3F69695dBD60cb64411562325cCfbEceD. At the exploit state it exposed public getters consistent with a sale configuration: salePrice() = 8000000000000000000000, minBuy() = 100000000000000000, maxBuy() = 600000000000000000000, and owner() = 0x76eBdad3F69695dBD60cb64411562325cCfbEceD. It also held 10055198000000000000000 raw USDT units of reserve at token address 0x55d398326f99059fF775485246999027B3197955. The pricing path depended on Chainlink BNB/USD oracle 0x0567F2323251f0Aab15c8dFb1967E4e8A7D42aeE, whose answer at the exploit state was 61840939882.
The issue is an externally reachable reserve-draining attack, not an ownership takeover. The victim exposed public selector without access control beyond pause and range checks. The trace shows this path first pulling raw USDT units from the caller and then paying back raw USDT units from the victim reserve. That output matches the sale-price formula , which is appropriate for token-sale pricing but not for a USDT reserve payout. The contract therefore violated the reserve-conservation invariant for a public payout function: reserve outflow exceeded user contribution on every call. Because the function was public and the owner remained unchanged, any unprivileged actor with temporary capital could realize the drain.
0x85d07203(uint256,address)76500000000000000000989635670427665056608amount * salePrice / (latestAnswer * 1e10)The critical invariant is: for a public payout call, the victim reserve must not decrease by more than the caller contributes unless an owner-authorized withdrawal path is executing. The exploitable breakpoint is inside selector 0x85d07203, after the contract has already pulled USDT from the caller and before it transfers an overcomputed amount back out in USDT.
From the victim code artifacts, selector 0x85d07203 is public:
Victim decompilation:
/// @custom:selector 0x85d07203
/// @custom:signature Unresolved_85d07203(uint256 arg0, address arg1) public
function Unresolved_85d07203(uint256 arg0, address arg1) public { ... }
The exploit trace shows the concrete asset mismatch on-chain. Within the flash-loan callback, the helper repeatedly calls the victim, and each iteration transfers in 76.5 USDT then receives 989.635670427665056608 USDT back:
Seed trace excerpt:
BEP20USDT::transferFrom(helper, victim, 76500000000000000000)
emit Transfer(from: helper, to: victim, value: 76500000000000000000)
BEP20USDT::transfer(helper, 989635670427665056608)
emit Transfer(from: victim, to: helper, value: 989635670427665056608)
The computed output is consistent with the pricing formula cited in the analysis:
76500000000000000000 * 8000000000000000000000 / (61840939882 * 1e10)
= 989635670427665056608
That formula is semantically a token-sale conversion, but the contract uses it as a USDT transfer amount. This converts a pricing quote into immediate reserve outflow. No privileged state change is required. The only exploit conditions are that the victim is unpaused, holds enough USDT, and the caller can fund or flash-borrow the input amount.
The attacker EOA 0x5af00b07a55f55775e4d99249dc7d81f5bc14c22 first deployed helper contract 0x6def9e4a6bb9c3bfe0648a11d3fff14447079e78 in tx 0xe2ad1a84ef3dad3bd1d0ba234d30b99a7961384a4a03284507ab5e8ee626c9e7. In the next transaction, 0xbd330fd17d0f825042474843a223547132a49abb0746a7e762a0b15cf4bd28f6, the helper borrowed 825555500000000000000 raw USDT units from flash-loan pool 0x6098A5638d8D7e9Ed2f952d35B2b67c34EC6B476, entered the public victim path eleven times, repaid the principal, and transferred the residual USDT to the attacker EOA.
Seed trace excerpt:
flashLoan(0, 825555500000000000000, helper, ...)
...
BEP20USDT::transfer(flashLoanPool, 825555500000000000000)
BEP20USDT::transfer(attackerEOA, 10044490614704315622688)
The owner was not changed during the exploit block. This matters because it rules out a privileged-admin explanation and confirms that the exploit depended only on public callability and reserve mispricing.
The victim contract lost 10044.490614704315622688 USDT in a single transaction. The seed balance diff records the victim reserve dropping from 10055198000000000000000 to 10707385295684377312 raw units, while the attacker EOA gained 10044490614704315622688 raw units of USDT. Gas cost was a small BNB outflow from the attacker EOA, and the reported net profit remained about 10043.595452257698101008 USD after fee conversion.
0xbd330fd17d0f825042474843a223547132a49abb0746a7e762a0b15cf4bd28f60xe2ad1a84ef3dad3bd1d0ba234d30b99a7961384a4a03284507ab5e8ee626c9e70x5fbbb391d54f4FB1d1CF18310c93d400BC80042E0x5af00b07a55f55775e4d99249dc7d81f5bc14c220x6def9e4a6bb9c3bfe0648a11d3fff14447079e780x55d398326f99059fF775485246999027B31979550x6098A5638d8D7e9Ed2f952d35B2b67c34EC6B4760x0567F2323251f0Aab15c8dFb1967E4e8A7D42aeE