Init. Commit
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
<h1 align="center">В глазах всё написано</h1>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/category-Stego-blueviolet" alt="Stego"/>
|
||||
</p>
|
||||
|
||||
На старте есть 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}`
|
||||
Reference in New Issue
Block a user