CTFzone write-ups – Shall I reverse it?

в 15:06, , рубрики: bizone, ctf, ctfzone, zeronights, Блог компании BI.ZONE, информационная безопасность

image

Друзья, бурные выходные прошли, и мы готовы представить вам новую партию райтапов – на этот раз мы подробно разберем задания ветки Reverse. Надеемся, вы уже разобрались с двумя заданиями из OSINT и готовы полностью погрузиться в процесс реверс-инжиниринга. Обещаем, будет интересно ;)

Это направление имело большую популярность среди участников — одно только задание на 100 решили 103 человека. Однако, таск на 1000 так и остался нерешенным. Поэтому, как и в случае с OSINT, райтап на самое сложное задание CTFzone будет опубликован несколько позже в отдельном посте. А сейчас бросайте все свои дела, и полный вперед!

Reverse_50. Console version 1.337

A.U.R.O.R.A.: Lieutenant, you are standing in the Alpha base in front of the SCI430422 mainframe art console where its sixty-four LED lights are blinking in hypnotic patterns. As you know, this system is renowned for its top-notch security measures. Only the most expert or resourceful hackers are able to break in — and you are definitely one of them.

Решение:

В этом задании нам нужно было попасть в систему консоли. Для начала запускаем файл на исполнение и видим окно с приветствием и предложением ввести пароль:

image

Что же делать? Открываем файл в отладчике OllyDbg и находим строчку «Please enter password:» — в основном окне пролистаем листинг вверх, до адреса 004010F9:

image

Как вы можете видеть, проверка введенного пароля осуществляется в функции, расположенной по адресу 00401000. Попробуем поставить на этот адрес брейкпоинт (клавиша F2 в OllyDbg), затем нажмем F9 (продолжить исполнение) и для примера введем какую-нибудь строку в окне программы:

image

Нажимаем «Enter», и происходит срабатывание брейкпоинта. Далее переходим в OllyDbg и нажимаем клавишу F7, чтобы перейти внутрь функции по адресу 00401000:

image

Сразу можно заметить, что по адресу 004010CF производится вызов функции strcmp() из стандартной библиотеки C. Эта функция сравнивает две строки и возвращает 0, если эти строки одинаковы. Ставим брейкпоинт на вызов функции (клавиша F2), нажимаем F9 (продолжить исполнение) и смотрим, какая еще строка, кроме введенной нами «password123», будет в нее передаваться:

image

В окне стека (и в окне регистров) мы видим, что строка s1 равна «ctfzone{l33t_haxx0r_is_you!!1}» (без кавычек).

image

Вот и флаг!

Ответ: ctfzone{l33t_haxx0r_is_you!!1}

Reverse_100. The Doors of Dorun

*A.U.R.O.R.A.: Lieutenant, your co-pilot was abducted by aliens and put into prison. They are out hunting now and it’s your chance to set him free! He is held behind the Doors, the jambs invisible to the eye, and matched so perfectly with the metal bulkhead that when closed the Doors could not be seen.

The inscription on the archivolt read:

"The Doors of Dorun, Lord of Omega. Speak, friend, and enter. I, Norvy, made them. Calabrimbor of Alpha Centauri drew these signs".

But be careful and hurry up. They can be back any moment.*

image

Решение:

В этом задании нам нужно было подобрать пароль к вратам, за которыми держали в плену нашего второго пилота. В первую очередь мы запускаем CrackMe, и на экране появляется следующее окно:

image

Попробуем ввести любое слово и нажать «Try», но пароль не подходит, и мы видим такое сообщение:

image

Сам по себе CrackMe представляет собой 64-битный исполняемый файл PE формата. Откроем его в IdaPro и попробуем найти строчку «the door is still closed!»:

image

На эту строчку существует только одна перекрестная ссылка:

image

Вот функция, в которой IdaPro нашла обращения к этой строчке:

image

Здесь мы также видим зашифрованный флаг (легко убедится, что функция sub_140001160 занимается дешифровкой) и функцию, определяющую правильность пароля: «sub_1400012C0». При помощи GetDlgItemTextW в эту функцию передается строка, введенная в поле для ввода пароля. Проанализируем эту функцию:

