Всё о CTF в России

Task-bot 2020: подведение итогов

2020-07-28 00:00:00

Кажется, будто предыдущие пять недель пролетели со скоростью проголодавшегося гепарда, заметившего сочную антилопу где-то в мавританских тропиках. Оно и понятно: в этом году таски в нашем боте шли по нарастающей сложности. Сначала участников ждали 2 недели довольно лёгкой криптографии от Максима Смирнова, затем, ближе к экватору, температура немного накалилась: в качестве разработчика впервые предстал Юра Гришин с заданием на реверс. На четвёртой неделе пик сложности уже начал зашкаливать благодаря веб-таску Владимира Черепанова. Финальный отрезок выдался и вовсе убийственно-тяжелым: задание на реверс от Георгия Кигурадзе осталось без решений.

В этом году за путёвку на Летнюю школу CTF пытались побороться 129 человек, из которых с ненулевым результатом аж целых 41 человек. Трём участникам удалось решить 4 таска из 5, и этими красавчиками стали:

  1. R3seh 1000
  2. herrlestrate 1000
  3. W0lFreaK 1000

Ну, а теперь настало время узнать, как всё же решался последний таск.

Введите описание изображения

Описание: С последнего обновления прошивки, прошло не так много времени, и мы заметили существенное замедление работы и странное поведение, также у нас стали появляться различные баннеры при сёрфинге сайтов и приходит разный спам. Кажется, что к нашему роутеру у кого-то ещё есть доступ, можешь проверить последнюю прошивку и понять, что не так с роутером?

Предоставляемые файлы: FW2020_03_12_ENB12F32T5.bin (файл прошивки роутера)

Решение: Исходя из описания задачи, нам нужно понять, не установлен ли какой-нибудь бэкдор на роутере для удалённого доступа к нему.

Получая прошивку, сразу попробуем проанализировать её с помощью утилиты binwalk, которая находит различные бинарные паттерны в любом контейнере данных.

Введите описание изображения

Определяется только некоторый пожатый блоб, который лежит по смещению 0x100 относительно начала файла, если распаковать его, то станет понятно, что это ядро Linux.

На данном этапе нужно посмотреть, какой объём занимает сжатое ядро внутри файла, и найти ещё одну секцию в файле, которая начинается со смещения 0x080100.

Введите описание изображения

Здесь располагаются нечитаемые данные и не совсем понятно, к чему они приведены. Для понимания назначения этих данных нам нужно проанализировать заголовок прошивки.

Введите описание изображения

Первые 16 байт похожи просто на заглушку, также байты с 0x80 по 0x100 тоже не имеют значения, а с 0x100 начинается уже сжатое ядро. Поэтому, нам нужно сосредоточить внимание только на выделенных красным прямоугольником данных.

Посмотрим на записи подробно и держа в голове смещение, по которому у нас лежат неопознанные данные.

Введите описание изображения

Из небольшого логического анализа можно понять, что записи описывают некоторые блоки в прошивке, в частности, первая запись описывает блок сжатого ядра. Первые 4 байта (выделенные зелёным цветом) обозначают смещение по которому начинается блок, вторые 4 байта (выделенные красным цветом) определяют размер блока, последующие 8 байт служат для идентификации (можно сказать, представляют собой название блока).

Таким образом обработав все 7 записей, получаем следующую таблицу:

Введите описание изображения

Становится понятно, что последним блоком является файловая система для роутера, но, если взглянуть на её начало, то никаких магических констант для известных алгоритмов упаковки мы не увидим, так же, как и констант файловых систем. Можно предположить, что этот блок зашифрован. Наше предположение может быть подкреплено и тем, что мы видим в конце файла:

Введите описание изображения

Большое количество одинаковых блоков по 16 байт может свидетельствовать о применении одного ключа к каждому блоку длиной 16 байт.

Беря во внимание эти данные, взглянем на описание наших секций до rootfs:

modesize – режим и размер

Введите описание изображения

0xecb08000 можно трактовать как ECB (один из возможных режимов AES) и 0x80 (128 бит – один из размеров ключа для AES а также размер блока, то есть 16 байт). sb – им может быть sbox, используемый в алгоритме AES.

Введите описание изображения

Если взглянуть на константы, а потом поискать в интернете информацию об AES, то сомнений больше не останется. Взгляните на изображение из статьи: https://ru.wikipedia.org/wiki/Advanced_Encryption_Standard

Введите описание изображения

rsb – представляет собой обратный S-box;

rcon – массив для генерации раундовых ключей.

Остаётся один блок, который не имеет описания и имеет размер 16 байт.

Введите описание изображения

Также его значения не являются никакими магическими константами и не участвуют в алгоритме AES.

