Друзья, бурные выходные прошли, и мы готовы представить вам новую партию райтапов – на этот раз мы подробно разберем задания ветки 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.
Решение:
В этом задании нам нужно было попасть в систему консоли. Для начала запускаем файл на исполнение и видим окно с приветствием и предложением ввести пароль:
Что же делать? Открываем файл в отладчике OllyDbg и находим строчку «Please enter password:» — в основном окне пролистаем листинг вверх, до адреса 004010F9:
Как вы можете видеть, проверка введенного пароля осуществляется в функции, расположенной по адресу 00401000. Попробуем поставить на этот адрес брейкпоинт (клавиша F2 в OllyDbg), затем нажмем F9 (продолжить исполнение) и для примера введем какую-нибудь строку в окне программы:
Нажимаем «Enter», и происходит срабатывание брейкпоинта. Далее переходим в OllyDbg и нажимаем клавишу F7, чтобы перейти внутрь функции по адресу 00401000:
Сразу можно заметить, что по адресу 004010CF производится вызов функции strcmp() из стандартной библиотеки C. Эта функция сравнивает две строки и возвращает 0, если эти строки одинаковы. Ставим брейкпоинт на вызов функции (клавиша F2), нажимаем F9 (продолжить исполнение) и смотрим, какая еще строка, кроме введенной нами «password123», будет в нее передаваться:
В окне стека (и в окне регистров) мы видим, что строка s1 равна «ctfzone{l33t_haxx0r_is_you!!1}» (без кавычек).
Вот и флаг!
Ответ: 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.*
Решение:
В этом задании нам нужно было подобрать пароль к вратам, за которыми держали в плену нашего второго пилота. В первую очередь мы запускаем CrackMe, и на экране появляется следующее окно:
Попробуем ввести любое слово и нажать «Try», но пароль не подходит, и мы видим такое сообщение:
Сам по себе CrackMe представляет собой 64-битный исполняемый файл PE формата. Откроем его в IdaPro и попробуем найти строчку «the door is still closed!»:
На эту строчку существует только одна перекрестная ссылка:
Вот функция, в которой IdaPro нашла обращения к этой строчке:
Здесь мы также видим зашифрованный флаг (легко убедится, что функция sub_140001160 занимается дешифровкой) и функцию, определяющую правильность пароля: «sub_1400012C0». При помощи GetDlgItemTextW в эту функцию передается строка, введенная в поле для ввода пароля. Проанализируем эту функцию:
Здесь прослеживается цикл и два массива из пяти элементов. Также происходит проверка длины введенного пароля:
Проанализировав эту функцию, мы видим, что пароль из четырёх символов в кодировке 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
Восстановить исходные числа нам поможет Китайская Теорема об Остатках. В интернете можно найти решатели таких сравнений:
Итак, мы получили два числа, которые теперь необходимо преобразовать в строку UTF16. Для этого можно использовать Python (при этом не забываем про обратный порядок байт):
Теперь осталось проверить полученный результат. Введем эту строку в окно для ввода пароля.
Вот и все! Мы открыли врата.
Ответ: 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.
Решение:
После того, как мы открыли врата, оказалось, что перед нами следующая дверь. Новое задание – новый ключ :) Поехали!
Запускаем файл на исполнение, вводим ключ, но ничего не выходит.
Попробуем разобраться. Прежде всего следует выяснить, с чем мы вообще имеем дело. Чтобы определить тип исполняемого файла, можно воспользоваться CFF explorer:
Очевидно, что .Net Assembly, Ida и традиционные отладчики в данном случае бессильны. В этот момент можно было вспомнить про dnSpy — один из инструментов, которые могут помочь в исследовании кода, использующего .Net. Загружаем в него исполняемый файл.
По названию основного класса (PythonMain) и названию ресурсов можно сделать вывод, что эта программа была написана на IronPython. В основной функции происходит лишь подгрузка .NET сборки из ресурса «IPDll.WFCrackMe». Придется извлечь ресурс с этим именем и загрузить его в dnSpy.
Здесь все гораздо интересней, есть даже названия функций. Сразу предположим, что функция с названием «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 символу. Дальнейший анализ функции затруднителен, т.к. функция довольно большая, но, благодаря поверхностному анализу и отладке, мы сможем сделать несколько выводов:
- При дальнейшем анализе мы сразу заметим таблицу 9x9 из 81 числа.
- При попытке ввести в качестве пароля строку из 81 символов, содержащую не только цифры, мы попадем на обработку исключения: «IronPython.Runtime.Exceptions.ValueErrorException: Invalid integer literal». Следовательно, необходимо вводить только цифры.
- Также одно из ветвлений укажет нам на то, что все цифры, кроме 0, должны совпадать с цифрами из массива.
- Нулевые ячейки таблицы заполняются соответствующими ячейками во введенной строке.
Далее мы попадаем в ветвление со страшным, на первый взгляд, условием:
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 посложнее, но на этом этапе уже можно догадаться, что мы имеем дело с Судоку:
В интернете можно найти массу решателей, так что вы можете поупражняться самостоятельно ;)
Итак, воспользовавшись решателем, мы получаем искомый ключ.
Дверь открыта!
Ответ: 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»:
С помощью перекрестных ссылок поищем использование этой строчки в исполняемом файле. Для этого откроем файл в компиляторе IdaPro. Очевидно, что шифруется файл, передаваемый в качестве аргумента командной строки.
Из приведенного фрагмента кода видно, что шифрование осуществляет функция 401F60.
Поверхностно исследовав данную функцию, мы можем выделить функции выделения памяти, чтения файла, шифрования содержимого файла и записи зашифрованного содержимого в исходный файл:
Далее попробуем понять структуру зашифрованного файла, изучив простенькую функцию «WriteCryptedData»:
Конечно, можно определить тип используемого хеша, но это необязательно. Тем не менее, для дальнейшей работы нам необходимо понять, как ведется подсчет хэш-суммы. При подробном рассмотрении функции «HashCalculate» становится очевидно, что при подсчете хеша используется 8 байт вектора «IV».
Итак, мы выяснили, что зашифрованный файл имеет следующую структуру:
Посмотрим на файл «Bridge.txt»:
Далее определим последний байт вектора «IV». Для этого откроем программу в отладчике, указав в качестве аргумента любой файл, и поставим брейкпоинт на адрес функции «WriteCryptedData». После чего, предварительно немного поправив ассемблерный код, организуем перебор последнего байта вектора «IV». Результат перебора показан на скриншоте:
Теперь у нас есть полный вектор «IV»: [47 08 8F E7 C4 C0 E9 AB].
Далее необходимо проверить режим шифрования и симметричность используемого шифра.
Вернемся к функции «CryptData» в отладчике, подав на вход программе тестовый файл (в моем случае файл содержал строку «HelloWorld!»). Далее запишем вектор «IV» (находится в EAX перед вызовом CryptData) и результат шифрования (находится в EAX после вызова CryptData).
Проверим идентичность процедуры дешифрования и шифрования. Для этого перезапустим программу под отладчиком и снова поставим брейкпонт на «CryptData». Только теперь перед ее исполнением поправим вектор «IV» и шифруемый буфер.
В результате повторного шифрования зашифрованных данных мы получим исходный текст!
Теперь можно расшифровать файл из задания. Для этого удалим из него первые 15 байт и запустим программу под отладчиком, указав в качестве параметра файл с зашифрованными данными. Вновь поставим брейкпоинт на «CryptData» и перед вызовом функции исправим вектор «IV» на [47 08 8F E7 C4 C0 E9 AB]. После исправления вектора «IV» нажимаем клавишу F9, в результате чего программа зашифрует данные. Из результирующего файла удалим первые 15 байт и откроем его в текстовом редакторе:
Флаг найден!
Ответ: ctfzone{3RR4dIC473_7HIS_WORM!}
P.S. Для тех, кому интересно, мы использовали в этом задании алгоритм шифрования Salsa и алгоритм хэширования CubeHash. Но это может заметить только опытный глаз ;)
Кажется, теперь все встало на свои места. Если у вас есть какие-то вопросы или пожелания – пишите в наш чат в телеграме и оставляйте комментарии. А новые познания в области реверс-инжиниринга можно продемонстрировать по этой ссылке — задания будут доступны до 15 декабря.
Всем удачи и до новых встреч!
Комментарии (6)
lostpassword
29.11.2016 21:47Вот есть одна вещь, которая стабильно в последнее расстраивает в CTF — это не очень понятное засилье 64-битной архитектуры среди исполняемых файлов.
Сам по себе CrackMe представляет собой 64-битный исполняемый файл PE формата. Откроем его в IdaPro и попробуем найти строчку «the door is still closed!»:
Откроем вначале сайт производителя и убедимся, что демо-версия IDA Pro не поддерживает x64. Затем откроем страницу с ценами и поймём, что использовать IDA Pro для этого задания мы явно не сможем, т. к. по умолчанию она частным лицам вообще не поставляется.
P. S. К авторам CTF никаких претензий не имею — просто накипело немного.)
Я понимаю, что 31337 H4kk0rZ умеют такую фигню прямо в Блокноте дизассемблировать — но вот простой смертный с демо-версией (или бесплатной версией) IDA тут много не навоюет. :-(TrueBers
30.11.2016 12:58+1Задания довольно примитивны, Ида тут явно оверкилл, просто в ней удобнее и привычнее, только и всего. Ну и всякие туториалы и статьи писать нагляднее в ней.
Ида незаменима, когда приходится возиться с дикими алгоритмами, размазанным стеком по нескольким десяткам функций, множественным виртуальным наследованием, структурами размером под мегабайт.
А для целей статьи вполне подойдёт тот же radare2 или Snowman. Последний, кстати, иногда лучше HexRays'а декомпилирует некоторые конструкции.
А уж если так хочется Иду, так скачайте с торрентов, изучите её, станьте профессионалом, который, в конечном итоге купит её за свои, заработанные реверсом деньги. Ильфак ничего не потеряет, а только благодарен будет.
Про «засилье» 64 бит просто забавно. Последним 32-х битным процессором, ЕМНИП, был Нортвуд, выпущенный в начале двухтысячных. 15 лет прошло, хватит насиловать труп.
IDA, кстати, сейчас заметно адекватнее ведёт себя в 64-битном режиме.
GeMir
Ох и тяжко шестерёнке с четырьмя зубцами крутиться будет…