Write-Up от Алексея Ропана
Задача:
Universal dangerous positive: 194.226.244.125:1024. Отправь мне пароль: “3k8bbz032mrap75c8iz8tmi7f4ou00”. Формат флага — “RUCTF_.*”
Решение:
Точно не помню, но по-моему пароля сразу не было. Поэтому задача долго не сдавалась. В экспериментах удалось выяснить, что надо отправлять по протоколу UDP (потом уже обратили внимание на Universal Dangerous Positive). После третьего подхода, когда все же пароль был выложен, было получено сообщение:
Hello stranger!
You can go down(1280) with password: g5op3e6wxcy71m24dgcmhq714nd799
You can go right(1025) with password: 084399ptmy7u4bxwy003x2ko2oa06n
И тут задача показалось достаточно простой. Нужно просто ходить по лабиринту, отправляя для перехода нужный пароль. На python это было реализовано так:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.sendto(msg, (host, port)) reply, addr = sock.recvfrom(size)
Но, отправляя следующий пароль, в ответ ничего не приходило. Казалось, что это снова недоработка (как и вначале, когда пароль не был выложен). Попробовал ещё различные методы отправки сообщений через udp, но в ответ все та же тишина. И тут я обратил внимание на числа, которые указаны после направления движения (1025 и 1280). 1025 было очень похоже на начальный порт, только отличался на единицу. Ах, значит нам дается не только следующий пароль, но порт. Отлично. Немного правок в коде и был сделан первый шаг и получено сообщение:
Next step
You can go left with password: 3k8bbz032mrap75c8iz8tmi7f4ou00
You can go rigth with password: irbzlsqs1j681g84u9jq13u54265ir
О, и вправду, пойдем налево и окажемся в начальной команте (с паролем 3k8bbz032mrap75c8iz8tmi7f4ou00). Ррррр. Только тут уже не было портов и было непонятно куда переходить. Ладно, порт можно перебирать. Это первое что пришло в голову, но сразу не было оценено, и быстро написано. Следующие комнаты открывались, но очень долго. Кстати, наш сокет нужно было немножко поправить, чтобы он не ждал долго ответа:
sock.settimeout(timelimit)
0.3 секунды хватало с запасом, вроде как. Итак, двери открывались (именно так можно назвать процесс перебора порта и переход по в соседнюю клетку), но очень долго. И снова проанализировав информацию (т.е. пораскинув головой, а не руками) было замечена зависимость порта от направления движения. Когда шли вправо порт увеличивался на один, а влево уменьшался на один. Соответственно вверх и вниз порт увеличивался и уменьшался на 256. То есть у нас получался лабиринт 256х256 без первых 4 строчек (так как порты до 1024 не использовались). В итоге получили быстро бегающего по лабиринту бота, который что-то искал. Но через некоторые двери так и не удавалось пройти, даже пытаясь несколько раз. Леша объяснил это тем, что пакету udp могут теряться. Да ещё были странности такие как то, что в большинство дверей он заходил с первого раза, а в некоторые так и не смог зайти после долгих попыток. Таких двери откладывалися на потом, а пока обследовалось то, что можно было обследовать. Ясное дело, что зная порт и пароль можно было продолжать движение с любой комнаты. Поэтому вся информация сохранялась. По скорости изучения лабиринта нужно было около трех часов на все. Но было чувство, что в коде был баг, так как он почему-то оказывался в первой двери несколько раз при одном запуске. Но было уже утро. Я запустил скрипт и пошел спать. Проспал около четырех часов. Первое, что я увидел на мониторе – это то, что было обследовано 51% лабиринта и стало как-то грустно. Но и тут же было сообщение о том, что найдено необычное сообщение:
Congratulations! Flag is RUCTF_77pd9u784g059t0z18hjtn5d
Блуждание по лабиринту закончилось успешно, хоть и не удалось побывать в каждом его закоулке.
Исходный код бота:
import socket import sys import random import re import json import Queue import datetime timelimit = 0.3 class World(dict): def reopen(self): try: with open(self.filename, "r") as fo: data = json.load(fo) except: data = {} super(World, self).__init__(data) def save(self): try: dump = json.dumps(self, indent=4) with open(self.filename, "w") as fo: fo.write(dump) except Exception, error: print "ERROR SAVE", error assert False pass def __init__(self, filename): self.filename = filename self.reopen() world = World("world.json") queue = Queue.Queue() sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(timelimit) def get_port(port, move): if move == "down": return port + 256 elif move == "up": return port - 256 elif move == "left": return port - 1 elif move == "right" or move == "rigth": return port + 1 assert False, "Incorret move: " + move if world: for w in world.values(): msg, host, port = w['position'] queue.put((msg, host, port)) else: host= '194.226.244.125' port = 1024 msg = "3k8bbz032mrap75c8iz8tmi7f4ou00" queue.put((msg, host, port)) prev_time = None while not queue.empty(): curr_time = datetime.datetime.now() if prev_time == None or (curr_time - prev_time).seconds > 180: world.save() print len(world), "is", "%.3f%%" % (float(len(world)) / 2**16 * 100) prev_time = curr_time msg, host, port = queue.get() if msg in world: reply = world[msg]['reply'] else: i = 0 while i < 16: fail = False try: sock.sendto(msg, (host, port)) reply, addr = sock.recvfrom(1024) except socket.timeout: if i == 0: print "failed..." sock.close() sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.settimeout(timelimit) fail = True if not fail: break i += 1 if fail: print "FAILED", msg, port continue if i != 0: print i, "ok" result = reply.split("\n")[0] if result != "Next step": print msg, port, result world[msg] = {'position': (msg, host, port), 'reply': reply} for i in reply.split("\n")[1:]: data = i.split() directory = data[3] m = data[-1] if m in world: continue move = re.sub("([a-z]*).*", "\\1", directory) p = re.sub("[a-z]*\(([0-9]*)\)", "\\1", directory) queue.put((m, host, int(p) if p.isdigit() else get_port(port, move))) world.save()
Leave a Reply