Skip to main content

Таск за баллы: writeup

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

Таск за баллы

CategoryStego
DifficultyEasy
TechniqueASCII-art steganography → Base32 → Base64 → JSFuck
Flagflag{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 -dflag{W11bKCFb…KQ==}
3Снять обёртку flag{}sed -E 's/^flag\{(.*)\}$/\1/'строка Base64
4Декодировать Base64| base64 -dJSFuck-код ([]()+!…)
5Вычислить JSFuckquickjs / браузер DevToolshellorusski
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

  1. Алфавитный анализ. Base32 использует только A-Z2-7=, Base64 — A-Za-z0-9+/=, JSFuck — []()+!. Длинный непрерывный блок нетипичных для контейнера символов — первый сигнал скрытой нагрузки, не случайный шум.

  2. Слоёная распаковка. Правило: после каждого шага декодирования сразу проверяй формат результата. Задача-матрёшка специально использует обёртку flag{...} внутри Base32 как ложный финиш — остановившись на нём, получишь неправильный флаг.

  3. flag{...} как ловушка. В этой задаче контейнер flag{...} появляется после первого декодирования, но внутри ещё Base64 и JSFuck. Это типичная ловушка для тех, кто сдаёт флаг «как есть» при виде фигурных скобок — содержимое ещё не является финальным ответом.