Скоростные Пазлы – 1 (Fast Puzzles – 1)
Скоростные Пазлы – 1 (Fast Puzzles – 1)
Platform: bug-makers | Category: Misc | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-05-25 | Status: Solved Techniques: api_reverse_engineering, automated_puzzle_solving, mse_pixel_matching
Summary
Task: web-based 4×4 jigsaw puzzle that must be solved within 60 seconds; API provides original image and shuffled pieces as base64 JPEG. Solution: reverse-engineered the API, matched pieces to grid positions using MSE pixel comparison, submitted correct order automatically.
Скоростные Пазлы – 1 (Fast Puzzles – 1) — bug-makers
Description
Чтобы получить флаг, нужно успеть собрать весь пазл за 1 минуту. Количество пазлов: 16 шт. Адрес: https://fast-puzzles-1.t1.bug-makers.ru
English summary: A web-based jigsaw puzzle game where the player must correctly assemble a 4×4 grid (16 pieces) within 60 seconds to receive the flag.
Analysis
The challenge presents a drag-and-drop puzzle interface in the browser. Inspecting the page source and JavaScript (script.js) reveals two API endpoints:
-
GET /api/new-game— Starts a new game session. Returns JSON containing:originalImage: base64-encoded JPEG of the complete, unshuffled imagepuzzle: array of 16 objects, each withid(integer) andimage(base64-encoded JPEG of that piece)gridSize: 4 (the grid is 4×4)pieceSize: 137 (each piece is 137×137 pixels)success: boolean
-
POST /api/check-solution— Accepts{"solution": [array of piece IDs in correct grid order]}. Returns:success: booleanflag: the flag string (if correct)time: time taken in secondstimeUp: boolean (true if over 60 seconds)
The key insight is that the API provides the original unshuffled image alongside the shuffled pieces. This means we don't need to solve the puzzle visually — we can programmatically match each piece to its correct position by comparing pixel data.
Solution
Step 1: Reverse-engineer the API
Using browser DevTools and curl, the two endpoints were identified. The /api/new-game response conveniently includes the full original image, making automated solving trivial.
Step 2: Automate piece matching
The approach:
- Call
GET /api/new-gameto obtain the original image and all 16 shuffled pieces. - Decode all images from base64 into numpy arrays.
- For each grid position
(row, col), extract the corresponding 137×137 pixel region from the original image. - Compare each unmatched piece against the region using Mean Squared Error (MSE) — the piece with the lowest MSE is the correct match.
- Build the solution array of piece IDs in grid order (left-to-right, top-to-bottom).
- Submit via
POST /api/check-solution.
Step 3: Submit and get the flag
The script solved the puzzle in approximately 1.29 seconds, well within the 60-second time limit.
python read-only • copy allowed#!/usr/bin/env python3 """Solve the puzzle by matching pieces to the original image.""" import requests import base64 import json import numpy as np from io import BytesIO from PIL import Image BASE_URL = "https://fast-puzzles-1.t1.bug-makers.ru" # Start a new game session = requests.Session() resp = session.get(f"{BASE_URL}/api/new-game") data = resp.json() grid_size = data["gridSize"] piece_size = data["pieceSize"] pieces = data["puzzle"] original_b64 = data["originalImage"] # Decode original image orig_data = base64.b64decode(original_b64.split(",")[1]) orig_img = Image.open(BytesIO(orig_data)).convert("RGB") orig_arr = np.array(orig_img) # Decode each piece piece_images = {} for p in pieces: p_data = base64.b64decode(p["image"].split(",")[1]) p_img = Image.open(BytesIO(p_data)).convert("RGB") piece_images[p["id"]] = np.array(p_img) # For each grid position, find the best matching piece via MSE correct_order = [] used_pieces = set() for row in range(grid_size): for col in range(grid_size): y_start = row * piece_size x_start = col * piece_size region = orig_arr[y_start:y_start+piece_size, x_start:x_start+piece_size] best_id = None best_score = float('inf') for pid, p_arr in piece_images.items(): if pid in used_pieces: continue h, w = region.shape[:2] ph, pw = p_arr.shape[:2] if h != ph or w != pw: p_resized = np.array(Image.fromarray(p_arr).resize((w, h))) else: p_resized = p_arr diff = region.astype(float) - p_resized.astype(float) mse = np.mean(diff ** 2) if mse < best_score: best_score = mse best_id = pid correct_order.append(best_id) used_pieces.add(best_id) # Submit solution resp = session.post( f"{BASE_URL}/api/check-solution", json={"solution": correct_order}, headers={"Content-Type": "application/json"} ) result = resp.json() print(json.dumps(result, indent=2, ensure_ascii=False))