Introduction

Challenge Description:

Stranded on the frozen wastelands of Cryon-7, the Otternaut awakens in a half-buried, decades-old emergency capsule. Life support is failing, communications are dead, and the orbiting relay has gone silent. The only hope lies in restoring the capsule’s core systems. But the capsule was never meant to launch solo — it was built to be assembled piece by piece in a controlled lab, not reconstructed by paw in the wreckage of a crash site. Armed with salvaged tools, jury-rigged firmware, and sheer determination, the Otternaut begins to assemble the launch capsule manually, installing exo-frame parts, calibrating avionics, and overriding water-efficiency protocols. If every component fits — and every test passes — the capsule might just ignite. But the odds are thin. And Cryon-7 is getting colder.

Solution

The server exposes builder helpers that mint the exact resources the assemble_launch_capsule entrypoint expects. The only thing we need to understand is ordering the objects to match the client’s hard-coded parameter list, then calling the entrypoint with the required values: OS version 42 and safety rating >= 9.

Server

 1module challenge::otternaut_launch {
 2    public struct LaunchCapsule has key { id: UID, status: CapsuleStatus, flight_software_version: u8, safety_rating: u8 }
 3    public struct OtternautLab has key { id: UID }
 4    public struct LaunchInspectionLab has key { id: UID, required_safety: u8 }
 5    public struct MicroWrench has key { id: UID }
 6    public struct AvionicsCalibrator has key { id: UID }
 7    public struct HullFrame has key { id: UID }
 8    public struct Boosters { thrust_rating: u8 }
 9    public struct OtterFlightOS { version: u8 }
10
11    const REQUIRED_OS_VERSION: u8 = 42;
12    const REQUIRED_SAFETY_RATING: u8 = 9;
13    const INVALID_OS_VERSION: u64 = 4201;
14    const INSUFFICIENT_SAFETY: u64 = 4202;
15
16    public fun prepare_tools(_lab: &OtternautLab, user: address, ctx: &mut TxContext) {
17        transfer::transfer(MicroWrench { id: object::new(ctx) }, user);
18        transfer::transfer(AvionicsCalibrator { id: object::new(ctx) }, user);
19        transfer::transfer(HullFrame { id: object::new(ctx) }, user);
20    }
21
22    public fun build_boosters(_lab: &OtternautLab, thrust_rating: u8): Boosters {
23        Boosters { thrust_rating }
24    }
25    public fun generate_flight_os(_lab: &OtternautLab, version: u8): OtterFlightOS {
26        OtterFlightOS { version }
27    }
28
29    public fun assemble_launch_capsule(
30        capsule: &mut LaunchCapsule,
31        _lab: &OtternautLab,
32        lab: &LaunchInspectionLab,
33        wrench: MicroWrench,
34        calibrator: AvionicsCalibrator,
35        frame: HullFrame,
36        boosters: Boosters,
37        os: OtterFlightOS,
38    ) {
39        let OtterFlightOS { version } = os;
40        assert!(version == REQUIRED_OS_VERSION, INVALID_OS_VERSION);
41        inspect_safety(lab, capsule, boosters);
42        capsule.flight_software_version = version;
43        capsule.status = CapsuleStatus::READY_FOR_LAUNCH;
44    }
45
46    fun inspect_safety(lab: &LaunchInspectionLab, capsule: &mut LaunchCapsule, boosters: Boosters) {
47        let Boosters { thrust_rating } = boosters;
48        assert!(thrust_rating >= lab.required_safety, INSUFFICIENT_SAFETY);
49        capsule.safety_rating = thrust_rating;
50    }
51}

Client

PARAMS_LIST is the client’s hard-coded list of on-chain object “slots” that tells the server which objects to pass into your solution::solve function, in the exact order your Move script expects.

You retrieve these coordinate pairs directly from the server’s object dump, which it sends immediately after calling prepare_tools. That dump lists every live object as:

1object(1,0) → LaunchCapsule
2object(1,1) → LaunchInspectionLab
3object(1,2) → OtternautLab
4object(3,0) → AvionicsCalibrator
5object(3,1) → HullFrame
6object(3,2) → MicroWrench
1/// the list of parameters that will be passed to the `solution::solve` function
2const PARAMS_LIST: &[(u8, u8)] = &[
3    (1, 0), // LaunchCapsule
4    (1, 2), // OtternautLab
5    (1, 1), // LaunchInspectionLab
6    (3, 2), // MicroWrench
7    (3, 0), // AvionicsCalibrator
8    (3, 1), // HullFrame
9];

Once I match each (x,y) to its resource and order them to fit solution::solve, the framework hands my entrypoint the right objects, the checks pass, and we obtain the flag.

 1module solution::solution {
 2    use challenge::otternaut_launch::{
 3        // types
 4        LaunchCapsule,
 5        OtternautLab,
 6        LaunchInspectionLab,
 7        MicroWrench,
 8        AvionicsCalibrator,
 9        HullFrame,
10        // entry‐points
11        assemble_launch_capsule,
12        build_boosters,
13        generate_flight_os,
14    };
15
16    public fun solve(
17        capsule: &mut LaunchCapsule,
18        otter_lab: &OtternautLab,
19        inspect_lab: &LaunchInspectionLab,
20        wrench: MicroWrench,
21        calibrator: AvionicsCalibrator,
22        frame: HullFrame,
23    ) {
24        let boosters = build_boosters(otter_lab, 9);
25        let os = generate_flight_os(otter_lab, 42);
26        assemble_launch_capsule(
27            capsule,
28            otter_lab,
29            inspect_lab,
30            wrench,
31            calibrator,
32            frame,
33            boosters,
34            os,
35        );
36    }
37}
 1ctf ❯ docker run --rm -it -e HOST=launch.nc.jctf.pro -e PORT=31337 otternautlaunch:latest
 2+ cd framework-solve/solve
 3+ sui move build
 4Warning: unknown field name found. Expected one of [], but found 'version'
 5FETCHING GIT DEPENDENCY https://github.com/MystenLabs/sui.git
 6Warning: unknown field name found. Expected one of [], but found 'version'
 7INCLUDING DEPENDENCY challenge
 8INCLUDING DEPENDENCY Sui
 9INCLUDING DEPENDENCY MoveStdlib
10BUILDING solution
11Warning: unknown field name found. Expected one of [], but found 'version'
12+ cd ..
13+ cargo r --release
14   Compiling solve-framework v0.1.0 (/work/framework-solve)
15    Finished release [optimized] target(s) in 0.49s
16     Running `target/release/solve-framework`
17  - Connected!
18  - Loaded solution!
19  - Sent solution!
20  - Sent parameters list!
21  - Connection Output: '[SERVER] Challenge modules published at: 1c9816bb99b3e87794429f479a1c5fc02721b870168950d051ab641c3248e096'
22  - Connection Output: '[SERVER] Solution published at e9fda6e4de4d625e2b85ec349c889b1bb74a71b03ca5de5b40c2b64132a8f0af1c3248e096'
23  - Connection Output: '[SERVER] Congrats, flag: justCTF{<REDACTED>}'
24  - Terminated

Attachments

The full code can be found at solve.py.