8.3 KiB
Мы снова пришли к носу
На старте есть изображение kot5_nos.png. Из него извлекаем сетевой захват, затем изображения из HTTP и пакеты языка сцены. По ним восстанавливаем итоговый опубликованный рисунок. Флаг вычисляется из координат носа в этом рисунке.
Решение
Извлечение HTTP из захвата
В чанке caTs находим конверт CCAP. Его формат описан в CATD.proto: сигнатура, версия 1, длина распакованных данных, SHA-256 и zlib-поток. Проверяем конверт и распаковываем — получаем PCAPNG на 37 872 636 байт с 5788 пакетами.
В захвате есть полезные POST /upload на порт 8080 и шумовые GET /health на 8081. Соединения keep-alive, в одном потоке несколько запросов. Пакеты переупорядочены, встречаются ретрансмиты, поэтому склеивать TCP payload в порядке захвата нельзя.
Группируем TCP-сегменты по адресам и портам отправителя и получателя. Для каждого направления начинаем сборку с ISN + 1, поскольку SYN занимает один номер последовательности, и размещаем payload по sequence numbers. Совпадающие ретрансмиты отбрасываем. При конфликте байтов или пробеле в данных останавливаем сборку. В направлении от клиента к серверу разбираем запросы по заголовкам и Content-Length, оставляем POST /upload с Content-Type: image/png. Так получаем 16 PNG-тел.
В обратном направлении идут HTTP-ответы. В них пропускаем промежуточный 100 Continue, а тела финальных ответов отделяем по Content-Length или chunked-разметке. Эти ответы не добавляем к данным PNG.
Декодирование CATD
В каждом PNG есть CATD.desc: base64 и zlib скрывают дескриптор CDSC с 7240 позициями пар плиток. Каждая пара состоит из двух плиток 4 × 4, образующих блок 8 × 4. Сетка имеет размер 156 × 313. Декодируем base64, затем распаковываем zlib и проверяем CRC32 дескриптора. Его позиции используем в записанном порядке. Для индекса i получаем строку и столбец через divmod(i, 156), а левый верхний пиксель пары имеет координаты (8 * столбец, 4 * строка).
Для пары считаем среднее S = R + G слева и справа в достаточно широком числовом типе, чтобы сумма каналов не переполнилась. Знак разности кодирует бит. Если среднее слева больше, читаем 1, если меньше — 0. Разницу по модулю меньше 1.5 считаем неоднозначной и останавливаем декодирование. Байты собираем MSB-first. Контрольный байт 01010101 позволяет проверить направление сравнения на всех изображениях.
После калибровки появляется сигнатура CATD. Читаем заголовок размером 20 байт, затем payload указанной длины и четыре байта CRC32. Контрольная сумма покрывает заголовок и payload, а записана в little-endian.
| Поле | Смысл |
|---|---|
version |
u8, значение 1 |
type |
u8, значения START=1, DATA=2, END=4 |
session_id |
Идентификатор сессии, u32 LE |
sequence, total_packets |
Номер и количество пакетов, оба u32 LE |
payload_length, payload |
Длина u16 LE и данные |
crc32 |
Проверка пакета |
Выбираем сессию 2085938395, наиболее частую среди валидных пакетов. Из 16 пакетов два — точные дубли, остаются 14. Если одинаковому sequence соответствуют разные байты, останавливаем сборку. Проверяем, что присутствуют все номера от 0 до total_packets - 1, и сортируем пакеты по sequence. Первый должен быть START, последний END, остальные DATA. Первые 32 байта END — SHA-256 склеенных DATA payload. Остаток END содержит правила сборки.
Исполнение сцены
START содержит спецификацию языка и контрольный пример: точка (5, 2) после ROT90 k=1 и родительского TRANSLATE(10, 0) становится (8, 5). Из примера следует, что сначала применяются преобразования своей группы, затем родительской.
После проверки SHA-256 разбираем склеенный поток DATA. В нём 544 байта с 48 командами. Язык умеет объявлять шаблоны, рисовать линии и маркеры, создавать группы и экземпляры, применять переносы, повороты и отражения, удалять объекты и публиковать группу. Координаты — i32 LE в тысячных.
Шаблон кота содержит 27 линий и три маркера. Нужен MARK id=1, нос в локальной точке (200000, 263000). Но экземпляров шаблона три:
| Объект | Состояние | Учитываем? |
|---|---|---|
oid=1 |
Черновая группа gid=10 |
Нет, группа не опубликована |
oid=2 |
Вложенная gid=21 внутри опубликованной gid=20 |
Да |
oid=3 |
В gid=20, затем DELETE 3 |
Нет, объект удалён |
Для оставшегося носа применяем отражение по оси 1, поворот ROT90 k=2 и перенос корня:
| Преобразование | Координаты |
|---|---|
| Локальный маркер | (200000, 263000) |
REFLECT axis=1 |
(200000, -263000) |
ROT90 k=2 |
(-200000, 263000) |
TRANSLATE(212345, -330890) |
(12345, -67890) |
Получаем мировые координаты (12.345, -67.890). Простое чтение локального маркера дало бы другую точку, поскольку здесь важен результат исполнения сцены, включая удаление и публикацию.
Вычисление флага
В nose.flagfn задана функция от координат в тысячных:
X, Y = 12345, -67890
N = 2000001
U, V = X + 1000000, Y + 1000000
Z = U * N + V # 2024691944455
Подставляем Z в caplag{nose_<Z>}. С параметром --render солвер рисует только опубликованную сцену с применёнными преобразованиями и отмечает нос, чтобы результат можно было проверить визуально.
Флаг
caplag{nose_2024691944455}