Files
2026-04-22 10:58:32 +03:00

54 lines
3.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<h1 align="center">Художественная галерея</h1>
<p align="center">
<img src="https://img.shields.io/badge/category-Stego-blueviolet" alt="Stego"/>
<img src="https://img.shields.io/badge/points-979-critical" alt="979 pts"/>
</p>
Изучаем выданный `gallery.psd` — PSD-файл с пятью слоями:
| # | Имя | Состояние |
|---|---|---|
| 0 | `Background` | видимый |
| 1 | `Title` | видимый |
| 2 | `Pattern A` | скрытый |
| 3 | `Pattern B` | скрытый |
| 4 | `Encrypted` | скрытый |
Два из трёх скрытых слоёв — ложный след, а настоящий QR-код лежит в третьем и дополнительно зашифрован AES'ом.
## Решение
PSD разбираем руками по [официальной спецификации Adobe](https://www.adobe.com/devnet-apps/photoshop/fileformatashtml/). Структура файла такая:
```mermaid
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-метки:
```python
aes_key = sha256(timestamp.encode()).digest()[:16]
```
Расшифровываем R-канал в `AES.MODE_ECB`, снимаем PKCS#7-паддинг по последнему байту, интерпретируем результат как `h × w` grayscale-картинку. Внутри — настоящий QR, внутри QR — флаг. Если `pyzbar` с первого раза не узнал код, помогает `NEAREST`-апскейл в 23 раза.
Готовый солвер — [`solve/solver.py`](solve/solver.py).
## Флаг
`caplag{l4y3rs_0f_d3c3pt10n_unv31l3d}`