На данный момент мы знаем назначение всех блоков внутри файла прошивки, кроме блока в 16 байт, приведённого выше. Можно предположить, что этот блок и является ключом для расшифровки rootfs.

Реализуем небольшой скрипт на Python, предварительно вырезав из файла rootfs, и поместив этот блок в отдельный файл.

Введите описание изображения

Запускаем скрипт и смотрим на полученный файл.

Введите описание изображения

Отлично! Мы получили расшифрованную файловую систему. Теперь распакуем её. Это можно сделать с помощью binwalk.

Введите описание изображения

Теперь приступим к анализу полученных файлов и поиску бэкдора. Один из самых простых способов — это просто взять и отсортировать все файлы по времени изменения и найти самые новые. Второй способ заключается в анализе автозагрузки, т.к. бэкдор должен переживать перезапуск системы. Поэтому можно проанализировать скрипты, которые запускаются при старте системы. Например, в скрипте /etc/scripts/config.sh, рядом с запуском веб-сервера, можно найти запуск некоторого бинарного файла /usr/sbin/bd с аргументом 1234.

Введите описание изображения

Ещё одним вариантом для определение лишнего файла в системе может служить скачивание оригинальной версии прошивки, т.к. по конфигурационным файлам можно понять, что это прошивка от роутера DIR-300.

Пути выхода на файл /usr/sbin/bd могут быть различными, так или иначе нам нужно его проанализировать.

Исполняемый файл представляет собой 32-битный ELF файл под архитектуру MIPS. Слинкован статически и не содержит символьной информации. Для более удобного анализа можно использовать GHIDRA, т.к. она может декомпозировать MIPS код.

В функции main создаётся простой TCP-сервер на порту, указанном в аргументе. Если порт указан не был, то используется значение 0xc41 (3137) в качестве порта. После определения места создания сокета и локальной переменной, в которую он сохраняется, остальные сетевые функции определяются довольно просто. В итоге получаем примерно следующий код функции main.

Введите описание изображения

Очевидно, что нас интересует взаимодействие с клиентом, поэтому продолжим анализировать только функцию ClientMainHandler. Немного просмотрев код обработчика соединения, можно понять, что сначала осуществляется некоторая аутентификация, и если она прошла успешно, то вызывается ещё ряд функций, взаимодействующих с клиентом, что в конце может привести к выполнению кода через прямой вызов указателя. В целом, это похоже на бэкдорный функционал, который позволяет пользователю выполнять любой код на системе.

Введите описание изображения

Т.к. в данной задаче нам не даётся опытный экземпляр сервера, то запускать код нам не нужно, но мы можем посмотреть этап прохождения авторизации, возможно, там мы сможем найти верный пароль для бэкдора. Посмотрим функцию Auth_mb.

Из анализа функции можно понять, что для передачи используется некоторый протокол. В частности, в начале пакета должна идти константа 0x42, после чего должен быть нуль байт, далее — размер данных полезной нагрузки.

Введите описание изображения

Как можно заметить, выполняется копирование данных со смещения 5 внутри пакета в некоторое место в памяти, где был создан буфер на 256 байт. После чего эти данные передаются в функцию CheckPassword, которая занимается проверкой пароля. По результатам проверки возвращаемое значение должно быть равно 1, иначе авторизация не пройдёт.

Теперь нам осталось понять, какой пароль верный. Это можно сделать, проанализировав функцию CheckPassword. В начале функции наш пароль декодируется из base64 (это можно понять по использованию таблицы base64 в функции декодирования), а также — некоторая странная строка преобразуется через довольно простой алгоритм.

Введите описание изображения

Посмотрим на функцию decrypt_path.

Введите описание изображения

Сначала выполняется XOR с числом 13, а потом декодирование из base64. Если выполним эти операции над строкой, то получим путь до файла /etc/config/.k

Введите описание изображения

В этом файле находятся данные, похожие на MAC-адрес.

Введите описание изображения

На самом же деле эти данные используются для первого преобразования нашего пароля. Происходит циклический XOR нашего пароля со считанной строкой из файла.

Введите описание изображения

Далее, на основе этих же данных, из файла формируется seed для инициализации стандартного рандома в Си (через вызов srand()). После чего уже преобразованный пароль ещё раз проходит через XOR, но уже со случайно сгенерированными байтами.

Введите описание изображения

В конце функции происходит проверка с заложенными в память константами, а также проверка размера пароля.

Введите описание изображения

Этапы получения пароля будут следующие:

1) Реализация алгоритма получения корректного seed,

2) Генерация необходимого количества случайных байт,

3) XOR с полученными байтами,

4) XOR с содержимым файла.

В итоге реализуем следующий код.

Введите описание изображения

Запускаем и получаем флаг.

Введите описание изображения