- 11 mins read

+++ date = ‘2025-10-10T19:49:47-07:00’ draft = false title = ‘picoCTF2025: Virtual Machine 1’ +++

View solve on github

I thought it would be a good idea to try another picoCTF problem. I noticed none of the problems on last week’s picoCTF Mini were hard, so I picked Virtual Machine 1. This problem has 429 solves and a whopping 30% satisfication rate d The enemy has upgraded their mechanical analog computer. Start an instance to begin.We grabbed this design doc from enemy servers: Download. We know that the rotation of the red axle is the input and the rotation of th axle is output. Reverse engineer the me blueechanism and get past their checker program:thnc saturn.picoctf.net 60698.

CAD Model

First off,

1
2
3
zimengx@endeavour ~/S/p/P/virtualMachine1> unzip Virtual-Machine-1.zip
Archive:  Virtual-Machine-1.zip
  inflating: VirtualMachine1.dae

we get a COLLADA .dae file??? A 3D File?? I don’t have anything that opens COLLADA files (blender removed support a few versions ago), so I converted it into a STEP file using imagetostl.com

We get a sneak peak at the model, this will be fun…

It looks like OnShape has failed to translate the file (over 30 minutes) So I converted it into a .blend and opened it with blender

And then exported it as .glTF so I could import it into OnShape (I think the gear relations and revolute parameters might come in handy)

While it’s converting (hopefully), lets inspect the model first, it seems to be some Lego Technic gears, with some differentials(?)

Let’s take a look at the instance first:

1
2
3
zimengx@endeavour ~/S/p/P/virtualMachine1 [SIGINT]> nc saturn.picoctf.net 50299
If the input to the machine is 20698, what is the output?
Answer> ^C⏎

It looks like the machine is a calculator, 50299 might in radians or degrees or turns, or something like that. We have to solve for the rotation of the output blue axle with relation to the red input axle.

The OnShape finished importing:

Breaking down into Sections

We can start working on it in sections:

Lego Gears

Here is a list of Lego gear sizes: And a differential The differential is a Technic, Gear Differential 24-16 Tooth

Separating the gears, we see a total of 6 distinct gear ratios

“Differential Equations”, lol

The differential acts as an averaging mechanism for rotational velocities:

$$\omega_{diff} = \frac{\omega_1 + \omega_2}{2}$$

where $\omega_1$ and $\omega_2$ are the input rotational velocities. Thus, we need to split this section into two parts, by first calculating $A_{1,1}$ and $A_{1,2}$

Section I

We can derive both sections symbolically with python:

1
2
3
4
5
6
7
def A_1_1(A1):
	a0 = A1
	a1 = -a0*24/8
	a2 = a1
	a3 = a2*16/8
	a4 = -a3
	a5 = a4

But wait…how do we handle rotation on different axis?? Let’s define positive rotation as clockwise when viewed from directly above. But the question still remains, how do we define rotation in the horizontal axis? Lets arbitrarily define it as clockwise when viewed from the right when viewed from the back: Let’s also give the gears on different axis different names: With this configuration, if a pitch axial gear (with $\omega$ b) is on the left of a yaw axial gear (with $\omega$ a), then b=-a, and vice versa.

Thus,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def B_1_1(iA1):
    """
    Calculate first branch output for Section I
    Gear ratios: 24T:8T → 16T:8T → sign inversion
    """
    a0 = iA1
    a1 = -a0*24/8    # First gear reduction (24:8)
    a2 = a1
    a3 = -a2*16/8   # Second gear reduction (16:8)
    a4 = -a3        # Sign inversion
    a5 = a4
    b6 = -a5        # Final sign inversion
    return b6

def B_1_2(iA1):
    """
    Calculate second branch output for Section I
    Gear ratios: 24T:12T → 16T:8T → 16T:8T
    """
    a0 = iA1
    a1 = -a0*24/12  # First gear reduction (24:12)
    a2 = a1
    a3 = -a2*16/8   # Second gear reduction (16:8)
    a4 = a3
    a5 = -a4*16/8   # Third gear reduction (16:8)
    a6 = -a5
    a7 = a6
    b8 = a7
    return b8

