3.3 KiB
Художественная галерея
Изучаем выданный gallery.psd — PSD-файл с пятью слоями:
| # | Имя | Состояние |
|---|---|---|
| 0 | Background |
видимый |
| 1 | Title |
видимый |
| 2 | Pattern A |
скрытый |
| 3 | Pattern B |
скрытый |
| 4 | Encrypted |
скрытый |
Два из трёх скрытых слоёв — ложный след, а настоящий QR-код лежит в третьем и дополнительно зашифрован AES'ом.
Решение
PSD разбираем руками по официальной спецификации Adobe. Структура файла такая:
block-beta
columns 1
H["8BPS header"]
CM["Color Mode Data"]
IR["Image Resources<br/>XMP ID 0x0424 ← ключ к расшифровке"]
LMI["Layer and Mask Info<br/>слои, флаги, каналы"]
ID["Image Data"]
classDef base fill:#f3f4f6,stroke:#6b7280,color:#111827
classDef hit fill:#fef3c7,stroke:#f59e0b,color:#78350f
class H,CM,LMI,ID base
class IR hit
В секции Image Resources ищем XMP-метаданные (блок с ID 0x0424) — там и запрятан ключ ко всей задаче. Внутри XMP смотрим на тег <xmp:CreateDate>: ISO 8601 timestamp создания файла. В секции Layer and Mask Information для каждого слоя лежит имя, флаги видимости (бит 0x02 в flags = «скрытый»), opacity и сырые данные каналов. Пиксельные данные хранятся несжатыми — можно напрямую залить в numpy-массив h × w.
Первое, что теперь приходит в голову при виде двух скрытых «паттернов» — это XOR'нуть их между собой. Берём Pattern A и Pattern B, XOR — получается картинка с вполне читаемым QR-кодом. Сканируем, внутри флаг, таск решён… кроме того, что флаг внутри фейковый. Настоящий QR живёт в третьем скрытом слое — Encrypted, и это шифротекст исходного QR в режиме AES-ECB.
Ключ для AES собирается из той самой timestamp-метки:
aes_key = sha256(timestamp.encode()).digest()[:16]
Расшифровываем R-канал в AES.MODE_ECB, снимаем PKCS#7-паддинг по последнему байту, интерпретируем результат как h × w grayscale-картинку. Внутри — настоящий QR, внутри QR — флаг. Если pyzbar с первого раза не узнал код, помогает NEAREST-апскейл в 2–3 раза.
Готовый солвер — solve/solver.py.
Флаг
caplag{l4y3rs_0f_d3c3pt10n_unv31l3d}