diff --git a/Writeup/doublespending/day23/Ans.sol b/Writeup/doublespending/day23/Ans.sol new file mode 100644 index 00000000..29593847 --- /dev/null +++ b/Writeup/doublespending/day23/Ans.sol @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "./WBC.sol"; +import {console} from "forge-std/Test.sol"; + +contract Ans { + WBC public immutable wbc; + + constructor(address wbc_) { + wbc = WBC(wbc_); + wbc.bodyCheck(); + } + + function win() external { + wbc.ready(); + } + + function judge() external view returns (address) { + return block.coinbase; + } + + function steal() external pure returns (uint160) { + return 507778882907781185490817896798523593512684789769; + } + + function execute() external pure returns (bytes32) { + string memory ans = "HitAndRun"; + return bytes32(uint256(uint80(bytes10(abi.encodePacked(uint8(bytes(ans).length), ans))))); + } + + function shout() external view returns (string memory) { + if (gasleft() % 3 == 2) { + return "I'm the best"; + } else { + return "We are the champion!"; + } + } +} diff --git a/Writeup/doublespending/day23/WBC.t.sol b/Writeup/doublespending/day23/WBC.t.sol new file mode 100644 index 00000000..d3743965 --- /dev/null +++ b/Writeup/doublespending/day23/WBC.t.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {console2} from "forge-std/console2.sol"; +import {Test} from "forge-std/Test.sol"; +import {WBC, WBCBase} from "src/WBC/WBC.sol"; +import {Ans} from "src/WBC/Ans.sol"; + +contract WBCTest is Test { + WBCBase public base; + WBC public wbc; + Ans public ans; + + uint256 count; + + function setUp() external { + uint256 startTime = block.timestamp + 60; + uint256 endTime = startTime + 60; + uint256 fullScore = 100; + base = new WBCBase(startTime, endTime, fullScore); + base.setup(); + wbc = base.wbc(); + } + + function testExploit() external { + for (uint256 i = 0; i < 1000; ++i) { + try new Ans{salt: bytes32(i)}(address(wbc)) returns (Ans _ans) { + ans = _ans; + break; + } catch {} + } + ans.win(); + base.solve(); + assertTrue(base.isSolved()); + } + + function testCannotHomeRunEasily() external { + vm.expectRevert("try again"); + wbc.homerun(); + vm.expectRevert(); + base.solve(); + } +} diff --git a/doublespending.md b/doublespending.md index 67cdaa85..6013028c 100644 --- a/doublespending.md +++ b/doublespending.md @@ -325,4 +325,35 @@ B: [EthTaipei CTF 2023](https://github.com/dinngo/ETHTaipei-war-room/)(5) - Then, [solidity (version < 0.8.0)](https://github.com/consenlabs/account-abstraction/blob/3381bd75823c238a05c870fb11c61548c8fc7c9d/src/paymaster/OffChainPaymaster.sol#L2) has not applied underflow check as default. - Finally, we can withdraw twice using `onERC721Received` callback and make the balance underflow. +### 2024.09.20 + +B: [EthTaipei CTF 2023](https://github.com/dinngo/ETHTaipei-war-room/)(5) + +- WBC + - We should create contract `Ans` implmenting `IGame` to satisfy the requirements of `WBC` + - To pass `bodyCheck` + - Call `bodyCheck` inside `constructor` of `Ans` to meet the requirement [here](https://github.com/dinngo/ETHTaipei-war-room/blob/b5bdb72097172f50baa13b996be2422fd1b6786c/src/WBC/WBC.sol#L32) + - Use differnt salt to create `Ans` with different addresses to meet the requirement [here](https://github.com/dinngo/ETHTaipei-war-room/blob/b5bdb72097172f50baa13b996be2422fd1b6786c/src/WBC/WBC.sol#L33) + - To pass `ready` + - `judge()` of `Ans` should return `block.conbase`. + - To pass `_swing` + - To pass `_secondBase` + - `steal()` of `Ans` should return the `input` value by just copy + - To pass `_thirdBase` - We should find xxx that `this.decode(xxx) = "HitAndRun"`. + - xxx = 0\*(32-10)||9||HitAndRun + ``` + function decode(bytes32 data) external pure returns (string memory) { + assembly { + mstore(0x20, 0x20) + mstore(0x49, data) + // [0x20(offset), 0*9 || data[0:23](length), data[23:32] || 0*23(raw)] + // [0x20(offset), 0*9 || xxx.length(length), xxx.data || 0*23(raw)] + return(0x20, 0x60) + } + } + ``` + - To pass `_homeBase` + - We should find a way to distinguish the two sequential static call. - We can use `gasleft()` + - We can find a value `i` - `gasleft() % i == 0` in the first call - `gasleft() % i != 0` in the second call +