Otternaut Launch
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.