Ыж на фоне ритуального торта.
Во время Зимней школы, spbctf подготовили для нас соревнование в формате jeopardy.
Таски на реверс за авторством @groke (Egor Zaytsev)
reverse, 150 баллов > Hand Cracker
Помогите мне решить эту крякмиху!
nc 109.233.56.90 15451
reverse, 300 баллов > Robots Taking Reversers' Jobs
Говорят, скоро всех заменят роботы. А те, кто делает роботов, будут править миром.
Начинайте править уже сейчас: решите 10 таких же крякмих, как на первом уровне.
nc 109.233.56.90 15451
Как видите, оба таска имеют одну цель, поэтому проще начать решать сразу вторую задачу, чтобы покрыть сразу два таска.
Постучавшить по нэткату, получаем такое вот приветствие.
Оно статично из раза в раз и его решение покрывает первую часть нашего задания. Второй и следующий текст генерируются рандомно.
Если упростить объяснения, получается такой механизм:
- Программа состоит из двух типов структур: состояний
state
и переходовtransition
. - Переход характеризуется буквой перехода и ссылкой на какую-либо структуру
state
. - Состояние характеризуется значением состояния и набором переходов (0+) к следующему состоянию. У состояний есть также индекс.
- Назначаются начальное (с индексом
0
) и конечное (со значением состояния0xCADE7
) состояния. - От пользователя требуется передать ключ, представляющий собой строку.
- Программа пытается пройтись от начального состояния до других, используя переходы между состояниями.
Пример схемы переходов
Таким образом, если состояние с индексом 0
будет обладать значением 1337
,
то ключом, который введет пользователь, должен быть RK
.
Поделим программу на несколько частей:
- Получение кода.
- Парсинг кода.
- Нахождение пути до нужного состояния.
- Отправка решения.
Внизу представлен код из тетради Юпитера как есть, без красоты и дополнительных комментариев
import nclib
from time import sleep
nc = nclib.Netcat(('109.233.56.90', 15451), verbose=False)
sleep(1)
target_value = 0xCADE7
while 1:
ans = getcode()
blocks = gen_block(ans)
tos = wave(blocks)
nc.send(tos.encode())
Основной блок
Получения кода
Когда-нибудь я перейду на использование pwntools
…
def getcode():
ans = ""
while "answer:" not in ans:
#sleep(0.3)
ans+= nc.read().decode()
return ans
Так как я получаю строки блоками по 4096 символов, я просто жду появления стоп-фразы.
С задержкой была проблема: когда ты прорешивал все 10 задач,
то сервер отправлял тебе флаг и закрывал соединение, поэтому
мы просто не могли получить флаг. Функция nc.recv_all
, которая
может в таймауты, почему-то не возвращала содержимое сообщения,
поэтому такой вот костыль.
Парсинг кода
#blocks = {} # индекс: {символ_перехода: значение_блока,
# символ_перехода: значение_блока,
# value: val }
def gen_block(ans):
blocks = {}
lines = ans.split("\n")
i=0
while i!= len(lines):
line = lines[i]
print(line)
if "struct state **a = malloc(sizeof(struct state *) * " in line:
print("### Init struct state a")
str_len = int(line.split()[-1].replace(");", ""))
while " " == line[0]:
line = line[1:]
if line.startswith("b = ") or line.startswith("int b = "):
print("### Block init")
b = int(line.split()[-1].replace(";", ""))
i+=1
#print("## pl", lines[i])
print("## BA", b, lines[i])
___ = lines[i].replace(" ", "").replace("b+=", "").replace(";", "")
#exec(lines[i])
b+= int(___)
print("## BB", b)
i+=1
a_value = int(lines[i].split()[-1].replace(";", ""))
print("## a_val", a_value)
blocks.update({b:{"value": a_value}})
i+=3
while "create_trans" in lines[i]:
tt = lines[i].replace("create_trans(tptr, ", "").replace("]);++tptr;", "").split()
t_sym = tt[0][1]
t_b_offs = b+ int(tt[-1])
print("Create trans",t_sym, t_b_offs)
blocks[b].update({t_sym: t_b_offs})
i+=1
i+=1
return blocks
Непростая схема парсинга кода на сях, костыльно-велосипедная методология функционального программирования. Если ты, @user, знаешь, как это можно упростить, при этом затратив на это полчаса, как и в этом варианте - стучись.
Нахождение пути до нужного состояния.
# распространение
# string: cur_index
def wave(blocks):
strs = {"":blocks[0]}
while 1:
strs_temp = {}
for strr, dictt in strs.items():
#print(strr, dictt)
if dictt["value"] == target_value:
print("OK", strr)
return strr
for symbol, index in dictt.items():
if symbol == "value":
continue
if len(strr)>3 and symbol == strr[-1] == strr[-2]:
continue
strs_temp.update({strr+symbol: blocks[index]})
strs = strs_temp
Вот такой обход в глубину. Строка с прекращением ветки, если последние три символа одинаковы, нужна для теоретического случая, когда один из переходов состояния ссылается на то же самое состояние.
Если ты знаешь модуль, который сделает этот волновой обход гораздо более лаконичным, то отзовись)
Итог таска
Большая часть времени была уделена написанию парсера и разбору проблем, связанных с получением флага: из-за задержки перед получением ответа сокет закрывался раньше того момента, как я получал флаг.
Таски на реверс за авторством @mrvos (Vlad Roskov)
reverse, 150 баллов > Scratch
Решил обучать свою шестимесячную дочку ревёрсу:
https://scratch.mit.edu/projects/366017349/
Докажите, что вы круче неё
Проект на языке программирования с помощью блоков Scratch.
Нас встречает вот такая медузка, спрашивающая флаг.
Дальнейший разбор находится здесь. Да, попробуйте поковырять его самостоятельно, он в действительности приносит много опыта а разборе алгоритмов.