image

Здесь прослеживается цикл и два массива из пяти элементов. Также происходит проверка длины введенного пароля:

image

Проанализировав эту функцию, мы видим, что пароль из четырёх символов в кодировке UTF-16 (кодировка для WideChar в Windows) состоит из двух чисел c размером DWORD. Далее мы видим, что остатки от деления этих чисел на числа ((1 << (1 << i)) + 1) сравниваются с захардкоженными значениями.

Можно заметить, что ((1 << (1 << i)) + 1) = 2^(2^i) + 1, и что это числа Ферма: 3, 5, 17, 257, 65537. Алгоритм проверки пароля далее можно свести к двум системам сравнений:

X1 % 3 = 0
X1 % 5 = 0
X1 % 17 = 1
X1 % 257 = 241
X1 % 65537 = 995

X2 % 3 = 1
X2 % 5 = 4
X2 % 17 = 6
X2 % 257 = 104
X2 % 65537 = 413

Восстановить исходные числа нам поможет Китайская Теорема об Остатках. В интернете можно найти решатели таких сравнений:

image

image

Итак, мы получили два числа, которые теперь необходимо преобразовать в строку UTF16. Для этого можно использовать Python (при этом не забываем про обратный порядок байт):

image

Теперь осталось проверить полученный результат. Введем эту строку в окно для ввода пароля.

image

image

Вот и все! Мы открыли врата.

Ответ: ctfzone{ch1n4_t0wn}

Reverse_300. Python's urn

A.U.R.O.R.A.: Lieutenant, the Doors are open but there is one more lock behind them. You will find the key in the Japanese vase, but be careful – don’t wake this sleeping python up otherwise we won’t get out.

Решение:

После того, как мы открыли врата, оказалось, что перед нами следующая дверь. Новое задание – новый ключ :) Поехали!

Запускаем файл на исполнение, вводим ключ, но ничего не выходит.

image

Попробуем разобраться. Прежде всего следует выяснить, с чем мы вообще имеем дело. Чтобы определить тип исполняемого файла, можно воспользоваться CFF explorer:

image

Очевидно, что .Net Assembly, Ida и традиционные отладчики в данном случае бессильны. В этот момент можно было вспомнить про dnSpy — один из инструментов, которые могут помочь в исследовании кода, использующего .Net. Загружаем в него исполняемый файл.

image

По названию основного класса (PythonMain) и названию ресурсов можно сделать вывод, что эта программа была написана на IronPython. В основной функции происходит лишь подгрузка .NET сборки из ресурса «IPDll.WFCrackMe». Придется извлечь ресурс с этим именем и загрузить его в dnSpy.

image

Здесь все гораздо интересней, есть даже названия функций. Сразу предположим, что функция с названием «verifyPassword» проверяет введенный нами пароль, и чтобы убедиться в этом, достаточно протестировать в отладчике, какая строка будет передана ей в качестве аргумента.
Далее нам необходимо понять, как проверяется пароль. Самая большая проблема при анализе кода – это разобраться с функционированием «strongBox» и «globalArrayFromContext». Эти переменные на самом деле заполнялись в функции «__main__».

Рассмотрим процесс реверсинга verifyPassword на примере небольшого куска кода с ветвлением:

if ((arg = (CallSite<Func<CallSite, object, int, bool>>)strongBox.Value[37]).Target(arg, (arg2 = (CallSite<Func<CallSite, CodeContext, object, object, object>>)strongBox.Value[38]).Target(arg2, globalContext, globalArrayFromContext[26].get_CurrentValue(), password), 81))
            {
                num = 73;
                num = 73;
                result = globalArrayFromContext[16].get_CurrentValue();
            }

В первую очередь нам необходимо подставить значения на места «strongBox» и «globalArrayFromContext». Значения берем из следующих строк «__main__»:

