В глазах всё написано

Stego

На старте есть RGB-изображение `kot1_v_glazah.png` размером 1254 × 1254. Начинаем с текстовых чанков PNG. Запись `EditorNote` описывает работу редактора с блоками 2 × 2. ## Решение Читаем метаданные через Pillow: ```python from PIL import Image img = Image.open("public/kot1_v_glazah.png") for key, value in img.info.items(): print(key, value, sep="\n") ``` Из заметки узнаём, что редактор только переставляет четыре пикселя внутри блока, не меняя их цвета. Пригодны блоки, в которых все цвета различны, а разброс каждого канала не превышает `3`. После перестановки блок остаётся пригодным по тем же признакам. Обходим сетку от `(0, 0)` по строкам, слева направо. Для каждого пригодного блока сортируем цвета по ключу: ```text (77 * R + 150 * G + 29 * B, R, G, B) ``` Так получаем канонические цвета `C0`, `C1`, `C2`, `C3`. Позиции в блоке нумеруем отдельно: `0` — сверху слева, `1` — сверху справа, `2` — снизу слева, `3` — снизу справа. Перестановку записываем как **позиции канонических цветов**: где находится `C0`, затем `C1`, `C2` и `C3`. Например, если в порядке чтения блока стоят `C2, C0, C3, C1`, нужный кортеж — `(1, 3, 0, 2)`. Кортеж номеров цветов `(2, 0, 3, 1)` задаёт обратную перестановку и даст другой полубайт. У четырёх элементов `4! = 24` перестановки. Нумеруем их лексикографически. Первые 16 номеров кодируют значения полубайта от `0` до `15`. ```python from itertools import permutations catalog = list(permutations(range(4))) # px — четыре RGB-пикселя в порядке TL, TR, BL, BR. positions = tuple(sorted( range(4), key=lambda i: (77 * int(px[i][0]) + 150 * int(px[i][1]) + 29 * int(px[i][2]), *map(int, px[i])), )) nibble = catalog.index(positions) ``` Первые 16 пригодных блоков должны дать контрольный ряд `0, 1, …, 15`. Сверяем его перед чтением сообщения: так проверяем отбор блоков, сортировку и направление обхода сразу. Пропускаем 16 калибровочных блоков. Дальше объединяем полубайты, старший первым, `byte = (hi << 4) | lo`. В начале потока появляется конверт: | Поле | Формат | |---|---| | Сигнатура | `CTP1` | | Версия | `1` | | Длина payload | `u32 LE` | | Payload | Текст указанной длины | | Контрольная сумма | CRC32 payload, `u32 LE` | После заголовка читаем только указанное число байт payload и следующие четыре байта CRC32: остальные пригодные блоки к сообщению уже не относятся. Сверяем контрольную сумму. В тексте лежит флаг, а за ним авторская приписка: «Он ничего не потерял. Просто всё переставил. Даже взгляд.» [Солвер](solve/solve.py). ## Флаг `caplag{p3r3st4n0vk1_v_gl4z4h_k0t4}`