/Разборы задач
6 ноября 2026
Райтап на задачу «Edit from planes»
Разбираем видео на кадры, исследуем младшие биты изображений и находим спрятанный флаг.11
11

Stegano
Задача «Мозаика внутри PNG»
Сначала открываем картинку. Внешне это обычное фото группы Dabro размером 1000 × 1000, но файл весит почти 1,2 МБ. Сигнатура PNG на месте, однако Pillow отказывается открывать файл с ошибкой cannot identify image file. Значит, дело не в пикселях, а в структуре файла.
Открываем файл в hex-редакторе. После сигнатуры и IHDR идут чанки с именами pT69, pT01, pT06… По спецификации имя чанка — четыре ASCII-буквы, а здесь в имени стоят цифры. Из-за этого строгие парсеры и не читают файл. Всего внутри 139 чанков: IHDR, 37 × IDAT, IEND и 100 кастомных pT00…pT99.
Каждый чанк устроен просто: 4 байта длины, 4 байта имени, данные и 4 байта CRC. У всех pT-чанков CRC посчитан только от данных, без имени чанка, поэтому некоторые утилиты дополнительно считают файл битым. На извлечение это не влияет.
Чанки pT лежат в файле в перемешанном порядке: pT69, pT01, pT06, pT56, pT74, pT99… А после каждого есть текст, который разбросан кусками. Так как номера чанков у нас даны, попытаемся отсортировать их лёгким скриптом на Пайтоне и посмотреть текст, который получится.
import struct
data = open("dabro_bro.png", "rb").read()
pos = 8
pts = {}
while pos < len(data):
length = struct.unpack(">I", data[pos:pos + 4])[0]
name = data[pos + 4:pos + 8].decode("latin1")
if name.startswith("pT"):
pts[name] = data[pos + 8:pos + 8 + length]
pos += 12 + length
# вся склейка — вот здесь:
text = b"".join(pts["pT%02d" % i] for i in range(100)).decode("utf-8")
print(text)
first = text.split("First part of your flag is '")[1].split("'")[0]
second = text.split("Second part is '")[1].split("'")[0]
print("Флаг: bUgCtF{%s%s}" % (first, second))Запускаем: