48 lines
4.3 KiB
Markdown
48 lines
4.3 KiB
Markdown
<h1 align="center">Он немного изменился</h1>
|
||
|
||
<p align="center">
|
||
<img src="https://img.shields.io/badge/category-Stego-blueviolet" alt="Stego"/>
|
||
</p>
|
||
|
||
На старте есть изображение `kot4_izmenilsya.png`. В метаданных указаны встроенный слепок и движение внутри клеток. Размер плитки и таблица направлений не указаны. Сначала ищем исходное изображение внутри PNG.
|
||
|
||
## Решение
|
||
|
||
В приватном ancillary-чанке `caTs` лежит конверт `CREF`: сигнатура, версия `1`, длина `u32 LE`, SHA-256 и байты чистого PNG. Проверяем CRC чанка, затем длину и SHA-256 конверта. Получаем эталонную картинку для сравнения.
|
||
|
||
Разность изображений показывает изменения в клетках, выровненных от `(0, 0)`. Пробуем размеры `4 × 4`, `8 × 8`, `16 × 16`. Для каждого сравниваем изменённую плитку с четырьмя циклическими сдвигами эталонной на один пиксель.
|
||
|
||
```python
|
||
import numpy as np
|
||
|
||
candidates = [
|
||
np.roll(tile, -1, axis=0), # вверх
|
||
np.roll(tile, 1, axis=1), # вправо
|
||
np.roll(tile, 1, axis=0), # вниз
|
||
np.roll(tile, -1, axis=1), # влево
|
||
]
|
||
```
|
||
|
||
Подходит только сетка **8 × 8**, в которой каждая изменённая плитка точно совпадает с одним из сдвигов. При других размерах движение пересекает предполагаемые границы плиток, и такого совпадения нет.
|
||
|
||
Однозначное направление можно прочитать только у плиток, которые действительно меняются при сдвиге. Поэтому пригодность проверяем по оригиналу, ещё до чтения битов. Все четыре сдвига должны различаться между собой и с исходной плиткой, а максимальная абсолютная разница по каналам между каждой сдвинутой плиткой и оригиналом не должна превышать `4`. Разность считаем в знаковом типе, чтобы вычитание `uint8` не переполнилось.
|
||
|
||
Обходим пригодные плитки по строкам. По подсказке первые использованные клетки служат для калибровки. В первых четырёх видим сдвиги вверх, вправо, вниз и влево. Пробуем сопоставить им пары `00`, `01`, `10`, `11` по порядку, а затем проверяем гипотезу по сигнатуре и CRC полученного потока:
|
||
|
||
| Направление | Пара бит |
|
||
|---|---|
|
||
| Вверх | `00` |
|
||
| Вправо | `01` |
|
||
| Вниз | `10` |
|
||
| Влево | `11` |
|
||
|
||
Пропускаем четыре калибровочные плитки и собираем байты по четыре пары, старшая первой. Сначала читаем девятибайтный заголовок с сигнатурой `CTM4`, версией `1` и длиной payload в `u32 LE`. Затем читаем payload указанной длины и ещё четыре байта CRC32 в little-endian. Полная длина конверта равна `9 + payload_length + 4` байтам. Совпадение сигнатуры, версии и CRC подтверждает выбранную карту направлений.
|
||
|
||
В payload читаем флаг и фразу «Это точно та же самая картинка. Мы почти уверены.» Встроенного эталона и самих различий хватило, чтобы восстановить схему без готовой карты в метаданных.
|
||
|
||
[Солвер](solve/solve.py).
|
||
|
||
## Флаг
|
||
|
||
`caplag{sdv1g_n4_0d1n_p1ks3l}`
|