Skip to content

Commit d5d65ae

Browse files
committed
✨ add challenges and solutions
1 parent bf20b99 commit d5d65ae

File tree

4,586 files changed

+894777
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

4,586 files changed

+894777
-0
lines changed

alienspaceship/.challengeignore

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
challenge/project/broadcast/*
2+
challenge/project/cache/*
3+
challenge/project/out/*
4+
5+
6+
challenge/solve.py
7+
challenge/project/script/Solve.s.sol
8+
challenge/project/src/AlienSpaceship.sol

alienspaceship/README.md

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
## Solution
2+
3+
Alien Spaceship is hard EVM challenge which provides teams with the bytecode of a smart contract. Per the description, teams need to take over control of their spaceship and successfully `abortMission`.
4+
5+
First, you can get the bytecode of the contract by using the eth_getCode RPC Method or simply copying the bytecode provided in the [deploy script](challenge/project/script/Deploy.s.sol). With this, you can use a decompiler. However, only a few decompilers work for this bytecode. [Here](https://app.dedaub.com/decompile?md5=60cad9a4c1fe82b05f6591dcceb5ecdb) is an example of using the Dedaub decompiler.
6+
7+
With the decompiled contract, you can trace the steps needed to call the `abortMission` function. For easy reference, we will use the [AlienSpaceship](challenge/project/src/AlienSpaceship.sol) source contract.
8+
9+
As we can see from the `abortMission` function, the criteria to call this function is:
10+
11+
- `distance()` must be less than 1_000_000 * 10**18
12+
- `payloadMass` must be less than 1_000 * 10**18
13+
- `numArea51Visits` must be greater than 0
14+
- `msg.sender` must have code (i.e. be a smart contract not calling from constructor)
15+
16+
The `distance()` function calls the `_calculateDistance` function passing three points stored in the contract: x, y and z. The calculation function employs the L1 norm (also known as the Manhattan distance or taxicab geometry) -- for each coordinate, the function computes its absolute value using the `_abs` helper function. The purpose of using absolute values is to ensure distance is always considered as a positive quantity, regardless of the direction. Then, the distance is calculated as the sum of the absolute values of the x, y, and z coordinates, a way of measuring distance in a grid-based path.
17+
18+
To pass the first check, the return of the calculation must be less than 1_000_000 * 10**18. If we look for modifications to the position points, we notice that the `visitArea51` function sets the position points to high values exceeding the criteria. However, the `jumpThroughWormhole` function let's us choose the points. Therefore, we know that the `visitArea51` function must be called before the `jumpThroughWormhole` function.
19+
20+
Both mentioned functions require the `msg.sender` to have the `CAPTAIN` role. In order to get this, we can look at the `applyForPromotion` function which allows us to pass a role that we want to get promoted to. However, the first check requires us to already have the `PHYSICIST` role before `CAPTAIN`. To get the former role, we can use the `applyForJob` function but, similarly, this function requires us to have the `ENGINEER` role before. We can simply get the latter role by calling teh `applyForJob` function in the start.
21+
22+
To get the `PHYSICIST` role, the AlienSpaceship contract must hold the `ENGINEER` role as well. If we check the `runExperiment` function, we notice that there's a way we can make the contract call itself which can be used to call the `applyForJob` function like we did.
23+
24+
Once the contract has the `ENGINEER` role, we can call the `applyForJob` function and claim the `PHYSICIST` role. Going back to the `applyForPromotion` function, we have another check to pass: 12 seconds must pass between our promotion to `PHYSICIST` and our call to `applyForPromotion`. Thefore, we need to separate the solve script into two transactions to pass this check.
25+
26+
The next check in the function verifies that we have enabled the `enabledTheWormholes` variable. To do so, we need to call the `enableWormholes` function as soon as we obtain the `PHYSICIST` role and before calling the `applyForPromotion` function. The `enableWormholes` function needs to be called by an EOA (Externally Owned Account) or under specific conditions that relate to the presence of contract code (i.e. calling in constructor). Calling in constructor also makes it easier for us to split the solve into two parts to bypass the other check mentioned before.
27+
28+
At this point, we should have the `CAPTAIN` role allowing us to call the `visitArea51` and `jumpThroughWormhole` functions. The `visitArea51` function takes a `secret` parameter, which must be 51 - the address of the caller in uint160 format (allowing this calculation to overflow). This function will increment our number of visits to Area 51, passing one of the remaining cheks in the `abortMission` function. Aditionally, it will increase our position numbers, so we need to call the `jumpThroughWormhole` function to set them to the desired values that pass the check.
29+
30+
This function requires one of the last checks to pass: that the payload mass of the ship is less than 1_000 * 10 ** 18. To do so, we need to call the `dumpPayload` function some time before passing the right amount. Now we can call the `jumpThroughWormhole` function passing values that pass the distance check in the `abortMission` function.
31+
32+
Now, we can call the `abortMission` function and get the flag!

alienspaceship/challenge.yaml

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
apiVersion: kctf.dev/v1
2+
kind: Challenge
3+
metadata:
4+
name: alienspaceship
5+
annotations:
6+
type: PWN
7+
name: Alien Spaceship
8+
description: "You have hacked into an alien spaceship and stolen the bytecode that controls their spaceship. They are on a mission to attack your home planet. Luckily for you their spaceship runs on the EVM. Take over control of their spaceship and successfully `abortMission`."
9+
author: "steventhornton"
10+
tags: "pwn"
11+
flag: "OZCTF{Th1s_miSs10n_d3S3rv3s_1tS_0Wn_M0v13}"
12+
spec:
13+
deployed: true
14+
powDifficultySeconds: 0
15+
network:
16+
public: true
17+
healthcheck:
18+
enabled: false

alienspaceship/challenge/Dockerfile

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
FROM ghcr.io/foundry-rs/foundry:latest AS foundry
2+
3+
COPY project /project
4+
5+
# artifacts must be the same path
6+
RUN true && \
7+
cd /project && \
8+
forge build --out /artifacts/out --cache-path /artifacts/cache && \
9+
true
10+
11+
FROM ghcr.io/openzeppelin/ctf-infra:latest as chroot
12+
13+
# ideally in the future, we can skip the chowns, but for now Forge wants to write the cache and broadcast artifacts
14+
15+
USER 1000
16+
WORKDIR /home/user
17+
18+
COPY --chown=user:user . /home/user/challenge/
19+
20+
COPY --from=foundry --chown=user:user /artifacts /artifacts
21+
22+
FROM gcr.io/paradigmxyz/ctf/kctf-challenge:latest
23+
24+
VOLUME [ "/chroot", "/tmp" ]
25+
26+
COPY --from=chroot / /chroot
27+
28+
# nsjail help
29+
RUN touch /chroot/bin/kctf_restore_env && touch /chroot/environ
30+
31+
CMD kctf_setup && \
32+
kctf_persist_env && \
33+
kctf_drop_privs socat TCP-LISTEN:1337,reuseaddr,fork EXEC:"nsjail --config /nsjail.cfg -- /bin/kctf_restore_env /usr/local/bin/python3 -u challenge/challenge.py"

alienspaceship/challenge/challenge.py

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
from typing import Dict
2+
3+
from ctf_launchers.pwn_launcher import PwnChallengeLauncher
4+
from ctf_server.types import LaunchAnvilInstanceArgs
5+
6+
BLOCK_TIME = 12
7+
8+
class Challenge(PwnChallengeLauncher):
9+
def get_anvil_instances(self) -> Dict[str, LaunchAnvilInstanceArgs]:
10+
return {
11+
"main": self.get_anvil_instance(
12+
block_time=BLOCK_TIME,
13+
),
14+
}
15+
16+
Challenge().run()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: paradigm-ctf-challenge
2+
services:
3+
launcher:
4+
container_name: challenge
5+
image: challenge
6+
build:
7+
context: .
8+
target: chroot
9+
command: socat TCP-LISTEN:1337,reuseaddr,fork exec:"python3 -u challenge/challenge.py"
10+
expose:
11+
- 1337
12+
ports:
13+
- "1337:1337"
14+
networks:
15+
- ctf_network
16+
networks:
17+
ctf_network:
18+
name: paradigmctf
19+
external: true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
broadcast/
2+
cache/
3+
out/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[profile.default]
2+
src = 'src'
3+
libs = ['lib']
4+
fs_permissions = [{ access = 'read-write', path = '/'}]
5+
6+
remappings = [
7+
"forge-std=lib/forge-std/src",
8+
"forge-ctf=lib/forge-ctf/src",
9+
]
10+
11+
# See more config options https://github.com/foundry-rs/foundry/tree/master/config
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity >=0.6.2 <0.9.0;
3+
4+
import "forge-std/Script.sol";
5+
6+
abstract contract CTFDeployment is Script {
7+
function run() external {
8+
address player = getAddress(0);
9+
address system = getAddress(1);
10+
11+
address challenge = deploy(system, player);
12+
13+
vm.writeFile(vm.envOr("OUTPUT_FILE", string("/tmp/deploy.txt")), vm.toString(challenge));
14+
}
15+
16+
function deploy(address system, address player) virtual internal returns (address);
17+
18+
function getAdditionalAddress(uint32 index) internal returns (address) {
19+
return getAddress(index + 2);
20+
}
21+
22+
function getPrivateKey(uint32 index) private returns (uint) {
23+
string memory mnemonic = vm.envOr("MNEMONIC", string("test test test test test test test test test test test junk"));
24+
return vm.deriveKey(mnemonic, index);
25+
}
26+
27+
function getAddress(uint32 index) private returns (address) {
28+
return vm.addr(getPrivateKey(index));
29+
}
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity >=0.6.2 <0.9.0;
3+
4+
import "forge-std/Script.sol";
5+
6+
abstract contract CTFSolver is Script {
7+
function run() external {
8+
uint256 playerPrivateKey = vm.envOr("PLAYER", uint256(0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80));
9+
address challenge = vm.envAddress("CHALLENGE");
10+
11+
vm.startBroadcast(playerPrivateKey);
12+
13+
solve(challenge, vm.addr(playerPrivateKey));
14+
15+
vm.stopBroadcast();
16+
}
17+
18+
function solve(address challenge, address player) virtual internal;
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
name: CI
2+
3+
on:
4+
workflow_dispatch:
5+
pull_request:
6+
push:
7+
branches:
8+
- master
9+
10+
jobs:
11+
build:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v3
15+
16+
- name: Install Foundry
17+
uses: onbjerg/foundry-toolchain@v1
18+
with:
19+
version: nightly
20+
21+
- name: Print forge version
22+
run: forge --version
23+
24+
# Backwards compatibility checks:
25+
# - the oldest and newest version of each supported minor version
26+
# - versions with specific issues
27+
- name: Check compatibility with latest
28+
if: always()
29+
run: |
30+
output=$(forge build --skip test)
31+
32+
if echo "$output" | grep -q "Warning"; then
33+
echo "$output"
34+
exit 1
35+
fi
36+
37+
- name: Check compatibility with 0.8.0
38+
if: always()
39+
run: |
40+
output=$(forge build --skip test --use solc:0.8.0)
41+
42+
if echo "$output" | grep -q "Warning"; then
43+
echo "$output"
44+
exit 1
45+
fi
46+
47+
- name: Check compatibility with 0.7.6
48+
if: always()
49+
run: |
50+
output=$(forge build --skip test --use solc:0.7.6)
51+
52+
if echo "$output" | grep -q "Warning"; then
53+
echo "$output"
54+
exit 1
55+
fi
56+
57+
- name: Check compatibility with 0.7.0
58+
if: always()
59+
run: |
60+
output=$(forge build --skip test --use solc:0.7.0)
61+
62+
if echo "$output" | grep -q "Warning"; then
63+
echo "$output"
64+
exit 1
65+
fi
66+
67+
- name: Check compatibility with 0.6.12
68+
if: always()
69+
run: |
70+
output=$(forge build --skip test --use solc:0.6.12)
71+
72+
if echo "$output" | grep -q "Warning"; then
73+
echo "$output"
74+
exit 1
75+
fi
76+
77+
- name: Check compatibility with 0.6.2
78+
if: always()
79+
run: |
80+
output=$(forge build --skip test --use solc:0.6.2)
81+
82+
if echo "$output" | grep -q "Warning"; then
83+
echo "$output"
84+
exit 1
85+
fi
86+
87+
# via-ir compilation time checks.
88+
- name: Measure compilation time of Test with 0.8.17 --via-ir
89+
if: always()
90+
run: forge build --skip test --contracts test/compilation/CompilationTest.sol --use solc:0.8.17 --via-ir
91+
92+
- name: Measure compilation time of TestBase with 0.8.17 --via-ir
93+
if: always()
94+
run: forge build --skip test --contracts test/compilation/CompilationTestBase.sol --use solc:0.8.17 --via-ir
95+
96+
- name: Measure compilation time of Script with 0.8.17 --via-ir
97+
if: always()
98+
run: forge build --skip test --contracts test/compilation/CompilationScript.sol --use solc:0.8.17 --via-ir
99+
100+
- name: Measure compilation time of ScriptBase with 0.8.17 --via-ir
101+
if: always()
102+
run: forge build --skip test --contracts test/compilation/CompilationScriptBase.sol --use solc:0.8.17 --via-ir
103+
104+
test:
105+
runs-on: ubuntu-latest
106+
steps:
107+
- uses: actions/checkout@v3
108+
109+
- name: Install Foundry
110+
uses: onbjerg/foundry-toolchain@v1
111+
with:
112+
version: nightly
113+
114+
- name: Print forge version
115+
run: forge --version
116+
117+
- name: Run tests
118+
run: forge test -vvv
119+
120+
fmt:
121+
runs-on: ubuntu-latest
122+
steps:
123+
- uses: actions/checkout@v3
124+
125+
- name: Install Foundry
126+
uses: onbjerg/foundry-toolchain@v1
127+
with:
128+
version: nightly
129+
130+
- name: Print forge version
131+
run: forge --version
132+
133+
- name: Check formatting
134+
run: forge fmt --check
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Sync Release Branch
2+
3+
on:
4+
release:
5+
types:
6+
- created
7+
8+
jobs:
9+
sync-release-branch:
10+
runs-on: ubuntu-latest
11+
if: startsWith(github.event.release.tag_name, 'v1')
12+
steps:
13+
- name: Check out the repo
14+
uses: actions/checkout@v3
15+
with:
16+
fetch-depth: 0
17+
ref: v1
18+
19+
- name: Configure Git
20+
run: |
21+
git config user.name github-actions[bot]
22+
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
23+
24+
- name: Sync Release Branch
25+
run: |
26+
git fetch --tags
27+
git checkout v1
28+
git reset --hard ${GITHUB_REF}
29+
git push --force
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
cache/
2+
out/
3+
.vscode
4+
.idea
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "lib/ds-test"]
2+
path = lib/ds-test
3+
url = https://github.com/dapphub/ds-test

0 commit comments

Comments
 (0)