Skip to main content

010: writeup

Ссылка на задачу http://f8tasks.ru/challenges#010-22

010

CategoryStegano
DifficultyEasy
TechniqueBitmap Reconstruction → QR
Flagflag{are_you_robot?}

Recon

Нам выдан файл file.txt. На первый взгляд — просто текст. Задача называется «Черное и белое». Найди флаг.

Артефакт: file.txt.

file file.txt
wc -c file.txt
file.txt: ASCII text, with no line terminators
108900 file.txt

Ключевые наблюдения:

  • файл состоит исключительно из символов 0 и 1;
  • расширение .txt не говорит о структуре данных — нужно смотреть внутрь;
  • размер файла — 108900 байт.

Первая проверка — является ли размер точным квадратом:

from math import isqrt
print(isqrt(108900))   # → 330
print(330 * 330)       # → 108900

Результат: 330. Вывод: 108 900 бит раскладываются в квадрат 330 × 330.


Bitmap Reconstruction

Bitmap (битовая карта) — изображение, в котором каждый пиксель задаётся одним битом: 1 = чёрный, 0 = белый (или наоборот).

Ключевой вопрос: если разбить поток из 108 900 символов на строки по 330 — что получится?

QR-код версии 2 устроен так: поле данных 25 × 25 модулей, тихая зона по 4 модуля с каждой стороны, каждый модуль масштабирован до 10 × 10 пикселей. Итоговый размер:

$$ (25 + 4 + 4) \times 10 = 33 \times 10 = 330 $$

Размер 330 × 330 в точности совпадает с QR версии 2. Гипотеза: перед нами QR-код, сериализованный как плоский поток битов.

Проверяем, просто нарисовав картинку:

from pathlib import Path
from math import isqrt
from PIL import Image

data = Path("file.txt").read_text().strip()
side = isqrt(len(data))

img = Image.new("1", (side, side))
px = img.load()
for i, bit in enumerate(data):
    px[i % side, i // side] = 0 if bit == "1" else 1  # 1 → чёрный пиксель
img.save("qr.png")
qr.png — три угловых finder-паттерна видны сразу

Три большие квадратные метки по углам — классический признак QR-кода. Телефон или любой QR-сканер читает его мгновенно.


Decryption

ШагДействиеФормула / команда
1Убедиться, что длина — точный квадратisqrt(108900) = 330; 330 × 330 = 108900
2Сложить биты в матрицу 330 × 330первые 330 символов → строка 0; следующие 330 → строка 1; …
3Отрисовать как чёрно-белое изображениеImage.new("1", (330, 330))
4Распознать QR по трём угловым finder-паттернамвизуальная проверка
5Считать QR телефоном или библиотекойflag{are_you_robot?}

Root cause: задача намеренно сохранила QR-код как поток символов в .txt. Стандартные инструменты (file, strings) ничего не сообщат — нужно сначала понять форму данных, а уже потом пытаться её декодировать.

Правильный подход при анализе неизвестного файла:

  • не доверяй расширению — проверяй реальное содержимое;
  • смотри, из каких символов состоит файл (только 0 и 1 = явный сигнал);
  • проверяй размер на квадратность / кратность известным форматам;
  • пробуй несколько интерпретаций: картинка, аудио, архив, кодировка.

Automation

from pathlib import Path
from math import isqrt
from PIL import Image

data = Path("file.txt").read_text().strip()
side = isqrt(len(data))

assert side * side == len(data), "данные не образуют квадрат"

img = Image.new("1", (side, side))
px = img.load()

for i, bit in enumerate(data):
    # "1" в файле → чёрный пиксель (0 в PIL); "0" → белый (1 в PIL)
    px[i % side, i // side] = 0 if bit == "1" else 1

img.save("qr.png")
print("Saved qr.png — scan with phone or qreader")
python3 solve.py
# Saved qr.png — scan with phone or qreader

После сканирования qr.png любым QR-сканером:

flag{are_you_robot?}

Key Takeaways

  1. Расширение файла — не тип данных. .txt может содержать битовую карту, архив или любой другой бинарный формат. Всегда проверяй содержимое через file и анализ символов.

  2. Квадратный размер данных — сигнал. Если число символов равно $n^2$, данные скорее всего представляют квадратную матрицу. Одна строка isqrt(len(data))**2 == len(data) подтверждает или опровергает гипотезу за секунду.

  3. Ловушка: название «Черное и белое». Это прямой намёк на монохромное изображение — но многие участники тратят время на поиск Base64, XOR-шифра или LSB-стего внутри текста, не проверив базовую структуру файла первой.