strongBox.Value[38] = CallSite<Func<CallSite, CodeContext, object, object, object>>.Create(PythonOps.MakeInvokeAction($globalContext, new CallSignature(1)));
strongBox.Value[37] = CallSite<Func<CallSite, object, int, bool>>.Create(PythonOps.MakeComboAction($globalContext, PythonOps.MakeBinaryOperationAction($globalContext, ExpressionType.NotEqual), PythonOps.MakeConversionAction($globalContext, typeof(bool), 1)));

Если нет уверенности в правильности полученного для «strongBox» значения, его можно проверить в отладчике. Подставим значения в исследуемый участок кода и упростим его:

if (NotEqual(len(password), 81))
    {
        result = false;
    }
 Или 
if (len(password) != 81)
        result = false;

Таким образом мы выяснили, что длина пароля должна быть равна 81 символу. Дальнейший анализ функции затруднителен, т.к. функция довольно большая, но, благодаря поверхностному анализу и отладке, мы сможем сделать несколько выводов:

  1. При дальнейшем анализе мы сразу заметим таблицу 9x9 из 81 числа.
  2. При попытке ввести в качестве пароля строку из 81 символов, содержащую не только цифры, мы попадем на обработку исключения: «IronPython.Runtime.Exceptions.ValueErrorException: Invalid integer literal». Следовательно, необходимо вводить только цифры.
  3. Также одно из ветвлений укажет нам на то, что все цифры, кроме 0, должны совпадать с цифрами из массива.
  4. Нулевые ячейки таблицы заполняются соответствующими ячейками во введенной строке.

Далее мы попадаем в ветвление со страшным, на первый взгляд, условием:

if ((arg49 = (CallSite<Func<CallSite, object, bool>>)strongBox.Value[59]).Target(arg49, (!(arg50 = (CallSite<Func<CallSite, object, bool>>)strongBox.Value[60]).Target(arg50, obj9 = (arg51 = (CallSite<Func<CallSite, CodeContext, object, object, object>>)strongBox.Value[61]).Target(arg51, globalContext, globalArrayFromContext[11].get_CurrentValue(), obj))) ? obj9 : ((!(arg52 = (CallSite<Func<CallSite, object, bool>>)strongBox.Value[62]).Target(arg52, obj10 = (arg53 = (CallSite<Func<CallSite, CodeContext, object, object, object>>)strongBox.Value[63]).Target(arg53, globalContext, globalArrayFromContext[12].get_CurrentValue(), obj))) ? obj10 : (arg54 = (CallSite<Func<CallSite, CodeContext, object, object, object>>)strongBox.Value[64]).Target(arg54, globalContext, globalArrayFromContext[13].get_CurrentValue(), obj))))

После подстановки соответствующих значений и упрощения это условие превращается в простую проверку:

if (BoolConvert((!BoolConvert(obj9 = CheckLines(obj))) ? obj9 : ((!BoolConvert(obj10 = checkColumns(obj))) ? obj10 : CheckSquares(obj))))
Можем убрать лишние BoolConvert и раскрыть конструкцию «a?b:c». Получается еще проще:
if(CheckLines(obj) && СheckColumns(obj) && CheckSquares(obj))

Нам нужно попасть на истинную ветку данного условия, т.е. все функции должны вернуть «True».
Теперь проанализируем функцию CheckLines. Она довольно простая — функция проверяет, что в каждой строке встречаются цифры от 1 до 9. Функции СheckColumns и CheckSquares посложнее, но на этом этапе уже можно догадаться, что мы имеем дело с Судоку:

image

В интернете можно найти массу решателей, так что вы можете поупражняться самостоятельно ;)

Итак, воспользовавшись решателем, мы получаем искомый ключ.

image

Дверь открыта!

Ответ: ctfzone{1_v3ry_l1k3_5ud0ku_9arm3!}

Reverse_500. Bridge repair

A.U.R.O.R.A.: Lieutenant, watch your step! There is a pit infested with worms down the road. There is a bridge over the pit but it’s in ruins and you can restore it only in the same way it was destroyed. I’ve got one worm in the quarantine, go for him and repair the bridge. Hurry up, we have to save our pilot!

