← Back to Writeups
CUSTOMMediumMisc

Ах#енный зум (Awesome Zoom)

XESXOR9/2/20264 min read
#misc#custom#medium#column_binary_decode#connected_component_centroids#grid_line_detection#grid_snapping#right_to_left_group_reading

Ах#енный зум (Awesome Zoom)

Platform: bug-makers | Category: Misc | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2026-05-29 | Status: Solved Techniques: column_binary_decode, connected_component_centroids, grid_line_detection, grid_snapping, right_to_left_group_reading

Summary

Task: a JPEG shows a cyan grid with glowing yellow dots; time/date/coordinates are encoded by dot positions. Solution: detect grid lines and dot centroids, snap to a 7x13 bit matrix, read each column as a 7-bit value, then read groups right-to-left — values match the Chernobyl disaster (01:23, 1986/04/26, 51.39N 30.10E).

Ах#енный зум (Awesome Zoom) — Bug Makers CTF

Description

RU: "На картинке зашифрованы время, дата и координаты. Расшифруйте эти данные."

EN: "Time, date, and coordinates are encrypted in the picture. Decrypt this data."

Artifact: a single JPEG (815.jpg, 1408×736) showing a sci-fi cyan grid on a black background with several glowing yellow dots placed in grid cells, plus faint decorative "matrix" background text (noise, not data).

Flag format: BugCTF{hh:mm_YY/mm/dd_xxxxyyyy}

Analysis

Standard recon was a dead end: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

  • exiftool 815.jpg — no useful metadata.
  • strings 815.jpg — nothing.

The decisive clue is the title "Awesome Zoom": the wordplay tells you to zoom into the grid and read the dot positions — the data is spatial, not hidden in the JPEG bytes (no LSB/appended-file stego).

Structure detection:

  • Grid lines are cyan pixels (high G, high B, low R). Vertical lines are spaced ~92.6 px apart, horizontal lines ~92.3 px apart.
  • Dots are yellow (R>180, G>150, B<120). Using scipy.ndimage.label to find connected components and their centroids gives 27 dots.
  • Dot X-centers cluster into 9 distinct columns, Y-centers into 7 distinct rows.
  • The full lattice is a 7-row × 13-column grid. Only 9 columns contain dots: indices 0, 3,4,5,6, 9,10,11,12. The two 2-column gaps (cols 1–2 and 7–8) are field separators, splitting the data into 3 groups of sizes 1 / 4 / 4 — exactly matching the three underscore-separated fields of the flag (time _ date _ coords). ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Recovered grid (1 = dot):

Row0: 0000001000000
Row1: 0000000000011
Row2: 1000101000101
Row3: 0000100001100
Row4: 1000011000110
Row5: 1000101001111
Row6: 1001000000011

Solution

Each data column is read top-to-bottom as a 7-bit binary number (Row0 = MSB):

colbits (Row0..Row6)value
0001101123
300000011
4000101026
500001014 (0000100=4)
6010011086
9000000110 (0001010=10)
10000011130 (0011110=30)
11001001139 (0100111=39)
12001111151 (0110011=51)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Per-column values: col0=23, col3=1, col4=26, col5=4, col6=86, col9=10, col10=30, col11=39, col12=51.

First attempt (left-to-right within each group) produced hh=23, mm=01, YY=26, mm=04, dd=86. Day 86 is impossible, signalling the within-group reading direction was wrong.

Insight: read each group right-to-left and recognize the famous event:

  • Time group (col3, col0) → hh=01, mm=23 → 01:23
  • Date group (col6, col5, col4) → YY=86, mm=04, dd=26 → 1986/04/26
  • Coords group (col12, col11, col10, col9) → 51 39 30 10 → 51393010 (i.e. 51.39 N, 30.10 E) ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Everything lines up with the Chernobyl nuclear disaster: 01:23 on 26 April 1986, at the Chernobyl NPP located at 51.389 N, 30.099 E. This historical coherence confirms both the field assignment and the right-to-left reading direction.

python
#!/usr/bin/env python3
# Column -> value extraction and right-to-left group reading
grid = [
    "0000001000000",
    "0000000000011",
    "1000101000101",
    "0000100001100",
    "1000011000110",
    "1000101001111",
    "1001000000011",
]

def col_value(c):
    bits = "".join(grid[r][c] for r in range(7))  # Row0 = MSB
    return int(bits, 2)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Data columns (dot-bearing); gaps at 1-2 and 7-8 are field separators
groups = [[0], [3, 4, 5, 6], [9, 10, 11, 12]]
vals = {c: col_value(c) for g in groups for c in g}
print("per-column:", vals)

# Read each group RIGHT-TO-LEFT
time_g  = [vals[c] for c in reversed([3, 0])]            # actually [0] + first-group cols
# time field = minutes(col0), hours(col3): right-to-left over [col0(min), col3(hr)]
hh, mm  = vals[3], vals[0]                                # 01, 23
yy, mo, dd = vals[6], vals[5], vals[4]                    # 86, 04, 26
a, b, c, d = vals[12], vals[11], vals[10], vals[9]        # 51, 39, 30, 10
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

flag = f"BugCTF{{{hh:02d}:{mm:02d}_{yy:02d}/{mo:02d}/{dd:02d}_{a:02d}{b:02d}{c:02d}{d:02d}}}"
print(flag)  # BugCTF{REDACTED}

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