Init. commit

This commit is contained in:
Caplag
2026-04-22 10:42:16 +03:00
commit 98e51ca58b
35 changed files with 2371 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
<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}`