def B1(b1_1, b1_2):
    """
    Differential averaging of both branches
    """
    return (b1_1 + b1_2)/2

Now, we can calculate $A_2$, first labeling the gears

Section I Summary: $$A_2 = -A_1 \times \frac{24}{8} \times \frac{16}{8} \times \frac{1}{2} = -A_1 \times 3$$

1
2
3
4
5
def A2(B1):
    b0 = -B1
    b1 = b0
    a0 = -b1
    return a0

Section II

Let’s annotate in a similar fashion Section #2 This looks daunting, but it’s not that bad. We have 6 distinct sections. Let’s number them: We can start coding now! We now have $B_{2,1}$ and $B_{2,2}$:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
def A_2_1(iA2):
    """
    First stage of Section II
    Gear ratio: 24T:12T
    """
    a0 = iA2
    a1 = -a0*24/12  # Gear reduction (24:12)
    return a1

def B_2_1(a2_1):
    """
    First branch of Section II
    Gear ratios: 24T:8T → 24T:8T → sign inversions
    """
    a0 = a2_1
    a1 = -a0*24/8   # First gear reduction (24:8)
    a2 = a1
    a3 = -a2*24/8   # Second gear reduction (24:8)
    a4 = -a3        # Sign inversion
    a5 = a4
    b0 = -a5        # Final sign inversion
    return b0

def B_2_2(a2_1):
    """
    Second branch of Section II
    Gear ratios: 24T:12T → 40T:8T
    """
    a0 = a2_1
    a1 = -a0*24/12  # First gear reduction (24:12)
    a2 = a1
    a3 = -a2*40/8   # Second gear reduction (40:8)
    a4 = a3
    b0 = a4
    return b0

Then, we can derive $B_{3,1}$: Ooops! It looks like this section (orange) is NOT a 36t, but 3x12. Now we can calculate $B_{3,1}$:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def B3_1(iB2):
    """
    First branch of Section III
    Gear ratios: 24T:12T → sign inversions → 40T:8T
    """
    b0 = -iB2*24/12  # First gear reduction (24:12)
    b1 = -b0         # Sign inversion
    b2 = -b1         # Sign inversion
    b3 = b2
    b4 = -b3*40/8    # Second gear reduction (40:8)
    return b4

Moving on, we calculate $B_{3,2}$ using $A_2$

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
def B3_2(iA2):
    """
    Second branch of Section III
    Gear ratios: 24T:12T → 16T:8T → 16T:8T → 16T:8T → 16T:8T → 24T:8T
    """
    a0 = iA2
    a1 = -a0*24/12   # First gear reduction (24:12)
    a2 = a1
    a3 = -a2*16/8    # Second gear reduction (16:8)
    a4 = a3
    a5 = -a4*16/8    # Third gear reduction (16:8)
    a6 = a5
    a7 = -a6*16/8    # Fourth gear reduction (16:8)
    a8 = a7
    a9 = -a8*16/8    # Fifth gear reduction (16:8)
    a10 = a9
    a11 = -a10*16/8  # Sixth gear reduction (16:8)
    a12 = a11
    a13 = -a12*24/8  # Seventh gear reduction (24:8)
    a14 = a13
    b0 = a14
    return b0

The approach is repetitive, but my brain is overwhelmed already, so I’ll do things the brute-force way.

With $B_{3,1}$ and $B_{3,2}$, we can calculate $B_3$ using the differential equation (1):

1
2
def B3(B3_1, B3_2):
    return (B3_1 + B3_2)/2

This allows us to calculate $A_3$ relatively easily:

Section II Summary: $$A_3 = -B_3 = -\frac{B_{3,1} + B_{3,2}}{2}$$

1
2
3
4
5
6
def A3(B3):
    b0 = B3
    b1 = -b0
    b2 = b1
    a0 = -b2
    return a0

