Ethernaut 题库闯关 - Switch 题解

  • honey_liu
  • 更新于 2023-08-18 17:36
  • 阅读 1140

通过本次闯关,让我们深入理解 calldata 数据编码。

Ethernaut 闯关投稿到 Ethernaut 题库闯关 专栏, 欢迎订阅,本题是一个名为 Switch 的合约,把 switchOn 设置为 true 则挑战成功, 难度等级:难。

Switch 合约如下:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Switch {
    bool public switchOn; // switch is off
    bytes4 public offSelector = bytes4(keccak256("turnSwitchOff()"));

     modifier onlyThis() {
        require(msg.sender == address(this), "Only the contract can call this");
        _;
    }

    modifier onlyOff() {
        //  将复杂的数据类型放入内存中
        bytes32[1] memory selector;
        // 检查位置(_data的位置)68处的calldata数据
        assembly {
            calldatacopy(selector, 68, 4) // //从calldata中抽取出函数选择器
        }
        require(
            selector[0] == offSelector,
            "Can only call the turnOffSwitch function"
        );
        _;
    }

    function flipSwitch(bytes memory _data) public onlyOff {
        (bool success, ) = address(this).call(_data);
        require(success, "call failed :(");
    }

    function turnSwitchOn() public onlyThis {
        switchOn = true;
    }

    function turnSwitchOff() public onlyThis {
        switchOn = false;
    }

}

通过本挑战,更好地让我们跟深刻的理解 calldata Calldata的编码。

分析合约

Switch 合约只有这3个可以从外部调用的函数:flipSwitchturnSwitchOnturnSwitchOff

但是flipSwitch 是唯一可以调用的函数,因为turnSwitchOnturnSwitchOff只有在msg.sender是当前合约时才能访问(因为onlyThis 修改器)。

让我们来看看你可以调用的函数:

 function flipSwitch(bytes memory _data) public onlyOff {
        (bool success, ) = address(this).call(_data);
        require(success, "call failed :(");
    }

你可以看到flipSwitch有一个函数修改器onlyOff, 此函数修改器对Calldata(calldata)进行检查。

modifier onlyOff() {
        // 将复杂的数据类型放入内存中
        bytes32[1] memory selector;
        // 检查位置(_data的位置)68处的calldata数据
        assembly {
            calldatacopy(selector, 68, 4) //从calldata中抽取函数选择器
        }
        require(
            selector[0] == offSelector,
            "Can only call the turnOffSwitch function"
        );
        _;
    }

函数修改器检查从位置 68 开始并且长度为4字节的数据是否是turnOffSwitch函数的选择器。

乍一看 ,flipSwitch只能在turnSwitchOff作为数据的情况下调用,但通过操纵calldata编码,你会发现不是这样的。

如何解题呢?如果你要挑战一下,可以暂停一下...

温习 calldata 编码

静态类型的 calldata编码

静态类型如下:

  • uint
  • int
  • address
  • bool
  • bytes32
  • tuples

这些类型的表示是用十六进制表示的,用零填充以覆盖32字节。

Input: 23 (uint256)
Output:
0x000000000000000000000000000000000000000000000000000000000000002a
Input: 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f (address of Uniswap)
Output: 
0x000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f

动态类型的calldata编码(string, bytes 和数组)

对于动态类型,calldata编码基于以下内容:

  • 前32个字节表示偏移量
  • 接下来的32个字节是数据长度
  • 继续接下来的是值(内容)

示例

  1. Bytes:
Input: 0x123

Output: 
0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000021234000000000000000000000000000000000000000000000000000000000000

其中:

offset:
0000000000000000000000000000000000000000000000000000000000000020

length(the value is 2 bytes length = 4 chrs):
0000000000000000000000000000000000000000000000000000000000000002

value(the value of string and bytes starts right after the length):
1234000000000000000000000000000000000000000000000000000000000000

2. String:

Input: “GM Frens”

Output: 
0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000008474d204672656e73000000000000000000000000000000000000000000000000

其中:


offset:
0000000000000000000000000000000000000000000000000000000000000020 

length:
00000000000000000000000000...

剩余50%的内容订阅专栏后可查看

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
honey_liu
honey_liu
0xFE8A...A8aE
江湖只有他的大名,没有他的介绍。