13 марта 2014

RUCTF 2014 QUALS - HARDWARE 100

Название задания: IR dump.
Описание задания: Какой номер кредитной карты был введен?
Ход решения:


Сначала открываем предложенный txt файл и смотрим его содержимое. Первые символы RMC364GY. Далее идет много нулей. Пролистав файл по-быстрому замечаем, что где-то в середине файла присутствуют очень длинные последовательности единичек. То есть HEX декодирование отпадает. Гуглим первые символы в файле, и вместе с названием задания понимаем что нам представлен лог работы устройства под названием: Infrared Remote RM C 364 GY - универсальный инфракрасный пульт управления устройствами (обычно телевизором). Понимаем что декодировать придется ИК сигнал, который шел от пульта к, для определенности, телевизору. Первым делом ищем в гугле варианты кодирования ИК сигнала и находим 3способа кодирования:

1) Двухфазная модуляция;
2) Модуляция длительностью пауз;
3) Модуляция длительностью импульса.

Путем нехитрых логических умозаключений приходим к выводу, что кодируется сигнал длительностями импульса (к тому же это самый простой способ и в случае неудачи мы всегда можем попробовать оставшиеся два).
Поясню принцип кодирования: работа начинается с подачи длинного сигнала 1 (здесь и в дальнейшем под "1" и "0" понимаем соответственно логические 1 или 0). После этого кодирование происходит таким образом. Если сигнал "0" и сигнал "1" одинаковой длины, то значит кодируется "0". Если сигнал "1" длиннее чем сигнал "0", то кодируется "1".
Проверяем наше предположение - пишем простенький парсер для dump.txt
def parse_dump(dump):
    result = {}
    first = 0
    last = 0
    i = 0 #Номер сигнала по порядку
    while first != -1: #Если не найдена последняя единичка выходим
        i += 1
        first = last
        first = dump.find('1', first) #начало последовательности единиц
        last = dump.find('0', first) #начало последовательности нулей
        ones = last-first #Вычисляем длину последовательности единиц
        first = dump.find('1', last) #следующую последовательность единиц
        zerous = first - last #Вычисляем длину последовательности нулей
        result[i] = (ones, zerous)
    return result

dump = open('dump.txt', 'r').read()
result = parse_dump(dump)
open('parsed_dump.txt', 'w').write(str(result))

Смотрим на вывод парсера и понимаем, что наше предположение оказалось верно. Причем, видимо для облегчения задачи, количество единиц либо немного больше чем количество нулей, либо намного (в 1.5-2 раза) меньше. Соответственно в первом случае кодируется "0" (на рисунке выделено желтым), во втором "1" (выделено зеленым). Далее замечаем что через определенные промежутки идет очень большое количество нулей (выделено красным). Также легко заметить, что каждый 18 элемент имеет большое количество нулей, а между ними кодируются "0" и "1". Дописываем парсер для переведения сигналов в биты:
def convert_to_bit(in_dict):
    result = {}
    for i,j in in_dict.items():
        if j[0] > j[1]:
            result[i] = 0
        elif j[1] > j[0]:
            if j[1]>2000:
                result[i] = "########"
                continue
            result[i] = 1
    return result

out_dict = convert_to_bit(result)
open('sequense_bits.txt', 'w').write(str(out_dict))
Снова лезем в интернет, на этот раз мы ищем любую документацию по кодированию сигналов для IR JVC (именно этой фирме принадлежит данная модель, о чем мы узнаем после первого этапа). Находим pdf-файл в пару страниц, который подтверждает наши предположения по кодированию и кроме того указывает что пакеты данных кодируются особым образом: сначала идет байт адреса, потом байт данных и в конце 1 бит контроля (именно с ним мы и видим "в паре" огромное количество нулей). Пришло время перевести полученные нолики и единицы в байты. Дописываем парсер, в качестве разделителей берем этот самый последний бит данных.
Сразу хочу отметить, что несовершенство нашего метода декодирования может слегка искажать некоторые из пакетов, что в конечном итоге не должно помешать успешному декодированию, однако если читатель желает, он может усовершенствовать представленный код.
def convert_to_byte(in_dict):
    bytes = "".join([str(i) for i in in_dict.values()])
    bytes = bytes.split("########")
    result = "\n".join([str(hex(int(i, 2))) for i in bytes])
    return result

encoded_str = convert_to_byte(out_dict)
open('encoded_str.txt', 'w').write(encoded_str)
Чтобы расшифровать коды долго и упорно ищем разные варианты кодирования сигнала пультами JVC и убеждаемся, что ни один из вариантов не подходит! Хочу отметить, что я пробовал очень много вариантов преобразования последовательностей нулей и единиц в исходном файле, но ни один из них не был похож на кодированный сигнал. В тоже самое время если открыть кодированный сигнал, который мы получили на предыдущем этапе, и выделить некоторые пакеты, то увидим, что они повторяются в коде достаточно часто. Наши коды вполне похожи на настоящие.
В конце концов мне удалось найти подходящий файл именно для этой модели пульта, а их, как оказалось, две модификации! (см. картинки выше). Здесь в XML форме описаны коды для кнопочек. Снова переписываем наш парсер, так чтобы он подставил вместо известных кодов названия кнопок.
def convert_to_code(in_str):
    codes = {
    "TVChannelDown":"c2c0",
    "TVChannelUp":"c2b0",
    "TVRed":"c060",
    "TVVolumeMute":"c038",
    "TVVolumeDown":"c0f8",
    "TVVolumeUp":"c078",
    "TVChannel1":"c084",
    "TVChannel2":"c044",
    "TVChannel3":"c0c4",
    "TVChannel4":"c024",
    "TVChannel5":"c0a4",
    "TVChannel6":"c064",
    "TVChannel7":"c0e4",
    "TVChannel8":"c014",
    "TVChannel9":"c094",
    "TVChannel0":"c004",
    "TVPower":"c0e8",
    "TVSource":"c208",
    "TVPicture":"c288",
    }
    for key,val in codes.items():
        in_str = in_str.replace(val, key)
    return in_str

result = convert_to_code(encoded_str)
open("decoded_str.txt", 'w').write(result)
Запускаем и видим, что не все коды преобразовались. В чем же дело?

Дело в том, что существует погрешность передачи, соответственно каждая нажатая кнопка передается целой пачкой однотипных пакетов, некоторые из которых повреждаются. Соответственно нам достаточно выбрать только те кнопки, которые мы видим, а вместо пропусков подразумеваем те же коды, только искаженные. Переписываем полученный код кредитки 48524840949592 и снова проблема! Код кредитной карты, во всяком случае современной, должен состоять из 16 цифр, а у нас всего 14. На всякий случай пробуем отослать флаг, и естественно код не подходит. Дальше подключаем воображение, и думаем, что же будет, если подряд будут идти одинаковые цифры? Правильно! Их код одинаковый и будет большей длины, чем код соседних символов. Проверяем наше предположение и видим, что первая четверка и последняя девятка немного длиннее, чем соседние символы. Подставляем, проверяем - готово!
flag:4485248409495992
//solved by gek0n