Решение:

Итак, в этом задании необходимо спасти пилота, однако добраться до него можно только преодолев разрушенный мост. Нам нужно его восстановить. В качестве «моста» предлагается файл «Bridge.txt».

Открыв файл «Bridge.txt» в шестнадцатеричном редакторе, мы можем убедиться, что он зашифрован. Из описания задания понятно, что нам предоставили программу, которая зашифровала этот файл, и нам необходимо его дешифровать.

Далее проверим, что не так с файлом «reverse500.exe». Запускаем файл, и в ответ на консоли появляется сообщение — «you must specify the file for encryption»:

image

С помощью перекрестных ссылок поищем использование этой строчки в исполняемом файле. Для этого откроем файл в компиляторе IdaPro. Очевидно, что шифруется файл, передаваемый в качестве аргумента командной строки.

image

Из приведенного фрагмента кода видно, что шифрование осуществляет функция 401F60.
Поверхностно исследовав данную функцию, мы можем выделить функции выделения памяти, чтения файла, шифрования содержимого файла и записи зашифрованного содержимого в исходный файл:

image

Далее попробуем понять структуру зашифрованного файла, изучив простенькую функцию «WriteCryptedData»:

image

Конечно, можно определить тип используемого хеша, но это необязательно. Тем не менее, для дальнейшей работы нам необходимо понять, как ведется подсчет хэш-суммы. При подробном рассмотрении функции «HashCalculate» становится очевидно, что при подсчете хеша используется 8 байт вектора «IV».
Итак, мы выяснили, что зашифрованный файл имеет следующую структуру:

image

Посмотрим на файл «Bridge.txt»:

image

Далее определим последний байт вектора «IV». Для этого откроем программу в отладчике, указав в качестве аргумента любой файл, и поставим брейкпоинт на адрес функции «WriteCryptedData». После чего, предварительно немного поправив ассемблерный код, организуем перебор последнего байта вектора «IV». Результат перебора показан на скриншоте:

image

Теперь у нас есть полный вектор «IV»: [47 08 8F E7 C4 C0 E9 AB].

Далее необходимо проверить режим шифрования и симметричность используемого шифра.
Вернемся к функции «CryptData» в отладчике, подав на вход программе тестовый файл (в моем случае файл содержал строку «HelloWorld!»). Далее запишем вектор «IV» (находится в EAX перед вызовом CryptData) и результат шифрования (находится в EAX после вызова CryptData).

image

Проверим идентичность процедуры дешифрования и шифрования. Для этого перезапустим программу под отладчиком и снова поставим брейкпонт на «CryptData». Только теперь перед ее исполнением поправим вектор «IV» и шифруемый буфер.

image

В результате повторного шифрования зашифрованных данных мы получим исходный текст!

Теперь можно расшифровать файл из задания. Для этого удалим из него первые 15 байт и запустим программу под отладчиком, указав в качестве параметра файл с зашифрованными данными. Вновь поставим брейкпоинт на «CryptData» и перед вызовом функции исправим вектор «IV» на [47 08 8F E7 C4 C0 E9 AB]. После исправления вектора «IV» нажимаем клавишу F9, в результате чего программа зашифрует данные. Из результирующего файла удалим первые 15 байт и откроем его в текстовом редакторе:

image

Флаг найден!

Ответ: ctfzone{3RR4dIC473_7HIS_WORM!}

P.S. Для тех, кому интересно, мы использовали в этом задании алгоритм шифрования Salsa и алгоритм хэширования CubeHash. Но это может заметить только опытный глаз ;)

Кажется, теперь все встало на свои места. Если у вас есть какие-то вопросы или пожелания – пишите в наш чат в телеграме и оставляйте комментарии. А новые познания в области реверс-инжиниринга можно продемонстрировать по этой ссылке — задания будут доступны до 15 декабря.

Всем удачи и до новых встреч!

Автор: BI.ZONE

Источник

Поделиться новостью

* - обязательные к заполнению поля