Райтап на задачу «Стружка-код»

dikiyponos

Автор:

dikiyponos

6 ноября 2026

11

06.11.2026
Стружка-код
Категория:

Rev

Задача «Стружка-код»

AlexB

Автор:

AlexB

Первая кровь:
@excsawirq
Сложность:
hard
Дата публикации:
11.10.2025
Решений:
58
Рейтинг:
4.29/5
Ссылка:
Решать

«Попробуй разберись…» звучит угрожающе. Скачаем файл и тут же его запустим.

Решение задачи «Стружка-код»: иллюстрация 1

Первым делом ловим DeprecationWarning и кучу страшных символов. На современной версии Python это нормальное явление. Игнорируем и смотрим вниз.

Решение задачи «Стружка-код»: иллюстрация 2

У нас есть проверка флага? При попытке что-то ввести получаем запрос ввести ещё раз.

Решение задачи «Стружка-код»: иллюстрация 3

А потом программа может и просто вылететь, подчёркивая неизвестно что. Видимо, пора открыть Python-файл и разобраться, что к чему.

Решение задачи «Стружка-код»: иллюстрация 4

4 строчки какого-то не пойми чего… Как нам это прочитать? Попробуем просто вбить chr() в IDLE.

Решение задачи «Стружка-код»: иллюстрация 5

Мы получили букву T — первый символ от «The main and the only purpose of this script is to guess the flag», что появляется при запуске программы. Какой-то магической математикой эти операции конвертируются в число, которое chr() превращает в символ. Как нам привести это к нормальному формату?

Для этого можно использовать ast — abstract syntax tree. Этот модуль позволяет анализировать Python-код в виде дерева. Надо вычислить каждое выражение и получить полноценный исходный код. Ссылка на исходники: https://pastebin.com/QXx26xF9.

Решение задачи «Стружка-код»: иллюстрация 6
import ast

# Мы не хотим, чтобы вызов input() сработал
# и заставил программу зависнуть, разрешаем только chr,
# которым делаются константы.
ENV = {"__builtins__": {}, "chr": chr}


class Yo(ast.NodeTransformer):
    def visit(self, node):
        if isinstance(node, ast.expr):  # Проверяем, что узел — выражение.
            try:
                # Пытаемся вычислить узел целиком.
                value = eval(compile(ast.Expression(node), "<x>", "eval"), ENV)
                const_node = ast.copy_location(ast.Constant(value), node)
                compile(ast.Expression(const_node), "<x>", "eval")
                return const_node
            except Exception:
                pass
        return super().generic_visit(node)


tree = Yo().visit(ast.parse(open("pyf_cked.py").read()))
ast.fix_missing_locations(tree)
print(ast.unparse(tree))

Наша цель — превратить chr() в человеческий вид, поэтому builtins отключаем: мы не хотим, чтобы input() повесил нашу программу.

Сохраняем вывод и получаем unfucked-версию программы.

Решение задачи «Стружка-код»: иллюстрация 7

Ну как unfucked, ещё надо переименовать переменные и красиво форматировать.

Решение задачи «Стружка-код»: иллюстрация 8
print("The main and the only purpose of this script is to guess the flag")
while True:
    booba1 = input("Let's check flag correctness:")
    z = "3xtended_"
    booba2 = all(map(lambda x: booba1[x] == "u", [1, 9, 17, 24, 34]))
    t = "_h3ll0"
    booba2 = (booba2 and booba1[3:6] == "CTF" and (
        int("".join([i if i.isdigit() or ord("a") <= ord(i) < ord("g") else ""
                     for i in booba1]), base=16,) == 1515751645894529294289854812)
    )
    booba2 = (
        booba2
        and all(map(lambda x: not booba1[x].isalnum(), [6, 10, 15, 28, 40, 45, 52]))
        and (booba1[2] == "g")
        and (booba1.find("ve_") > booba1.find("_h") > booba1.find("{y") > 0)
    )
    t = "_th1s_"
    booba2 = (
        booba2
        and booba1[0] == "B"
        and ([booba1[i] for i in [18, 19, 36, 47]] == ["c", "c", "c", "c"])
    )
    booba2 = booba2 and (ord(booba1[37]) == ord(booba1[0].lower()) - 1)
    booba2 = booba2 and booba1[-5:][::-1] == "}tpir"
    (
        print("You win!!!")
        if booba2
        and booba1.find(t) > 0
        and (booba1.find("y_d") == 27)
        and booba1[8].isdigit()
        else print("Can you give me another flag?")
    )

Теперь мы пришли к более-менее читаемому виду… Осталось всего лишь полностью всё разобрать.

booba1 — строка, которую ввёл пользователь. booba2 — флаг, но не тот, который надо ввести на борде, а тот, который bool. Если хотя бы одна проверка флага не сработает, то он станет False.

Первым делом откидываем переменные z и первое определение t — они нигде не используются, это ложный след.

Смотрим первую проверку booba2 на строчке 5. Тут из-за функции all оно требует, чтобы на позициях 1, 9, 17, 24 и 34 был символ u. Запомним.

