Skip to main content

Virtual Machine 1 picoCTF 2023 Solution

Reverse engineer a puzzle that requires understanding mechanical ratios to produce the correct answer for a remote server.

Published: April 26, 2023Updated: August 25, 2026

Description

A harder version of Virtual Machine 0. Instead of a simple two-gear system, this challenge hides a multi-stage bevel gear train inside a COLLADA (.dae) 3D model. Open the model in Blender, count the teeth on every gear in the train, compute the compound ratio, and submit the output rotation to a remote server to receive the flag.

Download and extract the challenge archive to get the .dae model file. Get the files from the challenge page on CyLab Security Academy (formerly play.picoctf.org).

Install Blender (free, from blender.org) to open the COLLADA model.

Connect to the remote server when you are ready to submit your answer.

bash
unzip vm1
bash
# Open the .dae file in Blender: File -> Import -> Collada (.dae)
bash
nc <HOST_FROM_INSTANCE> <PORT_FROM_INSTANCE>

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1Open the model in Blender and expose the gear train
    Observation
    The download is a COLLADA (.dae) model, so the puzzle lives in a mechanical assembly. Blender's COLLADA importer plus Edit Mode is the way to inspect it and count teeth.
    Import the .dae file into Blender. The scene contains a multi-stage bevel gear train surrounded by casing parts. Select and delete non-gear objects (the housing, axle shafts, decorative pieces) to reveal all gears clearly. You need to count the teeth on every gear in the chain from the red input axle to the blue output axle.
    bash
    # In Blender:
    bash
    # File -> Import -> Collada (.dae) -> select the model file
    bash
    # Click a housing object, press X -> Delete to remove it
    bash
    # Orbit the camera to inspect each gear stage
    bash
    # Count teeth by slowly rotating each gear in Edit Mode
    What didn't work first

    Tried: Trying to read the gear tooth counts directly from the COLLADA XML with a text editor or grep instead of opening the model in Blender.

    COLLADA stores raw vertex coordinates, not a tooth-count attribute. The file is thousands of floats with nothing marking where one tooth ends and the next begins. Edit Mode in Blender lets you orbit the rendered mesh and count peaks directly, which is the only practical route.

    Tried: Using FreeCAD or another 3D viewer instead of Blender to inspect the model.

    FreeCAD opens .dae files, but it often mishandles scenes with instanced objects and can collapse the whole gear train into one merged mesh, leaving no individual gears to identify. Blender's importer keeps the scene hierarchy, so you can isolate each object from the Outliner.

    Learn more

    COLLADA (.dae) is an XML-based 3D interchange format. Blender imports it via File -> Import -> Collada. The Virtual Machine 1 model is a Lego-style mechanical assembly with bevel gears at multiple stages, meaning gears whose axes are not parallel (bevel gears transfer rotation between shafts at an angle, like the gears in a car differential).

    The gear ratio for each meshing pair is simply the tooth count of the driven gear divided by the tooth count of the driving gear. A compound gear train multiplies the ratios of every stage together. If stage 1 gives 7:1, stage 2 gives 191:1, and stage 3 gives 7:1, the total ratio is 7 x 191 x 7 = 9359. That means one full turn of the input axle produces 9359 full turns of the output axle.

  2. Step 2Calculate the compound gear ratio
    Observation
    In Blender the model turns out to hold several bevel stages, not one pair. Multiply the stage ratios together to get the compound ratio before you talk to the server.
    Walk every gear mesh from input to output and record the driven/driving tooth ratio at each stage. Multiply all the stage ratios together. For this challenge the gear train works out to a compound ratio of 9359.
    python
    python3 - <<'PY'
    # Example of how to combine per-stage gear ratios:
    # Stage 1: driving gear has D1 teeth, driven gear has d1 teeth -> ratio = d1 / D1
    # Repeat for every stage, then multiply all ratios.
    stages = [
        (8, 56),   # stage 1: 56-tooth driven by 8-tooth -> 7x
        (1, 191),  # stage 2: 191-tooth driven by 1-tooth -> 191x  (adjust to match your count)
        (8, 56),   # stage 3: 56-tooth driven by 8-tooth -> 7x
    ]
    ratio = 1
    for driving, driven in stages:
        ratio *= driven / driving
    print(f"Compound ratio: {ratio}")   # should be 9359
    PY

    Expected output

    Compound ratio: 9359.0
    What didn't work first

    Tried: Dividing the driving tooth count by the driven tooth count instead of the other way around, producing a fractional ratio less than 1.

    The ratio is driven over driving, output over input. More teeth on the driven gear means the output turns slower; fewer teeth means it turns faster. Flip the fraction and instead of 9359 you compute roughly 0.000107, which the server rejects.

    Tried: Assuming the chain has only two gear stages because two gears are most visible after removing the housing.

    This model is a three-stage compound train, and one intermediate stage hides behind the main bevel pair if the housing is only partly removed. Miss it and you multiply two ratios instead of three, landing on the wrong count. Move every non-gear object to a hidden collection and look from several angles before committing.

    Learn more

    The ratio of 9359 comes from a three-stage bevel gear train. Each bevel gear pair works the same way as a spur gear pair for counting purposes: divide the driven tooth count by the driving tooth count. The challenge authors chose numbers whose product (9359) is large enough that you cannot guess it but small enough to count by hand or with Blender's vertex inspector.

    If your count differs from 9359, re-examine each stage. A common mistake is confusing which gear is driving and which is driven (the ratio inverts), or missing a stage hidden inside the housing. Move every non-gear object to a hidden collection in Blender so nothing obstructs your view.

  3. Step 3Connect to the server and answer the rotation question
    Observation
    The server asks how many times the output axle turns for a randomly chosen input count. Multiply whatever integer it gives you by the compound ratio of 9359.
    The remote server presents a large input rotation count and asks how many times the output axle will rotate. Multiply the input by the gear ratio (9359) and submit the result. The server validates the answer and prints the flag.
    bash
    nc <HOST_FROM_INSTANCE> <PORT_FROM_INSTANCE>
    bash
    # Server prompt example:
    bash
    #   How many times does the output rotate if the input rotates 22039 times?
    bash
    # Answer: 22039 * 9359 = 206263001
    What didn't work first

    Tried: Submitting the gear ratio itself (9359) as the answer instead of multiplying it by the server-provided input rotation count.

    The server asks for output rotations given a specific input count, not for the ratio itself, and it picks a fresh random input every session. Submit 9359 on its own and it is wrong. Read the integer out of the prompt and multiply.

    Tried: Hard-coding the example host and port (saturn.picoctf.net:<PORT_FROM_INSTANCE>) into the socket script instead of using the values from your own challenge instance.

    picoCTF spins up per-team instances, so a host and port copied from anywhere else belong to someone else's session and are probably already closed. Take them from your own challenge panel and paste them into the command.

    Learn more

    The server generates a random input value each session, so you will not see 22039 every time. The computation is always the same: output = input * 9359. The Python snippet below handles arbitrarily large numbers (picoCTF often uses big-integer inputs):

    import re
    import socket
    
    RATIO = 9359
    HOST, PORT = "saturn.picoctf.net", 51650  # replace with your instance values
    
    with socket.create_connection((HOST, PORT)) as s:
        data = s.recv(4096).decode()
        print(data)
        # Pull the input rotation count out of the prompt: it is the only integer,
        # and it is not the last token (the line ends in "times?").
        n = int(re.findall(r"\d+", data)[-1])
        answer = str(n * RATIO)
        s.sendall((answer + "\n").encode())
        print(s.recv(4096).decode())

    This challenge demonstrates that "reverse engineering" is not limited to binary analysis. Understanding the mechanism of a physical machine, whether real or simulated in a 3D model, is also reverse engineering. The gear train is the "program" and counting teeth is the "disassembly."

Interactive tools
  • File Magic IdentifierIdentify file types from magic numbers. Paste hex bytes or drop a file to detect PNG, JPEG, ZIP, PDF, ELF, PCAP, SQLite, and dozens of other formats.
  • Hex ViewerView text or raw hex bytes as a xxd-style hex dump with byte offset, hex columns, and ASCII sidebar. Highlights printable characters and null bytes.
  • Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.

Flag

Reveal flag

picoCTF{m0r3_g34r5_3g4d_...}

The compound gear ratio is 9359. Multiply the server-provided input rotation count by 9359 and submit the result to receive the flag.

Key takeaway

A compound gear train multiplies its stage ratios together, the same composition that makes key-derivation chains and multi-round ciphers stronger than any single step. Analyzing one correctly means tracing every link without dropping or reordering a stage. The same discipline applies to layered encodings, nested obfuscation, and chained redirects on the web.

Related reading

Useful tools for Reverse Engineering

Where to go next