Flare-On 2019. Решение. Задача №7.

Flare-On 2019. Решение. Задача №7.

https://t.me/hacker_sanctuary

Подробнее про Flare-On.

Flare-On - ежегодный конкурс, который проводит компания Fire-eye. Конкурс включает в себя 12 задач на обратную разработку (reverse) и чаще всего в нём принимают участия ревёрс-инженеры/вирусные аналитики/исследователи и все остальные специалисты, кто так или иначе связан с ковырянием в бинарном коде.

Конкурс проводится с 2014 года. В этом году он также проводился (в конце августа-начале сентября). И хотелось бы разобрать задания с данного конкурса поочерёдно. Официальный разбор есть на сайте компании Fire-eye (https://www.fireeye.com/blog/threat-research/2019/09/2019-flare-on-challenge-solutions.html), а также разбор на русском от a1exdandy на хабре (https://habr.com/ru/company/dsec/blog/469393/)

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

Предыдущие решения.

Задача №1 - ссылка

Задача №2 - ссылка

Задача №3 - ссылка

Задача №4 - ссылка

Задача №5 - ссылка

Задача №6 - ссылка

Задача №7 - wopr

Распаковываем архив и видим 2 файла (исполняемый и описание задания).

Прочтём описание.

Как обычно, некоторые предыстории и прелюдия и задача верно ввести код.

Предоставленный нам исполняемый файл представляет собой PE 32bit. Можно запустить его и посмотреть, что он ожидает и что выводит.

Запускает он относительно долго (несколько секунд, а не моментально) и выводит сначала строчку "LOADING..." после чего некоторый баннер и предоставляет что-то вроде консоли.

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

Введём случайное название и увидим, что для запуска у нас запрашивается некоторый код, судя по всему его и надо ввести верно.

Отлично, теперь мы примерно знаем, что надо искать, загрузим файл в IDA и начнём анализ.

Сразу при просмотре функции main в глаза бросается строчка "_MEIPASS2". По опыту прошлых ревёрсов и решений задачек можно сказать, что этот исполняемый файл скорей всего представляет собой самораспаковывающийся архив, возможно собранный из Python скриптов. Попробуем запустить binwalk на файл.

Множество zlib упакованных данных, это часто бывает при упаковке большого количества скриптов. Попробуем распаковать всё в автоматическом режиме с помощью того же binwalk'а (binwalk -e wopr.exe).

Получаем 198 файлов, различного размера и содержания. В таких случаях лучше посмотреть сначала файлы небольшого размера и отсеять их, если они не содержат чего то полезного.

Таким образом почти сразу можно понять, что этот исполняемый файл действительно был собран из Python скрипта(ов).

Для того, чтобы проще было получить все модули, которые были вкомпилированы в программу, можно отсеять остальные файлы по первым двум байтам "MZ" в начале файла (эти байты обозначают исполняемый файл или динамическую библиотеку под Windows).

В итоге у нас останется 146 файлов, самый крупный из которых является ZIP-архивом различных *.pyc модулей, поэтому его мы можем тоже отмести. На текущий момент наша задача определить именно пользовательский код.

Просматривать все модули и бегло анализировать их глазами в 16-ричном редакторе - не такая уж и плохая идея, однако их всё равно прилично, поэтому вначале стоит попробовать сделать grep по строчкам, которые мы видим при выполнении программы. К сожалению ни одна строчка не была найдена, после чего пришла идея того, что код может быть зашифрован и изначальное сообщение "LOADING..." каким то образом его расшифровывает. Поэтому попробуем сделать grep по этой строке.

Посмотрим на этот файл в 16-ричном редакторе и сразу увидим какие-то странные цитаты и после них множество точек и пробелов.

Долистав до конца дампа можно увидеть используемые модули и среди них сразу бросается в глаза "lzma". Возможно что-то распаковывается в процессе выполнения кода.

Попробуем декомпилировать данный модуль с помощью uncompyle6, но для начала ему нужно поставить заголовок, характерный для *.pyc файлов.

Примерно так

Запускаем декомпилятор.

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

А также 2 функции и блок main.

Функция fire очень сильно похожна на алгоритм RC4.

В итоге мы видим внизу очень интересный код, однако странно выглядит помещение print в блок try-except. На самом деле если взглянуть на код ниже и немного разобрать его.

То можно понять, что a и b представляют собой десятичные значения 16-ричной записи строк "exec" и "print". На самом деле print заменяется на exec в этом участке кода. Таким образом в конце вызывается функция exec от какого-то расшифрованного блоба.

Давайте разберёмся что же всё таки попадает на расшифровку функции RC4 (fire).

Итак мы берём заголовок полученного после декомпиляции файла (в нём содержатся различные цитаты и символы \x09 и \x20 (пробел)) и передаём его на вход функции eye. Данная функция каким то образом преобразует эти строки в бинарную последовательность. После этого мы передаём полученный поток данных вместе с ключом в функцию RC4, при этом первый байт ключа перебирается, а оставшаяся часть ключа (BOUNCE) получается из некоторого файла, который должен находиться по пути this/key.

Основная проблема в том, что мы распаковывали бинарный файл с помощью binwalk'а, а он не сохраняет оригинальные имена, поэтому мы не знаем, какой файл является ключом.

Полистав файлы, можно найти подозрительный файл, который выглядит как зашифрованный и его размер составляет 256 байт.

Можно предположить, что это и есть файл ключ.

Подменим содержимое BOUNCE этим файлом и запустив полученный скрипт мы получим надпись "LOADING...." и ничего больше происходить не будет, через какое-то время скрипт завершит выполнение.

Т.к. мы подставили содержимое файла key напрямую, то попробуем тоже самое сделать и с __doc__. Вытащим его руками из *.pyc файла и поместим в отдельный файл.

Вытаскиваем начинаю от сюда (изображение ниже)

И заканчивая тут (изображение ниже).

Теперь подставим содержимое файла вместо __doc__ и закомментируем замену функций.

Комментируем замену функций, чтобы получить print когда расшифровка пройдёт успешно.

И запускаем скрипт.

Получаем вывод на экран нового кода.

Сохраним его в отдельный файл и начнём анализ.

Полученный скрипт гораздо больше по объёму кода, чем прошлый, поэтому будем анализировать его по частям. Основной момент, который нам интересен это получение флага. Оно реализовано путём расшифровки некоторой последовательности с помощью RC4 и ключа, которым является код, вводимый пользователем.

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

Результаты сохраняются в списке b и он сравнивается со списком h, который генерируется кодом выше.

То есть мы знаем все 16 результатов и имеем 16 уравнений, то есть мы сможем верно решить эту систему (это следует из математики). Можно заметить, что список h изначально инициализируется результатом функции wrong.

Эта функция возвращает md5 хэш от некоторых данных.

В начале функция получает указатель на текущий исполняемый модуль, после чего производит на основе него ряд операций. В целом код похож на разбор PE-заголовка. Самый просто способ в данном случае, попробовать запустить оригинальный исполняемый файл (который был дан по задания) и сдампить секции. Это можно сделать в x32dbg, просто перейдя на точку входа в программу и сдампив все секции исполняемого файла.

Для начала можно попробовать садмпить первую секцию и посмотреть как поведёт себя скрипт.

Поменяем некоторый код в скрипте.

Теперь запустим и посмотрим, что он выведет.

Отлично, теперь мы знаем, что скрипту интересна только секция '.text', а это секция кода. Второе значение это размер секции, а третье это смещение. Можно предположить, что md5-хэш рассчитывается от секции кода, однако далее идёт некоторая обработка данной секции и судя по всему на выходе получается не совсем то, что находится в секции .text.

На самом деле, если разобраться чуть лучше, то станет понятно, что также обрабатывается секция .reloc поэтому нужно сдампить весь файл и немного модифицировать код функции wrong() чтобы она работала.

В итоге получается что-то такое.

Теперь мы можем получить значения списка h и решать систему линейных уравнений. Запустим скрипт и получим значения h.

Отлично, теперь просто воспользуемся библиотекой Z3 для решения уравнений.

Запускаем и почти сразу получаем решение.

Преобразуем всё в ASCII-символы.

В итоге получаем верный ключ для RC4 и нам остаётся только расшифровать его.

И флаг получен.

Задание было довольно интересным и очень понравилось, что оно состояло из нескольких этапов.

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

В целом задание хорошее, рекомендуем попробовать решить его (даже решение по описанию будет полезно, т.к. вы повторите действия и закрепите некоторые методики).

Report Page