RewardVault Proxy Reinitialization Theft
Exploit Transactions
0xd88f26f2f9145fa413db0cfd5d3eb121e3a50a3fdcee16c9bd4731e68332ce4bVictim Addresses
0x7bacb1c805cbbf7c4f74556a4b34fde7793d0887BSC0x3d11015d9044cabbb2504448e37f20d0d56e36f8BSCLoss Breakdown
Similar Incidents
MultiSender Arbitrary From Theft
39%Local Traders Price Takeover
35%Marketplace proxy 0x9b3e9b92 bug drains USDT and mints rewards
35%HedgePay Staking Proxy Repeated forceExit Withdrawal Drain
34%FiberRouter Allowance Reuse Drain
32%Telcoin Wallet Reinitialization Drain
32%Root Cause Analysis
RewardVault Proxy Reinitialization Theft
1. Incident Overview TL;DR
On BNB Smart Chain block 32820952, transaction 0xd88f26f2f9145fa413db0cfd5d3eb121e3a50a3fdcee16c9bd4731e68332ce4b let an unprivileged attacker take over RewardVaultDelegator at 0x7bacb1c805cbbf7c4f74556a4b34fde7793d0887, upgrade it to attacker-controlled code, and drain both vault-held assets and third-party user allowances in a single transaction. The attacker EOA was 0x8ebd046992afe07eacce6b9b3878fdb45830f42b; the attacker-operated helper contract was 0x5366c6ba729d9cf8d472500afc1a2976ac2fe9ff.
The root cause is a classic upgradeable-contract initialization failure. RewardVault.initialize(address,address,uint64) remained publicly reachable through the proxy fallback and had no one-time initialization guard, so any caller could rewrite the proxy's admin, distributor, and defaultExpireDuration storage. After seizing admin, the attacker legitimately invoked RewardVaultDelegator.setImplementation(address) and then executed arbitrary delegatecalled theft logic.
2. Key Background
RewardVaultDelegator is a proxy-style contract whose storage holds the live implementation address and the admin address. Any function selector that the proxy does not implement itself is forwarded to the current implementation through fallback() and delegatecall.
The deployed implementation was RewardVault at 0x3D11015d9044cAbBB2504448e37f20d0d56E36F8. Its state variables include admin, defaultExpireDuration, and distributor, which means any delegated call to the implementation mutates proxy storage rather than implementation storage.
That design is only safe if the initializer is reachable exactly once during deployment. The constructor of RewardVaultDelegator performs an initialization delegatecall and then sets implementation, but the runtime proxy still exposes the same implementation ABI to arbitrary callers through fallback. If the implementation initializer is left public and unguarded, post-deployment callers can repeat the initialization and overwrite privileged proxy state.
The vault also held custodial ERC-20 balances and had standing ERC-20 allowances from some users. Once the proxy admin was corrupted and the implementation was swapped, the proxy became a general-purpose attacker execution surface with access to both categories of value.
3. Vulnerability Analysis & Root Cause Summary
The incident is an ATTACK, not a pure MEV opportunity. The protocol invariant that only the authorized admin flow may change proxy control-plane state was broken by a public initializer that wrote directly into proxy storage when called through fallback. The vulnerable implementation code is:
function initialize(address payable _admin, address _distributor, uint64 _defaultExpireDuration) public {
require(_defaultExpireDuration > 0, "Incorrect inputs");
admin = _admin;
distributor = _distributor;
defaultExpireDuration = _defaultExpireDuration;
}
The proxy-side reachability is:
fallback() external payable {
_fallback();
}
function _fallback() internal {
if (msg.data.length > 0) {
(bool success, ) = implementation.delegatecall(msg.data);
...
}
}
And the privileged upgrade step that becomes exploitable after the admin overwrite is:
function setImplementation(address implementation_) public override onlyAdmin {
address oldImplementation = implementation;
implementation = implementation_;
emit NewImplementation(oldImplementation, implementation);
}
The enabling breakpoint is therefore precise: after deployment, initialize(address,address,uint64) should no longer be callable in proxy context by arbitrary users, but it remained callable and rewrote admin without any initialized-state check.
4. Detailed Root Cause Analysis
4.1 Code-Level Mechanism
The verified RewardVault source shows the initializer at the start of the contract, with no initialized flag, constructor-only restriction, or onlyAdmin gate:
function initialize(address payable _admin, address _distributor, uint64 _defaultExpireDuration) public {
require(_defaultExpireDuration > 0, "Incorrect inputs");
admin = _admin;
distributor = _distributor;
defaultExpireDuration = _defaultExpireDuration;
}
The verified proxy support code shows that all unknown selectors are forwarded to the current implementation:
fallback() external payable {
_fallback();
}
function _fallback() internal {
if (msg.data.length > 0) {
(bool success, ) = implementation.delegatecall(msg.data);
...
}
}
This means that a runtime call to RewardVaultDelegator.initialize(...) does not execute in the implementation's own storage. It executes in proxy storage, so writes to admin, distributor, and defaultExpireDuration mutate the live proxy authority slots.
4.2 Observed Pre-State
Immediately before the exploit, the historical reads collected for the proxy show:
pre
admin = 0xE9547CF7E592F83C5141bB50648317e35D27D29B
implementation = 0x3D11015d9044cAbBB2504448e37f20d0d56E36F8
distributor = 0x59558B7D604675A4CC9EB36c67f159550F73Bf9A
defaultExpireDuration = 7776000
The same pre-state also included vault-held reward tokens and existing ERC-20 approvals from third-party users to the vault. In particular, the balance diff for the exploit transaction shows the proxy held 1288 BUSD units that were later fully drained, and approved user 0xe83c6e8feedde85e72e810f82ee0943aa14ed2f6 exposed another 224.922137071076982 BUSD through a pre-existing allowance to the proxy.
4.3 Breakpoint in the Exploit Trace
The seed trace excerpt records the exact takeover sequence:
0x7bAC...::initialize(0x5366..., 0x5366..., 1)
0x3D11...::initialize(...) [delegatecall]
storage changes:
@ 1: 0x...e9547cf7e592f83c5141bb50648317e35d27d29b
-> 0x...5366c6ba729d9cf8d472500afc1a2976ac2fe9ff
@ 10: 0x0000000059558b7d604675a4cc9eb36c67f159550f73bf9a000000000076a700
-> 0x000000005366c6ba729d9cf8d472500afc1a2976ac2fe9ff0000000000000001
0x7bAC...::setImplementation(0x5366...)
storage changes:
@ 0: 0x...3d11015d9044cabbb2504448e37f20d0d56e36f8
-> 0x...5366c6ba729d9cf8d472500afc1a2976ac2fe9ff
Those two writes fully explain the privilege escalation:
- The attacker re-runs the public initializer through the proxy, setting
admin = 0x5366.... - Because
setImplementation(address)is guarded only byonlyAdmin, the now-attacker-controlled admin immediately upgrades the proxy to attacker code at the same address. - All subsequent proxy calls now delegate into attacker-controlled logic.
The historical post-state reads confirm the takeover:
post
admin = 0x5366c6BA729D9cF8d472500aFc1A2976aC2fE9fF
implementation = 0x5366c6BA729D9cF8d472500aFc1A2976aC2fE9fF
4.4 Why the Theft Was Then Possible
Once the proxy pointed to attacker code, the attacker no longer needed to bypass any additional authorization. The proxy itself became the execution identity for arbitrary delegated logic, which let the attacker:
- approve routers on behalf of the vault,
- transfer tokens already held by the vault,
- call
transferFromagainst users who had previously approved the vault as spender, - swap stolen assets into WBNB/BNB, and
- forward realized proceeds to the attacker EOA.
The receipt and trace evidence show exactly that pattern. For example, the BUSD leg includes an allowance read on the approved user, a transferFrom from that user into the proxy, and subsequent draining of the vault's own BUSD balance. The balance diff quantifies the same outcome:
- Approved user BUSD delta:
-224922137071076982 - RewardVault BUSD delta:
-1288000000000000000000 - BUSD captured downstream:
+1288224922137071076982
The native-balance deltas show the attacker EOA rising from 0.0960194366 BNB to 37.229943661953322829 BNB, for a net increase of 37.133924225353322829 BNB after 0.0027394225 BNB in gas.
The flaw is therefore not in PancakeRouter or any downstream token contract. Those systems behaved normally after the attacker had already obtained unauthorized proxy admin and arbitrary upgrade authority. The root cause remains the victim-side missing initializer guard combined with proxy fallback reachability.
The ACT exploit conditions were straightforward and public:
- the proxy still forwarded the
initialize(address,address,uint64)selector to the implementation, - the implementation still accepted any caller and any positive
_defaultExpireDuration, - and the current
adminretained unrestricted implementation-upgrade authority throughsetImplementation(address).
The violated security principles were equally direct:
- initialization of upgradeable logic must be one-time,
- privileged proxy storage must never be writable through public delegated entrypoints,
- and a contract that holds assets or user approvals must never permit arbitrary post-deployment code execution.
5. Adversary Flow Analysis
The attacker flow is a single-transaction ACT sequence.
5.1 Actors
- Attacker EOA:
0x8ebd046992afe07eacce6b9b3878fdb45830f42b - Attacker helper / malicious implementation:
0x5366c6ba729d9cf8d472500afc1a2976ac2fe9ff - Victim proxy:
0x7bacb1c805cbbf7c4f74556a4b34fde7793d0887 - Legitimate implementation before takeover:
0x3D11015d9044cAbBB2504448e37f20d0d56E36F8
5.2 Transaction Sequence
The attacker-submitted transaction was:
- Chain: BNB Smart Chain (
chainid = 56) - Block:
32820952 - Transaction hash:
0xd88f26f2f9145fa413db0cfd5d3eb121e3a50a3fdcee16c9bd4731e68332ce4b - Sender:
0x8ebd046992afe07eacce6b9b3878fdb45830f42b to:0x5366c6ba729d9cf8d472500afc1a2976ac2fe9ff
The helper contract orchestrated three stages inside the same transaction:
-
Proxy reinitialization:
- Calls
RewardVaultDelegator.initialize(0x5366..., 0x5366..., 1). - This delegatecalls into
RewardVault.initializeand rewrites the proxy's privileged slots.
- Calls
-
Unauthorized but now-authorized implementation change:
- Calls
RewardVaultDelegator.setImplementation(0x5366...). - Emits
NewImplementation(0x3D11015d9044cAbBB2504448e37f20d0d56E36F8, 0x5366c6ba729d9cf8d472500afc1a2976ac2fe9ff).
- Calls
-
Delegatecalled theft and profit realization:
- Executes attacker-defined logic through the proxy.
- Drains vault-held tokens.
- Uses existing user approvals to pull more tokens through the proxy identity.
- Swaps stolen assets into WBNB/BNB and forwards proceeds to the attacker EOA.
The critical decision point is after stage 1. Once admin is overwritten, every later step is simply the normal capability of the admin role or the arbitrary capability of the newly installed implementation.
6. Impact & Losses
The exploit caused both governance compromise and direct asset loss.
Governance and control impact:
- Full unauthorized takeover of the proxy admin role.
- Unauthorized replacement of the production implementation with attacker code.
- Permanent loss of trust in all proxy-held allowances and custodied assets at the time of exploitation.
Measured asset losses from the collected balance diff and the auditor's loss summary:
- RACA:
37335415.530122063120663838(37335415530122063120663838raw, 18 decimals) - BUSD:
1288.224922137071076982(1288224922137071076982raw, 18 decimals) - FLOKI:
12341371.593503137(12341371593503137raw, 9 decimals) - OLE:
3945.033718231379966543(3945033718231379966543raw, 18 decimals) - CSIX:
18882.727542566235544252(18882727542566235544252raw, 18 decimals) - BABY:
3318.622570906921282235(3318622570906921282235raw, 18 decimals)
Native-profit realization for the attacker:
- BNB before:
0.0960194366 - BNB after:
37.229943661953322829 - Gas paid:
0.0027394225 - Net BNB increase:
37.133924225353322829
The incident also proves that third-party users who had approved the vault as spender were exposed, even when their assets were not physically held inside the vault at the start of the transaction.
7. References
Primary transaction and state evidence:
- Exploit transaction:
0xd88f26f2f9145fa413db0cfd5d3eb121e3a50a3fdcee16c9bd4731e68332ce4b - Seed metadata for the transaction, including sender, gas, and block context
- Seed balance diff showing native and ERC-20 deltas
- Receipt showing
NewImplementationand downstream token-transfer logs - Trace excerpt showing
initialize,setImplementation, and subsequent delegatecalled theft actions - Historical pre/post reads confirming admin and implementation changes
Primary victim code evidence:
RewardVault.initialize(address,address,uint64)in the verifiedRewardVaultimplementationRewardVaultDelegator.setImplementation(address)in the verified proxy contractDelegatorInterface.fallback()and_fallback()showing unrestricted delegatecall forwarding
Addresses of interest:
- Victim proxy:
0x7bacb1c805cbbf7c4f74556a4b34fde7793d0887 - Victim implementation:
0x3D11015d9044cAbBB2504448e37f20d0d56E36F8 - Original admin before exploit:
0xE9547CF7E592F83C5141bB50648317e35D27D29B - Original distributor before exploit:
0x59558B7D604675A4CC9EB36c67f159550F73Bf9A - Attacker EOA:
0x8ebd046992afe07eacce6b9b3878fdb45830f42b - Attacker helper / malicious implementation:
0x5366c6ba729d9cf8d472500afc1a2976ac2fe9ff