Init. commit
This commit is contained in:
30
stego-summer-vacations/WRITEUP.md
Normal file
30
stego-summer-vacations/WRITEUP.md
Normal file
@@ -0,0 +1,30 @@
|
||||
<h1 align="center">Summer Vacations</h1>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://img.shields.io/badge/category-Stego-blueviolet" alt="Stego"/>
|
||||
<img src="https://img.shields.io/badge/points-799-yellow" alt="799 pts"/>
|
||||
</p>
|
||||
|
||||
На входе — картинка `vacation.png`. Если провести классический LSB-скан по красному каналу, тогда быстро находится читаемая строка, похожая на флаг — и это ловушка. Настоящий флаг расположен в младшем бите альфа-канала.
|
||||
|
||||
## Решение
|
||||
|
||||
Открываем картинку в RGBA (принудительно, чтобы гарантированно получить альфа-канал) и проходим по пикселям в растровом порядке (слева направо, сверху вниз). Для каждого пикселя проверяем условие:
|
||||
|
||||
```text
|
||||
(R * G * B) % 7 == 3
|
||||
```
|
||||
|
||||
Только те, что прошли фильтр, несут бит данных — и из них забираем младший бит альфа-канала:
|
||||
|
||||
```python
|
||||
if (r * g * b) % 7 == 3:
|
||||
bits.append(a & 1)
|
||||
```
|
||||
|
||||
Накопленные биты группируем по 8 (старший бит первым) и собираем в байты. Нулевой байт — маркер конца данных, аналог `\0`-терминатора в C. Принимаем только печатаемые ASCII (`0x20`–`0x7E`); всё остальное — либо конец флага, либо мусор.
|
||||
|
||||
Готовый солвер — [`solve/solver.py`](solve/solver.py).
|
||||
|
||||
## Флаг
|
||||
`caplag{p4t13nc3_r3v34ls_truth}`
|
||||
66
stego-summer-vacations/solve/solver.py
Normal file
66
stego-summer-vacations/solve/solver.py
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Правильный флаг спрятан в младшем бите (LSB) альфа-канала пикселей,
|
||||
где (R*G*B) % 7 == 3.
|
||||
Ложный флаг находится в LSB красного канала.
|
||||
Шаги:
|
||||
1. Открыть изображение в режиме RGBA
|
||||
2. Обойти пиксели в растровом порядке (слева направо, сверху вниз)
|
||||
3. Для каждого пикселя проверить условие (R*G*B) % 7 == 3
|
||||
4. Если условие выполнено — извлечь младший бит альфа-канала
|
||||
5. Декодировать каждые 8 бит в один ASCII-символ
|
||||
"""
|
||||
import sys, os
|
||||
from PIL import Image
|
||||
|
||||
def solve(filepath: str) -> str:
|
||||
# Открываем изображение и принудительно переводим в режим RGBA
|
||||
# (чтобы гарантированно получить альфа-канал)
|
||||
img = Image.open(filepath).convert('RGBA')
|
||||
pixels = img.load()
|
||||
|
||||
# Извлекаем младшие биты альфа-канала из подходящих пикселей
|
||||
bits = []
|
||||
for y in range(img.height):
|
||||
for x in range(img.width):
|
||||
r, g, b, a = pixels[x, y]
|
||||
|
||||
# Условие фильтрации: произведение RGB по модулю 7 равно 3
|
||||
# Это нестандартный критерий, чтобы скрыть данные от обычных стего-сканеров
|
||||
if (r * g * b) % 7 == 3:
|
||||
# Берём только последний (младший) бит альфа-канала
|
||||
bits.append(a & 1)
|
||||
|
||||
print(f"[*] Найдено {len(bits)} подходящих пикселей (бит)")
|
||||
|
||||
# Декодируем последовательность бит в строку флага
|
||||
flag = ''
|
||||
for i in range(0, len(bits) - 7, 8):
|
||||
# Собираем байт из 8 последовательных бит (старший бит — первый)
|
||||
byte_val = 0
|
||||
for j in range(8):
|
||||
byte_val = (byte_val << 1) | bits[i + j]
|
||||
|
||||
# Нулевой байт означает конец данных (аналог нуль-терминатора в C)
|
||||
if byte_val == 0:
|
||||
break
|
||||
|
||||
# Принимаем только печатаемые ASCII-символы (коды 32–126)
|
||||
# Всё остальное — признак конца флага или мусорных данных
|
||||
if 32 <= byte_val <= 126:
|
||||
flag += chr(byte_val)
|
||||
else:
|
||||
break
|
||||
|
||||
return flag
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
img_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
|
||||
'..', 'public', 'vacation.png')
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
img_path = sys.argv[1]
|
||||
|
||||
flag = solve(img_path)
|
||||
print(f"[+] Флаг: {flag}")
|
||||
Reference in New Issue
Block a user