|
| 1 | +--- |
| 2 | +title: Quick Recovery |
| 3 | +date: 2024-12-23 19:47:SS +/-0600 |
| 4 | +categories: [Capture The Flags, 1337Up Live 2024] |
| 5 | +tags: [ctf, 1337up, misc, writeups] |
| 6 | +--- |
| 7 | + |
| 8 | +Challenge description: |
| 9 | + |
| 10 | +> 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 😉 |
| 11 | +
|
| 12 | +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? |
| 13 | + |
| 14 | + |
| 15 | + |
| 16 | +Huh, that looks pretty scrambled. Well, lets run the python script! |
| 17 | + |
| 18 | +*Note: You will need to install pillow with pip if you haven't already.* |
| 19 | + |
| 20 | +```terminal |
| 21 | +┌─[slavetomints@parrot]─[~/CTFS/1337UP2024/misc/quick_recovery] |
| 22 | +└──╼ $python gen.py |
| 23 | +Traceback (most recent call last): |
| 24 | + File "/home/slavetomints/CTFS/1337UP2024/misc/quick_recovery/gen.py", line 5, in <module> |
| 25 | + qr_code_image = Image.open("qr_code.png") |
| 26 | + ^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 27 | + File "/home/slavetomints/.pyenv/versions/3.11.2/lib/python3.11/site-packages/PIL/Image.py", line 3469, in open |
| 28 | + fp = builtins.open(filename, "rb") |
| 29 | + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |
| 30 | +FileNotFoundError: [Errno 2] No such file or directory: '/home/slavetomints/CTFS/1337UP2024/misc/quick_recovery/qr_code.png' |
| 31 | +``` |
| 32 | +Hm, looks like it needs to read from `qr_code.png`, lets copy the `obscured.png` file and rename the copy. |
| 33 | + |
| 34 | +Lets take a closer look at the python code |
| 35 | + |
| 36 | +```python |
| 37 | +from PIL import Image, ImageDraw |
| 38 | +from itertools import permutations |
| 39 | +import subprocess |
| 40 | + |
| 41 | +qr_code_image = Image.open("qr_code.png") |
| 42 | +width, height = qr_code_image.size |
| 43 | +half_width, half_height = width // 2, height // 2 |
| 44 | + |
| 45 | +squares = { |
| 46 | + "1": (0, 0, half_width, half_height), |
| 47 | + "2": (half_width, 0, width, half_height), |
| 48 | + "3": (0, half_height, half_width, height), |
| 49 | + "4": (half_width, half_height, width, height) |
| 50 | +} |
| 51 | + |
| 52 | + |
| 53 | +def split_square_into_triangles(img, box): |
| 54 | + x0, y0, x1, y1 = box |
| 55 | + a_triangle_points = [(x0, y0), (x1, y0), (x0, y1)] |
| 56 | + b_triangle_points = [(x1, y1), (x1, y0), (x0, y1)] |
| 57 | + |
| 58 | + def crop_triangle(points): |
| 59 | + mask = Image.new("L", img.size, 0) |
| 60 | + draw = ImageDraw.Draw(mask) |
| 61 | + draw.polygon(points, fill=255) |
| 62 | + triangle_img = Image.new("RGBA", img.size) |
| 63 | + triangle_img.paste(img, (0, 0), mask) |
| 64 | + return triangle_img.crop((x0, y0, x1, y1)) |
| 65 | + |
| 66 | + return crop_triangle(a_triangle_points), crop_triangle(b_triangle_points) |
| 67 | + |
| 68 | + |
| 69 | +triangle_images = {} |
| 70 | +for key, box in squares.items(): |
| 71 | + triangle_images[f"{key}a"], triangle_images[f"{key}b"] = split_square_into_triangles( |
| 72 | + qr_code_image, box) |
| 73 | + |
| 74 | +a_order = ["1", "2", "3", "4"] # UPDATE ME |
| 75 | +b_order = ["4", "3", "2", "1"] # UPDATE ME |
| 76 | + |
| 77 | +final_positions = [ |
| 78 | + (0, 0), |
| 79 | + (half_width, 0), |
| 80 | + (0, half_height), |
| 81 | + (half_width, half_height) |
| 82 | +] |
| 83 | + |
| 84 | +reconstructed_image = Image.new("RGBA", qr_code_image.size) |
| 85 | + |
| 86 | +for i in range(4): |
| 87 | + a_triangle = triangle_images[f"{a_order[i]}a"] |
| 88 | + b_triangle = triangle_images[f"{b_order[i]}b"] |
| 89 | + combined_square = Image.new("RGBA", (half_width, half_height)) |
| 90 | + combined_square.paste(a_triangle, (0, 0)) |
| 91 | + combined_square.paste(b_triangle, (0, 0), b_triangle) |
| 92 | + reconstructed_image.paste(combined_square, final_positions[i]) |
| 93 | + |
| 94 | +reconstructed_image.save("obscured.png") |
| 95 | +print("Reconstructed QR code saved as 'obscured.png'") |
| 96 | +``` |
| 97 | +{: file="gen.py" } |
| 98 | + |
| 99 | +it looks like the `a_order` and `b_order` are influencing how the image gets rearranged, lets change them to |
| 100 | + |
| 101 | +```python |
| 102 | +a_order = ["1", "3", "2", "4"] # UPDATE ME |
| 103 | +b_order = ["4", "2", "3", "1"] # UPDATE ME |
| 104 | +``` |
| 105 | + |
| 106 | + |
| 107 | + |
| 108 | +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. |
| 109 | + |
| 110 | + |
| 111 | + |
| 112 | + |
| 113 | + |
| 114 | +FLAG: `INTIGRITI{7h475_h0w_y0u_r3c0n57ruc7_qr_c0d3}` |
0 commit comments