RFB Reward Brute Force
Exploit Transactions
0xcc8fdb3c6af8bb9dfd87e913b743a13bbf138a143c27e0f387037887d28e3c7aVictim Addresses
0x26f1457f067bf26881f311833391b52ca871a4b5BSC0x2bd049f1a6a4e93421d93d72afb7cb22cd958c43BSCLoss Breakdown
Similar Incidents
OKC Flash-LP Reward Drain
32%NFD Reward Sybil Exploit
32%Sareon Reward Drain
32%SellToken Reward Oracle Manipulation
31%ATK Reward Flashswap Overclaim
31%EGD Finance Reward Oracle Manipulation
31%Root Cause Analysis
RFB Reward Brute Force
1. Incident Overview TL;DR
On BNB Chain transaction 0xcc8fdb3c6af8bb9dfd87e913b743a13bbf138a143c27e0f387037887d28e3c7a, an unprivileged adversary used a 20 WBNB DODO flash loan to buy and sell Roast Football (RFB, 0x26f1457f067bf26881f311833391b52ca871a4b5) repeatedly inside one transaction. The profitable probes triggered RFB's reward path and drained BNB from the distributor 0x2bd049f1a6a4e93421d93d72afb7cb22cd958c43, after which the attacker repaid the flash loan and kept the remaining BNB. The root cause is that RFB treated a value-bearing reward as random even though the seed was built from same-transaction inputs the buyer could control and retry.
2. Key Background
Roast Football is a fee-on-transfer token with a Pancake pair at 0x03184aaa6ad4f7be876423d9967d1467220a544e and an external dividend distributor at 0x2bd049f1a6a4e93421d93d72afb7cb22cd958c43. On every buy from the pair, _transferFrom pushes execution into randMod, which may immediately call withdrawDistributor or withdrawPlazz on the distributor.
The relevant configuration is visible in the verified source:
pair = IUniswapV2Factory(router.factory()).createPair(router.WETH(), address(this));
distributor = IDividendDistributor(0x2BD049f1a6A4e93421D93d72AfB7Cb22Cd958c43);
...
if (sender == pair) {
luckyNum[recipient].push(randMod(recipient, amount));
}
This means any public buy through the Pancake pair can reach the reward logic. No privileged role was needed on RFB, PancakeRouter, the distributor, or the DODO pool.
3. Vulnerability Analysis & Root Cause Summary
The vulnerability is an attack-class logic flaw in RFB's reward mechanism. randMod computes a pseudo-random value from block.number, block.timestamp, buyer, and _balances[pair], then immediately pays BNB from the distributor if the modulus hits one of several winning branches. During a single transaction, block.number and block.timestamp are fixed, while the attacker can choose buyer and influence _balances[pair] by selecting trade sizes. Because the reward is settled immediately, the attacker can wrap each probe in an isolated call, revert losing attempts, and keep only winning attempts. That converts the reward system from a random lottery into a deterministic search problem. The explicit invariant violation is that distributor funds should not be claimable through buyer-controlled same-transaction entropy.
4. Detailed Root Cause Analysis
The breakpoint is in RFB::randMod:
function randMod(address buyer, uint256 buyamount) internal returns (uint) {
uint randnum = uint(keccak256(abi.encodePacked(block.number, block.timestamp, buyer, _balances[pair])));
uint256 buyBNBamount = buyamount.div(10**_decimals).mul(getPrice());
if (randnum % (1000 * luckyMultiplier) == 888) {
if (buyBNBamount.mul(100) > 10 ether) {
buyBNBamount = 0.1 ether;
}
try distributor.withdrawPlazz(buyer, buyBNBamount.mul(100).mul(90).div(100)) {} catch {}
try distributor.withdrawPlazz(marketingFeeReceiver, buyBNBamount.mul(100).mul(10).div(100)) {} catch {}
} else if (randnum % (10 * luckyMultiplier) == 8) {
if (buyBNBamount > 10 ether) {
buyBNBamount = 10 ether;
}
try distributor.withdrawPlazz(buyer, buyBNBamount.mul(90).div(100)) {} catch {}
try distributor.withdrawPlazz(marketingFeeReceiver, buyBNBamount.mul(10).div(100)) {} catch {}
}
}
The exploit transaction starts from EOA 0x5f7db41e2196080f397cdcf8dd58e8adfdaf2ade, which calls helper contract 0xd5de2914bc6d2f005228a04289e8d518c710a049. The trace shows the helper taking a public DODO flash loan:
0x0fe261aeE0d1C4DFdDee4102E82Dd425999065F4::flashLoan(20000000000000000000, 0, 0xD5DE2914..., 0x00)
0xD5DE2914...::DPPFlashLoanCall(...)
Inside the callback, the helper repeatedly invokes check(amount) with descending buy sizes. Each probe buys RFB, triggers randMod through a pair-originating transfer, sells back to WBNB, and reverts if the cycle is not profitable. The trace shows many reverting attempts followed by committed wins. A committed winning probe reaches these distributor payouts:
0x2BD049f1a6A4e93421D93d72AfB7Cb22Cd958c43::withdrawPlazz(0xD5DE2914..., 9000000000000000000)
0x2BD049f1a6A4e93421D93d72AfB7Cb22Cd958c43::withdrawPlazz(0x5C0A527BEeF80F5e479Abb0776e53073aaa2A935, 1000000000000000000)
Those 9 BNB and 1 BNB transfers match the 90/10 split in the randMod branch after the 10 BNB cap. The balance diff confirms the economic result of the same transaction: the distributor balance fell from 20858849613471113908 wei to 3136238716351357664 wei, while the adversary EOA rose from 993701635000000000 wei to 12482119295749381292 wei, a net gain of 11488417660749381292 wei after gas.
5. Adversary Flow Analysis
The end-to-end adversary flow is:
- The attacker EOA submits a transaction to helper contract
0xd5de2914bc6d2f005228a04289e8d518c710a049. - The helper borrows 20 WBNB from DODO pool
0x0fe261aee0d1c4dfddee4102e82dd425999065f4. - The helper loops through candidate trade sizes by calling its own
check(amount)entrypoint. - Each
checkcall buys RFB through PancakeRouter, which makes the RFB pair the sender and therefore triggersrandModfor the helper as buyer. - Losing probes revert and restore state; winning probes commit distributor payouts.
- The helper sells the bought RFB back to WBNB/BNB, wraps enough BNB to repay the flash loan, repays the DODO pool, and sends the remaining BNB back to the EOA.
This is an ACT exploit because every step uses public interfaces and public state. The attacker's only advantage was the ability to search the manipulable reward seed in one transaction.
6. Impact & Losses
The directly measured loss is 17.722610897119756244 BNB from the distributor, encoded on-chain as 17722610897119756244 wei. The adversary EOA realized 11.488417660749381292 BNB net profit after gas. The marketing receiver 0x5c0a527beef80f5e479abb0776e53073aaa2a935 also received BNB because the winning reward branch split payouts 90/10 between buyer and marketing receiver.
7. References
- Transaction metadata for
0xcc8fdb3c6af8bb9dfd87e913b743a13bbf138a143c27e0f387037887d28e3c7a - Full execution trace showing
flashLoan, repeatedcheck(amount)calls, and committedwithdrawPlazzpayouts - Balance diff showing distributor loss and attacker profit
- Verified RFB source for
_transferFromandrandMod