/Разборы задач
6 ноября 2026
Райтап на задачу «Стружка-код»
Разбираем обфусцированный Python-код: раскрываем chr() через AST, анализируем проверки и по шагам восстанавливаем флаг.17
9

Rev
Задача «Тихий страж»
Начнём наше решение с того, что разберёмся, с чем мы работаем:
file silent_guardianOUTPUT:
silent_guardian: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-musl-x86_64.so.1, BuildID[sha1]=033c4c2ac786080dd136f80a4683e5fa550af064, strippedИтак, перед нами ELF-бинарь, сбилженный под библиотеку /lib/ld-musl-x86_64.so.1. В зависимости от вашей системы у вас может запуститься этот бинарь, а может и не запуститься, как у меня:
chmod +x silent_guardian
bash silent_guardianOUTPUT:
silent_guardian: silent_guardian: не удалось запустить двоичный файлТем не менее на решение это особо не повлияет, задачу можно решить полностью статическим анализом.
Открываем Ghidra, закидываем наш бинарник и не забываем прожать Analyse.
Идём в Symbol Tree→Functions. Функций у нас не так много, поэтому ищем ту, где присутствует много кода:
Зайдём в функцию FUN_001010e0, там мы видим выводы; вероятнее всего, перед нами функция main.
Разберёмся с логикой:
pipe(&local_8c) и dup2(local_8c, 0x45)pipe создаёт неименованный канал. local_8c — это массив из двух дескрипторов (для чтения и записи).
dup2(local_8c, 0x45) берёт дескриптор чтения из трубы (local_8c) и дублирует его на файловый дескриптор с номером 0x45 (это 69 в десятичной системе). Теперь всё, что будет записано в трубу, можно прочитать через fd 69.while(1)read(0x45, local_84, 99) — читает до 99 байт из fd 69 в буфер local_84. Возвращает количество прочитанных байт (sVar2).
if (sVar2 < 1) break; — если read вернул 0 (конец файла) или -1 (ошибка), цикл прерывается. Поскольку записывающий конец трубы был закрыт (или никогда не был открыт), read сразу вернёт 0, и программа завершится.local_84[sVar2] = '\0'; — завершает строку нулём.
if (local_84[sVar2 - 1] == '\n') local_84[sVar2 - 1] = '\0'; — удаляет символ новой строки, если он есть.
if (strcmp(local_84, "/flag") == 0) — сравнивает введённую строку с "/flag".FUN_00101345(&DAT_00102030, 0x1a, "YnJ1aA==", 8).
Затем putchar выводит каждый байт результата на экран.Для нас тут самой важной является строка FUN_00101345(&DAT_00102030, 0x1a, "YnJ1aA==", 8).
Из неё, по сути, можно получить флаг, потому что логику FUN_00101345 и значение DAT_00102030 мы можем получить, а константы нам известны. Обратимся к FUN_00101345:
Если разобраться и присмотреться, то перед нами просто функция XOR одной строки на другую. В функцию подаются данные для шифрования, их размер, ключ и размер ключа.
Теперь перед написанием кода для дешифровки остаётся вытащить данные из DAT_00102030. Сделать это можно так:
DAT_00102030 в дизассемблированном коде, и нас кидает на место этих данных в памяти.Copy special→Python list — данные скопированы.Теперь остаётся написать простейший Python-скрипт для нахождения флага:
enc = [0x1b, 0x1b, 0x2d, 0x72, 0x35, 0x07, 0x46, 0x7b, 0x1d, 0x31, 0x7c, 0x08, 0x3e, 0x08, 0x6e, 0x62, 0x0d, 0x26, 0x0f, 0x6e, 0x2a, 0x04, 0x64, 0x40, 0x53, 0x6e]
key = b"YnJ1aA=="
flag = bytes([enc[i] ^ key[i % len(key)] for i in range(26)])
print(flag.decode())Флаг получен и таска решена!