Друзья, сегодня мы завершаем публикацию решений нашего CTF-марафона! В нем было пять уровней сложности, в каждом по пять заданий — всего 25 заданий. Перед вами разбор пятого уровня сложности. Предыдущие уровни вы можете изучить здесь: часть 1, часть 2, часть 3, часть 4.  Результаты марафона мы подвели в начале апреля, но задания все еще доступны — и вы можете попробовать решить их для себя. Запускаем  и видим, что это калькулятор: Определяем, на чем он написан: Далее либо соглашаемся, что все-таки на делфи никто не пишет, либо же возмущаемся, потому что легаси-проекты еще надо поддерживать, и делфи – прекрасный язык, который вообще-то не устарел (тут я в целом то согласен, но на нем действительно никто не пишет). Ах да, о чем это мы... Это делфи, для этого есть тулза IDR (Interactive Delphi Reconstructor). Запускаем ее и после анализа создаем идс-скрипт. P.S. Для тех, кто не знает: никогда не запускайте IDR на хостовой машине – он может напрямую запустить некоторые функции, которые могут являться малварными, иными словами –  запустить на хостовой машине малварь. Применяем .idc в IDA, ждем полчаса, соответственно размеру скрипта... После применения скрипта часть имен проставляется сама, но этого может быть недостаточно, если вы не знакомы со структурой делфи. Например, мейна у нас как такового нет и не будет, есть точка входа, и этим следует довольствоваться: Начинаем черную магию – ищем, где у нас будут храниться все объектно ориентированные штуки (всяческие таблицы, адреса и т. д.). Где в GUI-приложении могут быть какие-либо действия? Предположительно, в ивентах – следовательно, где-то эти ивенты нам надо найти. Скрипт не распарсил правильно, но у нас есть таблица методов, которую можно распарсить руками: В функциях сложения, вычитания и пр. находится некая функция, которая вызывается из каждого такого ивента: Что мы можем там найти? Например, switchcase с такими значениями: Предположительно, это некие значения, которые надо ввести в калькулятор. Также в функции TForm1_equalsButtonClick есть очень интересная функция, которая вызывается, если условие с нужными числами соблюдено: На этой функции, кстати, декомпиль ложится отдохнуть: Лег он тут: Есть «предположение», что столько аргументов оно в себя не принимает)): Фиксим, декомпилируем – и внутри нас ждет… (барабанная дробь)… крипта! А дальше – гигантская строка: Некий вызов функций, который IDA решила не парсить: Немного покопавшись в этом всем, определяем: у нас есть AES, есть шифрованное сообщение, и нам нужно составить некий ключ. Значений для этого ключа несколько, так что будем брутить. Пара вариантов функции декрипта: Флаг: DrWeb{1_l1k3_d3lph1_r3v3rs3_3ng1n33r1ng_d1d_y0u_kn0w_1_4m_utt3rly_1ns4n3?} Запускаем игру, видим следующее: Игровой функционал весьма скуден: мы можем стрелять, летать и ускоряться. Если чуток полетать, пострелять и попасть в вирус, то получится вот что: Мы видим множество ключей, которые спавнятся после попадания в вирус. Пробегаемся по файлам, получаем следующее. Модельки: Шейдеры (нам это не пригодится): Текстуры (это великолепие нам тоже особо не будет нужно): Открываем исполняемый файл в иде и начинаем искать по имеющимся зацепкам. Особенно нас интересуют объекты, которые содержат модели вирусов и ключей. Так как названия моделек у нас есть, первым делом ищем строки: Прекрасно – строки есть, и они не обфусцированы. Таким образом мы весьма быстро находим функцию инициализации объектов: Объектник ключа тут не создается – значит, его надо найти. Ищется он тоже просто: При этом вся эта конструкция находится в цикле на 0x2121 итераций: Листаем дальше, в цикле находим несколько функций. Одна из них задает некие константы, весьма большим объемом байт, а также имеет зашитую строку “flagis:”: Отслеживаем все из этой функции: Находим нечто прекрасное: Переходящее в еще более прекрасную функцию: В итоге у нас есть цикл на 0x2121 итераций, в каждую итерацию генерируется ключ путем XOR’а с константами 0x75 0x53AD 0x709D 0x779A 0x8D, а после дальнейшего XOR’a с пошифрованным флагом... ужас... Но, зная это, можно запустить игру, дойти до ключей, запустить дебаггер, приаттачиться к процессу, и сдампить память (ну или искать прямо в ней). Находим этот участок памяти и дампим его весь. Там будет находиться очень приличное количество объектов ключей с "flagis:XXXXXXXX...", тут уже есть вариант поискать нужный ключ вручную (флаг перекодирован в base64, чтобы было не так просто найти, и рядом с флагом лежит строка CORRECTcorrectCORRECTcorrect, по которой можно догадаться, что флаг нужный).  Один из простых способов – написать на питоне небольшой тул, который отсортирует нужный нам дамп памяти по корректным символам аскии. Вывелись две строки: Корректность флага можно определить по дополнительной вставочке “CORRECTcorrectCORRECTcorrect”. Раскодируем “RHJXZWJ7NWM0bl9NM19NM20wcnl9” из Base64 и получаем флаг. Флаг: DrWeb{5c4nM3M3m0ry} По факту это просто закомпиленный бинарь на golang. Сложность же состоит в том, что все символы были стрипнуты. А так как go прекрасным образом вкомпиливает в бинарь вообще все, то нам предстоит каким-то магическим образом отыскать Main. Главная загвоздка задания – в том, что IDA очень плохо парсит golang, а если отвязать символы, то все. А когда кто-либо стрипает символы при компиляции (без очередных запретных знаний, а просто флагом, который предлагает golang), они не стрипаются, а... отвязываются.  Так что в момент запуска и решения CTF вдруг обнаружилось, что IDA > 8.0 может эти отвязанные символы подгрузить... Таким образом, обладая этими буквально запретными знаниями (😊), можно открыть файл в IDA > 8.0 и прочитать его буквально как открытую книгу. Main -> PressXToWin  Получаем аргументы: Парсим аргументы: Хотя все-таки лучше парсить их без декомпиля: + немного помощи: Сопоставляем аргументы: PressXToWin уже создает drweb.key, также стоит посмотреть, куда он его создает – а создает он в “C:</span>Users</span>XXXXX\3D Objects</span>TempDunnoWhy”.. Запускаем программу с нужными аргументами – и вуаля, у нас есть drweb.key иии… он без флага! Зато рядом есть это: Флаг: DrWeb{V1rusAn4lyst101} Для IDA < 8.0: Мейн можно отыскать по строкам, хотя они будут указывать в неинициализированные данные (по мнению иды).  Строки: Строки могут не найтись, и чтобы это починить, зайдем в start и поищем странные оффсеты в большие куски данных, например: Все, что можем, превращаем в код, и побольше. В один момент появляются определенные строки – начинаем плясать отсюда: Дальше продолжаем, как и в первом варианте, но без нейминга, так что будет чуток сложнее. Само по себе задание по своему поведению весьма похоже на малварь, поэтому для тех, кто уже анализировал нечто подобное, оно может оказаться весьма несложным. Исполняемый код находится отнюдь не в исполняемом файле, а в .dll. Сам же исполняемый файл является обычным загрузчиком дотнета. В .dll содержится исполняемый дотнет-код, но уже обфусцированный. Переходим на точку входа. Далее мы можем найти следующие проверки на дебаггер: Они обходятся моментально, даже базовым функционалом dnspy, главное – выставить параметры в настройках. Если же мы запустим файл, то окажется, что антидебаг еще присутствует: Можно разобрать функции, которые у нас имеются. Некий расшифровщик строк: Составление словаря (ID : Base64_string): Мини-пелена джампов: Антидебаг, антивм (который скорее работает как антиНЕвм – такова была задумка, что ай-ай-ай запускать такое не на виртуалке): Следовательно, находим все мешавшие нам функции и nop’аем их через изменение IL-инструкций: Не забываем проставить возвращаемое значение и таким образом избавляемся от всего антиотладочного механизма. Если что-то пойдет не так, мы увидим это: Также, если что-то ломается во время отладки, мы всегда можем пропустить часть инструкций через “Set Next Statement”. Запускаем наш мейн, проходим функции расшифровки пейлоада через AES, доходим до активации самого пейлоада (если не знакомы с функциями типа Assembly.Load, то рекомендую почитать официальную документацию – они позволяют загрузить исполняемый .net-код из памяти): Забыли упомянуть, что пейлоад все это время лежал в ресурсах в зашифрованном виде: Далее нам надо зайти в инвоук, что весьма нетривиально. Можем поставить бряк на загрузку библиотеки или дошагать досюда (неважно, как оно работает, но оно работает): Вуаля! Мы в пейлоаде: Сказать, что он выглядит красиво, – ничего не сказать: Опять убиваем антиотладку, теперь это сделать куда сложнее. Мы обошли ее патчем байтика в памяти 😊 Далее мы видим несколько модулей шифровки/расшифровки строк, пробегаемся по ним – и в памяти появляется флаг. Флаг: DrWeb{S74ySh4rpDoN37} EXTRA: Строки из первой части задания: Строки из второй: Что у нас имеется? Весьма небольшой файлик, который... не работает! Ладно, шутка, он как бы работает, конечно, но крашится при неверном флаге. Открываем файл в иде, встречаем ну просто прекрасный семпл: Идем чуток дальше, и – вау, у нас саморасшифровывающийся код! Для расшифровки у нас есть... таблица для шифрования: Некое простое шифрование: Ну а самое интересное, конечно, что код расшифровывается от флага. Даже само название задания явно указывает на то, что его надо будет брутить. Сначала предлагаем облегчить задачу и подумать, каким должен быть флаг. Условно он будет выглядеть вот так: r“DrWeb{[a-zA-Z0-9]}”. Откуда мы это знаем? Не зря же мы решали задания до этого и поняли про приписку DrWeb и про то, что каждый флаг содержит в себе 1337 speech. Еще длина флага – 20 байт (у нас есть формат строки %20s). Значит, длина флага, которую надо брутить – 13 символов. Еще мы знаем, что наш семпл x64 (это важно, так как нам нужно брутить инструкции). Пробуем первую часть флага – и вот уже наш код стал приобретать какой-то вменяемый вид. Начинаем брутить, иии... Мы не писали никакого специального скрипта – честно говоря, было проще перебрать часть символов руками и попробовать составить из этого слово, каждый раз заново запуская семпл.  Так как шифрование производится побайтово, то, угадав и сбрутив один байт, мы можем уже не возвращаться к нему. Также в некоторых случаях мы ссылаемся на таблицу опкодов, например http://ref.x86asm.net/coder64.html.  Флаг: DrWeb{Pl5P4ckM3B4ck} P.S. Задание для любителей брутить, а если брутить не любим, то формат решения выйдет именно такой.