FPR Custody Admin-Seizure Drain
Exploit Transactions
0xec1b969e1435a1449dd5179404c54b5c60e49f15a1bf6bf8922e8d2978102f4a0x1b66170220287bd90f72a97368f8dea2420a24c9585e9f39bf236af6d2a7dde60x43da4322052b045f442cdfc03bcccc797d3bba467beda501222c35d8fd0ebd810x1f8e814029a073c52a8668e6ff5bb3264445a8b29886cc9e3ca8ed5f89ccacd3Victim Addresses
0x81c5664be54d89e725ef155f14cf34e6213297b7BSC0xe2f0a9b60858f436e1f74d8cdbe03625b9bcc532BSC0x39eb555f5f7afd11224ca10e406dba05b4e21bd3BSC0xba5b235cddaac2595bce6bab79274f57fb82bf27BSCLoss Breakdown
Similar Incidents
STRAC Callback Drain
34%ZS Pair Burn Drain
33%BSC Callback Auth Drain
33%Transit Router V5 Drain
33%RSunTokenLocker Double Withdraw on BNB
32%Unauthenticated Pancake Callback Drain
32%Root Cause Analysis
FPR Custody Admin-Seizure Drain
1. Incident Overview TL;DR
Across four attacker-crafted BNB Chain transactions, the attacker cluster centered on EOA 0xe3104e645bc3f6fd821930a6a39ee509a0e87d3b repeatedly seized control of FPR custody contracts, withdrew the contracts' token or LP balances, and liquidated the recovered assets into USDT. The observed helper contracts were 0xe3293f89fd3b9336ac2d514ec4a90477ca94b0d8 and 0x5dd07f8b12b8d5dbdf3664c2fa7c37da5048b462, and the four seed transactions realized 29852036609404116752868 raw USDT units in aggregate.
The root cause is an unauthenticated setAdmin(address) function that rewrites the CurrentOwner storage slot without checking msg.sender. Because remaining(address,address) only checks msg.sender == CurrentOwner before transferring the contract's full ERC20 balance to an arbitrary recipient, any unprivileged caller can first set itself as owner and then drain the custody contract.
2. Key Background
The affected contracts are small FPR-related custody contracts on BNB Chain:
0x81c5664be54d89e725ef155f14cf34e6213297b70xe2f0a9b60858f436e1f74d8cdbe03625b9bcc5320x39eb555f5f7afd11224ca10e406dba05b4e21bd30xba5b235cddaac2595bce6bab79274f57fb82bf27
The first three contracts store time-allocation state such as startTime, oneDay, nintyDay, tokenAddr, recAddress, CurrentOwner, and lastTime. The fourth stores signAddress, tokenAddr, CurrentOwner, and user-order mappings. Despite serving slightly different business flows, all four expose the same dangerous administrative pair: setAdmin(address) and remaining(address,address).
The representative exploit used victim contract 0x81c5664be54d89e725ef155f14cf34e6213297b7, FPR token 0xa9c7ec037797dc6e3f9255ffde422da6bf96024d, PancakeRouter 0x10ed43c718714eb63d5aa57b78b54704e256024e, and USDT 0x55d398326f99059ff775485246999027b3197955. Before tx 0xec1b969e1435a1449dd5179404c54b5c60e49f15a1bf6bf8922e8d2978102f4a, the victim still held 29729739764442 raw FPR units, making the custody balance immediately recoverable once ownership was seized.
3. Vulnerability Analysis & Root Cause Summary
This incident is an ATTACK-category access-control failure. The intended invariant is straightforward: only the incumbent owner should be able to change CurrentOwner, and contract-held tokens or LP tokens should leave the custody contract only through a legitimately authorized path that preserves the custody policy. The code breaks that invariant because setAdmin(address) has selector 0x704b6c02 and performs a direct write to CurrentOwner with no authorization check. The companion function remaining(address,address) treats CurrentOwner as the only authorization source and then transfers the contract's full balance of any supplied token address to an arbitrary recipient. Once an attacker can rewrite CurrentOwner, the owner gate in remaining no longer provides any protection. The exploit is therefore deterministic: become owner, withdraw the victim-held asset, and liquidate it through public AMM liquidity. The same primitive exists across all four identified custody contracts, which is why the attacker reused the same flow multiple times.
4. Detailed Root Cause Analysis
The representative victim decompilation shows the exact breakpoint:
function setAdmin(address arg0) public payable {
CurrentOwner = (address(arg0)) | (uint96(CurrentOwner));
}
function remaining(address arg0, address arg1) public payable {
require(msg.sender == (address(CurrentOwner)), "Ownable: caller is not the owner");
address var_b = address(this);
(bool success, bytes memory ret0) = address(arg1).Unresolved_70a08231(var_b); // balanceOf(this)
(bool success, bytes memory ret0) = address(arg1).{ value: 0 ether }adfepixw(); // transfer(arg0, fullBalance)
}
The same unauthenticated setAdmin(address) and owner-gated remaining(address,address) pair also appears in the LP-locker contract 0xba5b235cddaac2595bce6bab79274f57fb82bf27, showing the issue is systemic rather than isolated to one instance.
The pre-state for the ACT opportunity is BNB Chain block 23904139, before helper deployment tx 0x033def1674a3b5593dcf34d0e9fd79359e4db52c6c9b4fc2706d76a79b48d5d1. At that point the vulnerable custody contracts were already deployed, held recoverable balances, and exposed publicly callable bytecode. No privileged information or privileged keys were required; the attacker only needed standard transaction submission and public on-chain state.
The seed trace for tx 0xec1b969e1435a1449dd5179404c54b5c60e49f15a1bf6bf8922e8d2978102f4a confirms the exploit sequence:
0x81c5664be54d89E725ef155F14cf34e6213297B7::setAdmin(0xe3293F89FD3B9336Ac2d514Ec4a90477ca94b0d8)
0x81c5664be54d89E725ef155F14cf34e6213297B7::remaining(0xe3293F89FD3B9336Ac2d514Ec4a90477ca94b0d8, BEP20FPR: [0xA9c7ec037797DC6E3F9255fFDe422DA6bF96024d])
BEP20FPR::transfer(0xe3293F89FD3B9336Ac2d514Ec4a90477ca94b0d8, 29729739764442)
BEP20USDT::transfer(0xE3104e645BC3f6fD821930a6a39EE509a0E87D3b, 26274795847172541865812)
The corresponding balance-diff artifact shows the same state transition numerically:
{
"victim_fpr_before": "29729739764442",
"victim_fpr_after": "1",
"attacker_usdt_before": "0",
"attacker_usdt_after": "26274795847172541865812"
}
This proves the end-to-end mechanism. First, slot 0 is rewritten to an attacker-controlled address. Second, remaining transfers the victim contract's full FPR balance out, leaving only one unit of dust. Third, the helper liquidates the recovered FPR through PancakeRouter and the attacker EOA receives USDT proceeds. The same logic explains the other seed transactions and the LP-locker drain, where the recovered asset is LP-derived value rather than only raw FPR.
5. Adversary Flow Analysis
The adversary strategy is a reusable single-transaction drain flow: deploy or reuse a helper contract, seize ownership with setAdmin, withdraw with remaining, and liquidate the recovered asset.
The observed lifecycle is:
- Helper deployment:
0x033def1674a3b5593dcf34d0e9fd79359e4db52c6c9b4fc2706d76a79b48d5d1created helper0xe3293f89fd3b9336ac2d514ec4a90477ca94b0d8.0x1f8e814029a073c52a8668e6ff5bb3264445a8b29886cc9e3ca8ed5f89ccacd3created helper0x5dd07f8b12b8d5dbdf3664c2fa7c37da5048b462.
- Unauthorized admin seizure and withdrawal:
0xec1b969e1435a1449dd5179404c54b5c60e49f15a1bf6bf8922e8d2978102f4a0x1b66170220287bd90f72a97368f8dea2420a24c9585e9f39bf236af6d2a7dde60x43da4322052b045f442cdfc03bcccc797d3bba467beda501222c35d8fd0ebd810x1f8e814029a073c52a8668e6ff5bb3264445a8b29886cc9e3ca8ed5f89ccacd3
- Asset liquidation:
- Recovered FPR was sold through PancakeRouter into USDT.
- In the LP-locker case, LP tokens were redeemed into component assets and the FPR leg was also sold into USDT.
The attacker cluster is defensibly identified. Creator-lookup data ties helper 0xe3293f89fd3b9336ac2d514ec4a90477ca94b0d8 to EOA 0xe3104e645bc3f6fd821930a6a39ee509a0e87d3b, and the seed traces show that helper executing the exploit path against victim contracts. The same EOA sent all four seed transactions, making the cluster attribution coherent across the full incident.
6. Impact & Losses
The observed attacker cluster extracted value from four FPR-related custody contracts and realized the proceeds as USDT. The recorded aggregate loss is:
[
{
"token_symbol": "USDT",
"amount": "29852036609404116752868",
"decimal": 18
}
]
For the representative exploit transaction alone, the attacker EOA's USDT balance increased by 26274795847172541865812 raw units, while the victim custody contract's FPR balance fell from 29729739764442 to 1. The repeated reuse of the same primitive across multiple contracts shows the vulnerability was permissionless and repeatable by any unprivileged adversary.
7. References
- Tx
0x033def1674a3b5593dcf34d0e9fd79359e4db52c6c9b4fc2706d76a79b48d5d1: helper deployment creating0xe3293f89fd3b9336ac2d514ec4a90477ca94b0d8 - Tx
0xec1b969e1435a1449dd5179404c54b5c60e49f15a1bf6bf8922e8d2978102f4a: representative exploit trace and balance diff - Tx
0x1b66170220287bd90f72a97368f8dea2420a24c9585e9f39bf236af6d2a7dde6: repeated custody drain - Tx
0x43da4322052b045f442cdfc03bcccc797d3bba467beda501222c35d8fd0ebd81: repeated custody drain - Tx
0x1f8e814029a073c52a8668e6ff5bb3264445a8b29886cc9e3ca8ed5f89ccacd3: LP-locker drain and helper creation for0x5dd07f8b12b8d5dbdf3664c2fa7c37da5048b462 - Decompilations:
0x81c5664be54d89e725ef155f14cf34e6213297b70xe2f0a9b60858f436e1f74d8cdbe03625b9bcc5320x39eb555f5f7afd11224ca10e406dba05b4e21bd30xba5b235cddaac2595bce6bab79274f57fb82bf27
- Helper creator attribution:
0xe3293f89fd3b9336ac2d514ec4a90477ca94b0d8created by0xe3104e645bc3f6fd821930a6a39ee509a0e87d3b