← Back to Writeups
CUSTOMEasyMisc

Мозаика (Mosaic)

XESXOR9/2/20263 min read
#misc#custom#easy#base64_to_zip#image_rotation_180#qr_reassembly#quadrant_rearrangement#zip2john_cracking#linux

Мозаика (Mosaic)

Platform: hackerlab | Category: Misc | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-04-05 | Status: Solved Techniques: base64_to_zip, image_rotation_180, qr_reassembly, quadrant_rearrangement, zip2john_cracking

Summary

Task: a PNG image contains a QR code rendered with circular dots, split into 4 quadrants and rearranged (one quadrant rotated 180°). Solution: identify cut lines between finder patterns, rearrange quadrants to restore valid QR layout, decode to get base64-encoded password-protected ZIP, crack with john to extract flag.

Мозаика (Mosaic) — Hackerlab (CODEBY Games)

Description

Задача проста на первый взгляд, но только на первый. Мы нашли необычную картину - это не просто красочная мозаика, она была создана нашим старым другом, который обожает шифровать в них скрытые сообщения

English summary: the provided archive contains task.png (846×846 pixels) — a QR code rendered with anti-aliased circular dots instead of standard square modules. The QR code has been split into 4 quadrants and the pieces have been rearranged like a jigsaw puzzle, with one piece rotated 180°. The goal is to reassemble the QR code, decode it, and extract the flag. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Analysis

Initial observations

The 846×846 image shows a grid of circular dots of varying sizes on a white background. Three QR finder patterns (the characteristic concentric squares) are visible, but at non-standard positions:

  • Two finder patterns side by side near the top-center of the image
  • One finder pattern at the bottom-right corner

In a valid QR code, the three finder patterns must be at the top-left, top-right, and bottom-left corners. The unusual positioning immediately suggests the image has been cut and rearranged. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Grid analysis

The image contains a 61×61 grid of dots (QR version 11), with cell spacing of approximately 13.8 pixels. The dots are circular and anti-aliased, which causes most standard QR decoders (pyzbar, zbarimg) to fail — they expect sharp square modules.

Identifying the puzzle

The image was split into 4 quadrants by two cuts:

  • Vertical cut at approximately x=401 pixels (between the two adjacent top finder patterns)
  • Horizontal cut at approximately y=470 pixels (above the bottom-right finder pattern) ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

These cuts produce 4 unequal pieces:

  • A (top-left, 470×401): contains one finder pattern
  • B (top-right, 470×445): contains one finder pattern
  • C (bottom-left, 376×401): no finder pattern
  • D (bottom-right, 376×445): contains one finder pattern, rotated 180°

Reassembly

To restore the valid QR layout (finders at TL, TR, BL):

Original layout:     Reassembled:
  [A | B]              [B | A]
  [C | D]              [rot180(D) | C]

The bottom-right quadrant D must be rotated 180° before placement at the bottom-left position to correctly orient its finder pattern. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

Step 1: Reassemble the QR code

python
#!/usr/bin/env python3
"""Reassemble the mosaic QR code from 4 rearranged quadrants."""
import numpy as np
from PIL import Image

img = np.array(Image.open('task.png').convert('L'))
# Cut positions (between finder patterns)
cut_x, cut_y = 401, 470

# Extract 4 quadrants
A = img[0:cut_y, 0:cut_x]       # top-left
B = img[0:cut_y, cut_x:]        # top-right
C = img[cut_y:, 0:cut_x]        # bottom-left
D = img[cut_y:, cut_x:]         # bottom-right
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Rotate D by 180 degrees
D_rot = np.rot90(D, 2)

# Reassemble: [B, A] on top, [rot180(D), C] on bottom
assembled = np.vstack([
    np.hstack([B, A]),
    np.hstack([D_rot, C])
])

Image.fromarray(assembled).save('assembled_qr.png')

Step 2: Decode the QR code

Standard QR decoders fail on the circular dot style. The zxingcpp Python library handles it:

python
import zxingcpp

results = zxingcpp.read_barcodes(Image.fromarray(assembled))
for r in results:
    print(r.text)

Alternatively, the Aspose online QR reader (aspose.app) can scan dot-based QR codes. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The QR decodes to a base64 string:

UEsDBAoACQAAAP1t/FaDz0hbJAAAABgAAAAIABwAZmxhZy50eHRV...

Step 3: Decode base64 to ZIP

bash
echo "UEsDBAoACQAAAP1t/FaD..." | base64 -d > flag.zip

The PK header (0x504B) confirms this is a ZIP archive. The archive contains flag.txt but is password-protected (PKZIP traditional encryption).

Step 4: Crack the ZIP password

bash
zip2john flag.zip > hash.txt
john hash.txt
# Password found: iloveme

Step 5: Extract the flag

bash
unzip -P iloveme flag.zip
cat flag.txt
# CODEBY{REDACTED}

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