Post

Quick Recovery

1337UP Live 2024 Quick Recovery Challenge

Challenge description:

Hey, check this QR code ASAP! It’s highly sensitive so I scrambled it, but you shouldn’t have a hard time reconstructing - just make sure to update the a_order to our shared PIN. The b_order is the reverse of that 😉

Downloading the attached file and extrating its contents leads us to have a gen.py file and a obscured.png file. What is the png?

the original qr code

Huh, that looks pretty scrambled. Well, lets run the python script!

Note: You will need to install pillow with pip if you haven’t already.

1
2
3
4
5
6
7
8
9
10
┌─[slavetomints@parrot]─[~/CTFS/1337UP2024/misc/quick_recovery]
└──╼ $python gen.py 
Traceback (most recent call last):
  File "/home/slavetomints/CTFS/1337UP2024/misc/quick_recovery/gen.py", line 5, in <module>
    qr_code_image = Image.open("qr_code.png")
                    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/slavetomints/.pyenv/versions/3.11.2/lib/python3.11/site-packages/PIL/Image.py", line 3469, in open
    fp = builtins.open(filename, "rb")
         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: '/home/slavetomints/CTFS/1337UP2024/misc/quick_recovery/qr_code.png'

Hm, looks like it needs to read from qr_code.png, lets copy the obscured.png file and rename the copy.

Lets take a closer look at the python 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
from PIL import Image, ImageDraw
from itertools import permutations
import subprocess

qr_code_image = Image.open("qr_code.png")
width, height = qr_code_image.size
half_width, half_height = width // 2, height // 2

squares = {
    "1": (0, 0, half_width, half_height),
    "2": (half_width, 0, width, half_height),
    "3": (0, half_height, half_width, height),
    "4": (half_width, half_height, width, height)
}


def split_square_into_triangles(img, box):
    x0, y0, x1, y1 = box
    a_triangle_points = [(x0, y0), (x1, y0), (x0, y1)]
    b_triangle_points = [(x1, y1), (x1, y0), (x0, y1)]

    def crop_triangle(points):
        mask = Image.new("L", img.size, 0)
        draw = ImageDraw.Draw(mask)
        draw.polygon(points, fill=255)
        triangle_img = Image.new("RGBA", img.size)
        triangle_img.paste(img, (0, 0), mask)
        return triangle_img.crop((x0, y0, x1, y1))

    return crop_triangle(a_triangle_points), crop_triangle(b_triangle_points)


triangle_images = {}
for key, box in squares.items():
    triangle_images[f"{key}a"], triangle_images[f"{key}b"] = split_square_into_triangles(
        qr_code_image, box)

a_order = ["1", "2", "3", "4"]  # UPDATE ME
b_order = ["4", "3", "2", "1"]  # UPDATE ME

final_positions = [
    (0, 0),
    (half_width, 0),
    (0, half_height),
    (half_width, half_height)
]

reconstructed_image = Image.new("RGBA", qr_code_image.size)

for i in range(4):
    a_triangle = triangle_images[f"{a_order[i]}a"]
    b_triangle = triangle_images[f"{b_order[i]}b"]
    combined_square = Image.new("RGBA", (half_width, half_height))
    combined_square.paste(a_triangle, (0, 0))
    combined_square.paste(b_triangle, (0, 0), b_triangle)
    reconstructed_image.paste(combined_square, final_positions[i])

reconstructed_image.save("obscured.png")
print("Reconstructed QR code saved as 'obscured.png'")

it looks like the a_order and b_order are influencing how the image gets rearranged, lets change them to

1
2
a_order = ["1", "3", "2", "4"]  # UPDATE ME
b_order = ["4", "2", "3", "1"]  # UPDATE ME

better image

Hey!, now while it is split in half, lets take a screenshot of each half and rearrange those so that its in order. Sure, we could try finding the correct arrangement of numbers, but for a first try I’ll take it.

QR1

QR2

FLAG: INTIGRITI{7h475_h0w_y0u_r3c0n57ruc7_qr_c0d3}

This post is licensed under CC BY 4.0 by the author.