booba1[3:6] должен быть равен CTF. Оно и очевидно, ведь формат флага — BugCTF{}.

Затем идёт интересная и важная часть — hex-фильтр. Он идёт по каждому символу строки booba1 и оставляет только цифры 0–9 и строчные буквы a–f, всё остальное откидывается. Затем эта строка читается как base16 и сравнивается с длинным числом. В hex-форме это число — 0x4e5cce55f11de0bfca7e15c. То есть по порядку все hex-символы в флаге должны быть 4e5cce55f11de0bfca7e15c (23 символа).

Затем идёт снова знакомая нам функция all(), только теперь на индексах [6, 10, 15, 28, 40, 45, 52] символ не должен быть буквой или цифрой, то есть там должны быть знаки вроде _{}.

booba1[2] = "g" (Bug).

find() возвращает индекс, значит, в строке сначала идёт {y, потом _h, потом ve_ (строка 15).

booba1[0] = "B" (Bug).

На позициях [18, 19, 36, 47] должен быть символ c.

Чтобы понять 23-ю строчку, надо сначала вспомнить предыдущие условия. booba1[0] — это «B». "B".lower() — это «b». ord("b") = 98. Минус 1 будет 97. chr(97) будет «a», значит, 37-й символ — a.

В 24-й строчке берутся последние пять символов и переворачиваются, затем сравниваются с }tpir, значит, флаг заканчивается на ript}.

Финальные условия: в флаге должен быть _th1s_ (стр. 28), подстрока y_d начинается с индекса 27 (стр. 29), а 8-й символ должен быть цифрой. Вспомним про hex-проверки, значит, booba1[8] = 4.

Фух. Это было достаточно душно. Успокаиваемся, усваиваем все условия и начинаем потихоньку составлять флаг… Начинаем с BugCTF{ — тут и к гадалке не ходи.

Затем из find("{y") идёт y, 8-й символ = 4, а 9-й = u.

BugCTF{y4u

10 — явно нижнее подчёркивание, потому что 10-й символ должен быть не буквой и не цифрой. Для 11-го символа вспоминаем последовательность и find. Там говорится, что после нижнего подчёркивания идёт h. 11 = h. Может, сразу и добьём эту проверку? ve_ идёт после _h, а что, если это слово have в l33t? Оставим пока как предположение. К 12-му символу вернёмся позже.

BugCTF{y4u_h?ve_

Вспомним про hex-строку: пока что у нас идёт 4e, значит, ? — не hex-символ. Дальше по хексу должен идти 5, допустим. Затем на 17-й позиции — u. 18-й символ — c из списка. 19-й символ — то же самое, c.

BugCTF{y4u_h?ve_5ucc

Смотрим дальше hex. Идёт e55f — что, если наложить как есть?

BugCTF{y4u_h?ve_5ucce55f

— вырисовывается слово successfully, хороший знак.

Следующий символ — u из списка. Затем две hex-единицы. Потом find("y_d") == 27, выходит:

BugCTF{y4u_h?ve_5ucce55fu11y_d

Продолжим мучить hex-поток. e0bf (получается de0bf — вырисовывается deobfuscate в leet-записи). 34-й символ = u. На 35-й символ, по всей видимости, нет проверки, кроме hex-потока. По слову можно догадаться, что этот символ — s. 36-й = c (hex + список), 37-й = a (hex + ord), 38–39 = 7e по хексу.

BugCTF{y4u_h?ve_5ucce55fu11y_de0bfusca7e

Позиция 40 — не буква и не цифра, значит, _. Кстати, надо куда-то впихнуть _th1s_: он хорошо подходит по смыслу.

BugCTF{y4u_h?ve_5ucce55fu11y_de0bfusca7e_th1s_

41-й символ (1) совпадает с hex. Затем по hex идёт 5c и последние символы: ript}.

BugCTF{y4u_h?ve_5ucce55fu11y_de0bfusca7e_th1s_5cript}

You have successfully deobfuscated this script! Ура, мы почти получили флаг. Осталось узнать, что же за 12-й символ такой. Hex-символы не подходят: они нарушат hex-поток.

Можно долго пробовать разные варианты l33t-speak, чтобы угадать 12-й символ, но почему-то флаг упорно будет не подходить. Почему же? Вспомним, как у нас идёт проверка hex-символов — int(string, 16). Если string начинается с 0, то этот 0 просто проигнорируется.

Решение задачи «Стружка-код»: иллюстрация 9

Получается, всё это время 4-ка была не на том месте. Первый hex-символ = 0, а вместо 12-го идёт 4. Почините флаг — и остаётся только submit.

Если вы нашли опечатку или ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter

Также по теме:

Райтап на задачу «Зона риска»

/Разборы задач

Райтап на задачу «Зона риска»

Разбираем генератор ключа и обращаем шифрование в исполняемом файле, чтобы восстановить флаг.
Прочитать

16

Райтап на задачу «Bogosort»

/Разборы задач

Райтап на задачу «Bogosort»

Восстанавливаем флаг из значений синуса и анаграммы, не дожидаясь случайной сортировки.
Прочитать

8