Section III

We notice a heuristic here, We notice $A_1:A_2 = A_3:A_4$, so we can use the same transformation function for both sections.

Section III Summary: $$A_4 = A_2(A_3) = -A_3 \times 3$$

Thus, we just plug A3 back into our A1->A2 function, and we solve the entire system with:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
```python
def A1_A2(a1):
    """
    Transform A1 → A2 through Section I
    """
    return A2(B1(B_1_1(a1), B_1_2(a1)))

def A2_A3(a2):
    """
    Transform A2 → A3 through Section II
    """
    a2_1 = A_2_1(a2)
    b2 = B2(B_2_1(a2_1), B_2_2(a2_1))
    b3_1 = B3_1(b2)
    b3_2 = B3_2(a2)
    b3 = B3(b3_1, b3_2)
    return A3(b3)

def A3_A4(a3):
    """
    Transform A3 → A4 through Section III (same as Section I)
    """
    return A1_A2(a3)

def solveSystem(a1):
    """
    Complete system solution: A1 → A2 → A3 → A4
    """
    return A3_A4(A2_A3(A1_A2(a1)))

print(solveSystem(-17198))

Complete System Solution

The complete system can be represented as a series of transformations:

$$A_2 = f_1(A_1) = -A_1 \times 3$$ $$A_3 = f_2(A_2) = -\frac{B_{3,1}(A_2) + B_{3,2}(A_2)}{2}$$ $$A_4 = f_3(A_3) = -A_3 \times 3$$

Final System Equation: $$A_4 = f_3(f_2(f_1(A_1)))$$

Where the overall transformation is: $$A_4 = A_1 \times 9 \times \frac{B_{3,1}(A_1 \times 3) + B_{3,2}(A_1 \times 3)}{2}$$

Plugging it in

Lets see if it works!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
zimengx@endeavour ~/S/p/P/virtualMachine1> python3 solveSystem.py
Traceback (most recent call last):
  File "/home/zimengx/SchoolDrive/picoCTF/Practice/virtualMachine1/solveSystem.py", line 113, in <module>
    print(solveSystem(1))
          ~~~~~~~~~~~^^^
  File "/home/zimengx/SchoolDrive/picoCTF/Practice/virtualMachine1/solveSystem.py", line 111, in solveSystem
    return A3_A4(A2_A3(A1_A2(A1)))
                 ~~~~~^^^^^^^^^^^
  File "/home/zimengx/SchoolDrive/picoCTF/Practice/virtualMachine1/solveSystem.py", line 100, in A2_A3
    A_2_1 = A_2_1(A2)
            ^^^^^
UnboundLocalError: cannot access local variable 'A_2_1' where it is not associated with a value

Nope, looks like my use of the same character as both the variable and function name is causing issues.

1
2
zimengx@endeavour ~/S/p/P/virtualMachine1> python3 solveSystem.py
-9359.0

Looks like it works! Let’s test it on our input.

1
2
3
4
5
6
zimengx@endeavour ~/S/p/P/virtualMachine1> nc saturn.picoctf.net 51644

If the input to the machine is 13536, what is the output?
Answer> -126683424
That's not correct.
You have to wait 360.0 seconds before trying again.

Sighhh…this mean we only have one more attempt, let’s invert the sign of our input to see if we are using different coordinates.

Since I didn’t want to wait 360 seconds, I launched a new instance and inverted the number

1
print(solveSystem(-17198))
1
2
3
4
5
6
zimengx@endeavour ~/S/p/P/virtualMachine1 [SIGINT]> nc saturn.picoctf.net 53014
If the input to the machine is 17198, what is the output?
Answer> 160956082
160956082
That's correct!
picoCTF{xxxxxxxxxxxxxxxxxxxxxxxx}

YAYYYYYYYYYY. Total time took: 2hr 10min including time it took to write this, so I guess it would have taken roughly 2/3-1/2 the time without.

Complete System Analysis

Mathematical Summary

Key Equations:

  1. Differential Averaging: $$\omega_{diff} = \frac{\omega_1 + \omega_2}{2} $$

  2. Section I Transformation (A₁ → A₂): $$A_2 = -A_1 \times \frac{24}{8} \times \frac{16}{8} \times \frac{1}{2} = -A_1 \times 3 $$

  3. Section II Transformation (A₂ → A₃): $$A_3 = -\frac{B_{3,1}(A_2) + B_{3,2}(A_2)}{2} $$

  4. Section III Transformation (A₃ → A₄): $$ A_4 = -A_3 \times 3 $$

Complete System Solution: $$A_4 = A_1 \times 9 \times \frac{B_{3,1}(A_1 \times 3) + B_{3,2}(A_1 \times 3)}{2} $$

Full Code:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
def B_1_1(iA1):
    a0 = iA1
    a1 = -a0*24/8
    a2 = a1
    a3 = -a2*16/8
    a4 = -a3
    a5 = a4
    b6 = -a5
    return b6

def B_1_2(iA1):
    a0 = iA1
    a1 = -a0*24/12
    a2 = a1
    a3 = -a2*16/8
    a4 = a3
    a5 = -a4*16/8
    a6 = -a5
    a7 = a6
    b8 = a7
    return b8

def B1(b1_1, b1_2):
    return (b1_1 + b1_2)/2

def A2(iB1):
    b0 = -iB1
    b1 = b0
    a0 = -b1
    return a0

def A_2_1(iA2):
    a0 = iA2
    a1 = -a0*24/12
    return a1

def B_2_1(a2_1):
    a0 = a2_1
    a1 = -a0*24/8
    a2 = a1
    a3 = -a2*24/8
    a4 = -a3
    a5 = a4
    b0 = -a5
    return b0

def B_2_2(a2_1):
    a0 = a2_1
    a1 = -a0*24/12
    a2 = a1
    a3 = -a2*40/8
    a4 = a3
    b0 = a4
    return b0

def B2(b2_1, b2_2):
    return (b2_1 + b2_2)/2

def B3_1(iB2):
    b0 = -iB2*24/12
    b1 = -b0
    b2 = -b1
    b3 = b2
    b4 = -b3*40/8
    return b4

def B3_2(iA2):
    a0 = iA2
    a1 = -a0*24/12
    a2 = a1
    a3 = -a2*16/8
    a4 = a3
    a5 = -a4*16/8
    a6 = a5
    a7 = -a6*16/8
    a8 = a7
    a9 = -a8*16/8
    a10 = a9
    a11 = -a10*16/8
    a12 = a11
    a13 = -a12*24/8
    a14 = a13
    b0 = a14
    return b0

def B3(b3_1, b3_2):
    return (b3_1 + b3_2)/2

def A3(iB3):
    b0 = iB3
    b1 = -b0
    b2 = b1
    a0 = -b2
    return a0

def A1_A2(a1):
    return A2(B1(B_1_1(a1), B_1_2(a1)))

def A2_A3(a2):
    a2_1 = A_2_1(a2)
    b2 = B2(B_2_1(a2_1), B_2_2(a2_1))
    b3_1 = B3_1(b2)
    b3_2 = B3_2(a2)
    b3 = B3(b3_1, b3_2)
    return A3(b3)

def A3_A4(a3):
    return A1_A2(a3)

def solveSystem(a1):
    return A3_A4(A2_A3(A1_A2(a1)))

Other approaches

Let’s see how other people approached this problem. Searching for “picoCTF 2025 Virtual Machine 1”, I don’t see any solution besides this one, which did not provide a solution. Quite disappointing… I wanted to see if there was a faster way.

Looking at it, I can’t think of a faster way or a shortcut to solving this, besides modeling it out in OnShape and doing gear relations for each gear, this would help us solve A1->2 A2->3 relations faster? I don’t know how much faster it would be or how accurate it would be. It would also be fun to build this out, but I would like to think that would take more time.