rubix-cube
Category
Reverse Engineering
Points
328
Tags
So we got a rubixcube.py
and cube_scrambled.txt
rubixcube.py
:
import copy
import random
import numpy as np
class RubiksCube:
def __init__(self):
self.faces = {
'U': [['⬜'] * 3 for _ in range(3)],
'L': [['🟧'] * 3 for _ in range(3)],
'F': [['🟩'] * 3 for _ in range(3)],
'R': [['🟥'] * 3 for _ in range(3)],
'B': [['🟦'] * 3 for _ in range(3)],
'D': [['🟨'] * 3 for _ in range(3)]
}
def _rotate_face_clockwise(self, face_name):
face = self.faces[face_name]
new_face = [[None] * 3 for _ in range(3)]
for i in range(3):
for j in range(3):
new_face[i][j] = face[2-j][i]
self.faces[face_name] = new_face
def _rotate_face_counter_clockwise(self, face_name):
face = self.faces[face_name]
new_face = [[None] * 3 for _ in range(3)]
for i in range(3):
for j in range(3):
new_face[i][j] = face[j][2-i]
self.faces[face_name] = new_face
def display(self):
for i in range(3):
print(" " + " ".join(self.faces['U'][i]))
for i in range(3):
print(" ".join(self.faces['L'][i]) + " " +
" ".join(self.faces['F'][i]) + " " +
" ".join(self.faces['R'][i]) + " " +
" ".join(self.faces['B'][i]))
for i in range(3):
print(" " + " ".join(self.faces['D'][i]))
print("-" * 30)
def get_flat_cube_encoded(self):
return "".join([chr(ord(i) % 94 + 33) for i in str(list(np.array(self.faces).flatten())) if ord(i)>256])
def get_cube(self):
return self.faces
def U(self):
self._rotate_face_clockwise('U')
temp_row = copy.deepcopy(self.faces['F'][0])
self.faces['F'][0] = self.faces['R'][0]
self.faces['R'][0] = self.faces['B'][0]
self.faces['B'][0] = self.faces['L'][0]
self.faces['L'][0] = temp_row
def L(self):
self._rotate_face_clockwise('L')
temp_col = [self.faces['U'][i][0] for i in range(3)]
for i in range(3): self.faces['U'][i][0] = self.faces['B'][2-i][2]
for i in range(3): self.faces['B'][2-i][2] = self.faces['D'][i][0]
for i in range(3): self.faces['D'][i][0] = self.faces['F'][i][0]
for i in range(3): self.faces['F'][i][0] = temp_col[i]
def F(self):
self._rotate_face_clockwise('F')
temp_strip = copy.deepcopy(self.faces['U'][2])
for i in range(3): self.faces['U'][2][i] = self.faces['L'][2-i][2]
for i in range(3): self.faces['L'][i][2] = self.faces['D'][0][i]
for i in range(3): self.faces['D'][0][2-i] = self.faces['R'][i][0]
for i in range(3): self.faces['R'][i][0] = temp_strip[i]
def D_prime(self):
self._rotate_face_counter_clockwise('D')
temp_row = copy.deepcopy(self.faces['F'][2])
self.faces['F'][2] = self.faces['R'][2]
self.faces['R'][2] = self.faces['B'][2]
self.faces['B'][2] = self.faces['L'][2]
self.faces['L'][2] = temp_row
def R_prime(self):
self._rotate_face_counter_clockwise('R')
temp_col = [self.faces['U'][i][2] for i in range(3)]
for i in range(3): self.faces['U'][i][2] = self.faces['B'][2-i][0]
for i in range(3): self.faces['B'][2-i][0] = self.faces['D'][i][2]
for i in range(3): self.faces['D'][i][2] = self.faces['F'][i][2]
for i in range(3): self.faces['F'][i][2] = temp_col[i]
def B_prime(self):
self._rotate_face_counter_clockwise('B')
temp_strip = copy.deepcopy(self.faces['U'][0])
for i in range(3): self.faces['U'][0][i] = self.faces['L'][i][0]
for i in range(3): self.faces['L'][i][0] = self.faces['D'][2][2-i]
for i in range(3): self.faces['D'][2][i] = self.faces['R'][i][2]
for i in range(3): self.faces['R'][i][2] = temp_strip[2-i]
def apply_moves(self, moves_string):
moves = moves_string.split()
for move in moves:
if move == "U": self.U()
elif move == "D'": self.D_prime()
elif move == "L": self.L()
elif move == "R'": self.R_prime()
elif move == "F": self.F()
elif move == "B'": self.B_prime()
else:
print(f"Warning: Unknown move '{move}' ignored.")
moves = ["U", "L", "F", "B'", "D'", "R'"]
cube = RubiksCube()
#random scramble
for _ in range(1000):
cube.apply_moves(moves[random.randint(0,len(moves)-1)])
flag = "tjctf{" + cube.get_flat_cube_encoded() + "}"
first = cube.get_flat_cube_encoded()
with open("flag.txt", "w", encoding="utf-8") as f: f.write(flag)
random.seed(42)
for _ in range(20):
order = [random.randint(0,len(moves)-1) for _ in range(50)]
for i in range(len(order)):
cube.apply_moves(moves[order[i]])
with open("cube_scrambled.txt", "w", encoding="utf-8") as f: f.write(str(cube.get_cube()))
cube_scrambled.txt
:
{'U': [['🟨', '🟩', '🟧'], ['🟥', '⬜', '🟦'], ['⬜', '🟧', '🟩']], 'L': [['🟦', '🟩', '🟥'], ['⬜', '🟧', '🟧'], ['🟦', ' ⬜', '🟩']], 'F': [['🟧', '⬜', '🟨'], ['🟦', '🟩', '🟨'], ['🟦', '🟨', '🟩']], 'R': [['⬜', '🟥', '🟦'], ['🟧', '🟥', '🟥'], ['🟧', '🟨', '⬜']], 'B': [['🟧', '⬜', '🟥'], ['🟨', '🟦', '🟥'], ['🟨', '🟩', '🟥']], 'D': [['🟩', '🟦', '⬜'], ['🟦', '🟨', '🟩'], ['🟥', '🟧', '🟨']]}
to the point, to construct the flag, the rubixcube.py
has a method get_flat_cube_encoded()
that return a string, and it scrambles the cube using 1000 random moves:
scramble_moves_names = ["U", "L", "F", "B'", "D'", "R'"]
random.seed(42)
for _ in range(20):
for i in [random.randint(0, 5) for _ in range(50)]:
cube.move(scramble_moves_names[i])
the use of random.seed(42)
means the scramble is deterministic, we can reproduce it.
To solve it, we can copy all function in encryptor, and reversed it, the solver would be like this:
import copy
import random
import numpy as np
class RubiksCube:
def __init__(self):
# Initializes a solved cube, not used in the final solver logic
self.faces = {
'U': [['⬜'] * 3 for _ in range(3)],
'L': [['🟧'] * 3 for _ in range(3)],
'F': [['🟩'] * 3 for _ in range(3)],
'R': [['🟥'] * 3 for _ in range(3)],
'B': [['🟦'] * 3 for _ in range(3)],
'D': [['🟨'] * 3 for _ in range(3)]
}
def _rotate_face_clockwise(self, face_name):
face = self.faces[face_name]
new_face = [[None] * 3 for _ in range(3)]
for i in range(3):
for j in range(3):
new_face[i][j] = face[2 - j][i]
self.faces[face_name] = new_face
def _rotate_face_counter_clockwise(self, face_name):
face = self.faces[face_name]
new_face = [[None] * 3 for _ in range(3)]
for i in range(3):
for j in range(3):
new_face[i][j] = face[j][2 - i]
self.faces[face_name] = new_face
def get_flat_cube_encoded(self):
# Correctly encodes the cube state to find the flag content
return "".join([chr(ord(i) % 94 + 33) for i in str(list(np.array(list(self.faces.values())).flatten())) if ord(i) > 256])
# --- Original Moves ---
def U(self):
self._rotate_face_clockwise('U')
temp_row = copy.deepcopy(self.faces['F'][0])
self.faces['F'][0] = self.faces['R'][0]
self.faces['R'][0] = self.faces['B'][0]
self.faces['B'][0] = self.faces['L'][0]
self.faces['L'][0] = temp_row
def L(self):
self._rotate_face_clockwise('L')
temp_col = [self.faces['U'][i][0] for i in range(3)]
for i in range(3): self.faces['U'][i][0] = self.faces['B'][2-i][2]
for i in range(3): self.faces['B'][2-i][2] = self.faces['D'][i][0]
for i in range(3): self.faces['D'][i][0] = self.faces['F'][i][0]
for i in range(3): self.faces['F'][i][0] = temp_col[i]
def F(self):
self._rotate_face_clockwise('F')
temp_strip = copy.deepcopy(self.faces['U'][2])
for i in range(3): self.faces['U'][2][i] = self.faces['L'][2-i][2]
for i in range(3): self.faces['L'][i][2] = self.faces['D'][0][i]
for i in range(3): self.faces['D'][0][2-i] = self.faces['R'][i][0]
for i in range(3): self.faces['R'][i][0] = temp_strip[i]
def D_prime(self):
self._rotate_face_counter_clockwise('D')
temp_row = copy.deepcopy(self.faces['F'][2])
self.faces['F'][2] = self.faces['R'][2]
self.faces['R'][2] = self.faces['B'][2]
self.faces['B'][2] = self.faces['L'][2]
self.faces['L'][2] = temp_row
def R_prime(self):
self._rotate_face_counter_clockwise('R')
temp_col = [self.faces['U'][i][2] for i in range(3)]
for i in range(3): self.faces['U'][i][2] = self.faces['B'][2-i][0]
for i in range(3): self.faces['B'][2-i][0] = self.faces['D'][i][2]
for i in range(3): self.faces['D'][i][2] = self.faces['F'][i][2]
for i in range(3): self.faces['F'][i][2] = temp_col[i]
def B_prime(self):
self._rotate_face_counter_clockwise('B')
temp_strip = copy.deepcopy(self.faces['U'][0])
for i in range(3): self.faces['U'][0][i] = self.faces['L'][i][0]
for i in range(3): self.faces['L'][i][0] = self.faces['D'][2][2-i]
for i in range(3): self.faces['D'][2][i] = self.faces['R'][i][2]
for i in range(3): self.faces['R'][i][2] = temp_strip[2-i]
# --- Inverse Moves (Added for Solver) ---
def U_prime(self):
self._rotate_face_counter_clockwise('U')
temp_row = copy.deepcopy(self.faces['F'][0])
self.faces['F'][0] = self.faces['L'][0]
self.faces['L'][0] = self.faces['B'][0]
self.faces['B'][0] = self.faces['R'][0]
self.faces['R'][0] = temp_row
def L_prime(self):
self._rotate_face_counter_clockwise('L')
temp_col = [self.faces['U'][i][0] for i in range(3)]
for i in range(3): self.faces['U'][i][0] = self.faces['F'][i][0]
for i in range(3): self.faces['F'][i][0] = self.faces['D'][i][0]
for i in range(3): self.faces['D'][i][0] = self.faces['B'][2-i][2]
for i in range(3): self.faces['B'][2-i][2] = temp_col[i]
def F_prime(self):
self._rotate_face_counter_clockwise('F')
temp_strip = copy.deepcopy(self.faces['U'][2])
for i in range(3): self.faces['U'][2][i] = self.faces['R'][i][0]
for i in range(3): self.faces['R'][i][0] = self.faces['D'][0][2-i]
for i in range(3): self.faces['D'][0][i] = self.faces['L'][i][2]
for i in range(3): self.faces['L'][2-i][2] = temp_strip[i]
def D(self):
self._rotate_face_clockwise('D')
temp_row = copy.deepcopy(self.faces['F'][2])
self.faces['F'][2] = self.faces['L'][2]
self.faces['L'][2] = self.faces['B'][2]
self.faces['B'][2] = self.faces['R'][2]
self.faces['R'][2] = temp_row
def R(self):
self._rotate_face_clockwise('R')
temp_col = [self.faces['U'][i][2] for i in range(3)]
for i in range(3): self.faces['U'][i][2] = self.faces['F'][i][2]
for i in range(3): self.faces['F'][i][2] = self.faces['D'][i][2]
for i in range(3): self.faces['D'][i][2] = self.faces['B'][2-i][0]
for i in range(3): self.faces['B'][2-i][0] = temp_col[i]
def B(self):
self._rotate_face_clockwise('B')
temp_strip = copy.deepcopy(self.faces['U'][0])
for i in range(3): self.faces['U'][0][i] = self.faces['R'][2-i][2]
for i in range(3): self.faces['R'][i][2] = self.faces['D'][2][i]
for i in range(3): self.faces['D'][2][2-i] = self.faces['L'][i][0]
for i in range(3): self.faces['L'][i][0] = temp_strip[i]
cube = RubiksCube()
cube.faces = {'U': [['🟨', '🟩', '🟧'], ['🟥', '⬜', '🟦'], ['⬜', '🟧', '🟩']], 'L': [['🟦', '🟩', '🟥'], ['⬜', '🟧', '🟧'], ['🟦', '⬜', '🟩']], 'F': [['🟧', '⬜', '🟨'], ['🟦', '🟩', '🟨'], ['🟦', '🟨', '🟩']], 'R': [['⬜', '🟥', '🟦'], ['🟧', '🟥', '🟥'], ['🟧', '🟨', '⬜']], 'B': [['🟧', '⬜', '🟥'], ['🟨', '🟦', '🟥'], ['🟨', '🟩', '🟥']], 'D': [['🟩', '🟦', '⬜'], ['🟦', '🟨', '🟩'], ['🟥', '🟧', '🟨']]}
random.seed(42)
scramble_moves_names = ["U", "L", "F", "B'", "D'", "R'"]
scramble_sequence = []
for _ in range(20):
order = [random.randint(0, len(scramble_moves_names) - 1) for _ in range(50)]
for i in range(len(order)):
scramble_sequence.append(scramble_moves_names[order[i]])
inverse_move_map = {
"U": cube.U_prime,
"L": cube.L_prime,
"F": cube.F_prime,
"B'": cube.B,
"D'": cube.D,
"R'": cube.R
}
for move_name in reversed(scramble_sequence):
inverse_function = inverse_move_map[move_name]
inverse_function()
flag_content = cube.get_flat_cube_encoded()
flag = "tjctf{" + flag_content + "}"
print(flag)
rubix-cube Flag:
tjctf{G>BGG@BBGA>B>@B??>@G?@B??B>>?GA>@G@ABB@A?AA?@?AA>AG>G@}