Таск за баллы: writeup
Ссылка на задачу http://f8tasks.ru/challenges
Таск за баллы
| Category | Stego |
| Difficulty | Easy |
| Technique | ASCII-art steganography → Base32 → Base64 → JSFuck |
| Flag | flag{hellorusski} |
Recon
Пахнет каким-то красным равноправием.
Артефакты: ourCTF.txt.
file ourCTF.txt
awk '{print length}' ourCTF.txt | sort -n | uniq -c
ourCTF.txt: ASCII text, with CRLF line terminators
112 301
1 3744
Ключевые наблюдения:
- 112 строк по 301 символу — регулярная сетка, типичная для ASCII-арта.
- Одна строка длиной 3744 символа — структурная аномалия; CRLF-терминаторы намеренно фиксируют ширину строк.
- Подсказка «красным равноправием» намекает на знаки
=(padding) и прописные буквы — классические признаки Base32.
Проверяем аномальную строку явно:
grep -oE '[A-Z2-7=]{40,}' ourCTF.txt | awk '{print NR, length($0)}'
1 3740
Результат: один непрерывный блок длиной 3740 символов с алфавитом A-Z, 2-7, =. Вывод: в одной строке ASCII-арта спрятана закодированная нагрузка.
ASCII Steganography
Стеганография — скрытие данных внутри другого файла так, чтобы само существование секрета не было очевидным.
Аналогия: афиша с портретом из символов, где в одной строке вместо «мазков» — зашифрованные данные. Снаружи — картинка, внутри — полезная нагрузка. Ключевой вопрос: что за алфавит там использован?
Случайный шум дал бы россыпь коротких совпадений. Один длинный непрерывный блок — явная закодированная нагрузка. Base32-алфавит (A-Z2-7=) не встречается в символах ASCII-рисунка (., ', ,, :, ;) — это позволяет выделить блок одной командой grep.
Decryption
| Шаг | Слой | Команда / инструмент | Результат |
|---|---|---|---|
| 1 | Извлечь Base32-блок | grep -oE '[A-Z2-7=]{40,}' ourCTF.txt | строка 3740 символов |
| 2 | Декодировать Base32 | | base32 -d | flag{W11bKCFb…KQ==} |
| 3 | Снять обёртку flag{} | sed -E 's/^flag\{(.*)\}$/\1/' | строка Base64 |
| 4 | Декодировать Base64 | | base64 -d | JSFuck-код ([]()+!…) |
| 5 | Вычислить JSFuck | quickjs / браузер DevTools | hellorusski |
| 6 | Финальный флаг | — | flag{hellorusski} |
Root cause: задача построена как намеренная цепочка кодирований. flag{...} после шага 2 — ложный финиш: внутри ещё два слоя.
Правильный подход при анализе файлов:
- Проверяй статистику строк (
awk '{print length}'), а не визуальное впечатление. - Ищи аномальные строки по длине и алфавиту, а не по смыслу.
- Знай алфавиты форматов: Base32 —
A-Z2-7=, Base64 —A-Za-z0-9+/=, JSFuck —[]()+!. - После каждого шага перепроверяй формат результата перед тем как объявить задачу решённой.
Automation
import base64
import re
from pathlib import Path
import quickjs # pip install quickjs
text = Path("ourCTF.txt").read_text()
# Шаг 1–2: найти Base32-блок и декодировать его
m = re.search(r"[A-Z2-7=]{40,}", text)
if not m:
raise SystemExit("Base32 blob not found")
outer = base64.b32decode(m.group(0)).decode() # → flag{<base64>}
# Шаг 3–4: снять обёртку flag{}, декодировать Base64
match = re.fullmatch(r"flag\{(.*)\}", outer, re.S)
if not match:
raise SystemExit("flag{} wrapper not found")
jsfuck_code = base64.b64decode(match.group(1)).decode() # → JSFuck-код
# Шаг 5: вычислить JSFuck через quickjs
ctx = quickjs.Context()
result = ctx.eval(jsfuck_code) # → hellorusski
print(f"flag{{{result}}}")
pip install quickjs
python3 solve.py
# flag{hellorusski}
Key Takeaways
Алфавитный анализ. Base32 использует только
A-Z2-7=, Base64 —A-Za-z0-9+/=, JSFuck —[]()+!. Длинный непрерывный блок нетипичных для контейнера символов — первый сигнал скрытой нагрузки, не случайный шум.Слоёная распаковка. Правило: после каждого шага декодирования сразу проверяй формат результата. Задача-матрёшка специально использует обёртку
flag{...}внутри Base32 как ложный финиш — остановившись на нём, получишь неправильный флаг.flag{...}как ловушка. В этой задаче контейнерflag{...}появляется после первого декодирования, но внутри ещё Base64 и JSFuck. Это типичная ловушка для тех, кто сдаёт флаг «как есть» при виде фигурных скобок — содержимое ещё не является финальным ответом.
