通过本次闯关,让我们深入理解 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个可以从外部调用的函数:flipSwitch、turnSwitchOn和turnSwitchOff。
但是flipSwitch 是唯一可以调用的函数,因为turnSwitchOn和turnSwitchOff只有在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编码,你会发现不是这样的。
如何解题呢?如果你要挑战一下,可以暂停一下...
静态类型如下:
uint
等int
等address
bool
bytes32
等tuples
等这些类型的表示是用十六进制表示的,用零填充以覆盖32字节。
Input: 23 (uint256)
Output:
0x000000000000000000000000000000000000000000000000000000000000002a
Input: 0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f (address of Uniswap)
Output:
0x000000000000000000000005c69bee701ef814a2b6a3edd4b1652cb9cc5aa6f
对于动态类型,calldata编码基于以下内容:
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...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!