Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Хотели порешать эти ваши реверсы на CTF? Да ещё и на C++ с Windows Forms? К тому же чтобы он был не сложным и в райтапе были объяснения? Тогда вам сюда 😉

Ссылка на задание (файлы): нажми на меня :)

В архиве будет лежать cross_cr.exe. Флаг должен быть формата CODEBY{FLAG}.
В архиве будет лежать cross_cr.exe. Флаг должен быть формата CODEBY{FLAG}.
Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг
Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

1. Статический анализ файла

1.1 Detect It Easy

Начнём анализ исполняемого файла с помощью утилиты Detect It Easy (далее DIE).

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг
Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

DIE не обнаружил никаких упаковщиков/протекторов и прочих средств защиты. Сам таск свежий, если верить отметки времени. Кроме того он с графическим интерфейсом.

Посмотрим информацию о файле через продвинутый анализ.

Карта памяти

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

В карте памяти аномалий не выявлено.

Энтропия

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Файл кажется сжатым, но если смотреть по секциям, то наиболее сжатый по энтропии - это секция ресурсов (.rsrc). Пока что просто запомним это. Возможно, далее нам это пригодится.

Строки

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

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

Импорты

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Из импортов чего-то "слишком криминального" не выявлено. Но есть IsDebuggerPresent.

Примечание: Хоть функция IsDebuggerPresent и есть в программе, но она не используется в основном коде для изменения поведения таска во время отладки. Поэтому нам будет проще.

Анализ заголовков

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг
Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

По заголовкам всё выглядит нормально для EXE-файла.

Виртуализация

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

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

Ресурсы

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Теперь понятно, откуда такой размер. Это PNG-файл. Посмотрим более детально в Resource Hacker.

1.2 Resource Hacker

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Это иконка. Если смотреть на неё в Hexdump'е (Binary View) ничего странного не видно.

1.3 Hex-редактор

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Иногда в конце исполняемых файлов некоторых тасков что-то можно найти. Но тут ничего странного нет.

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

2. Динамический анализ

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг
Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Динамический анализ в Autoruns, Process Monitor, Process Hacker и Wireshark не показал ничего необычного.

3. Анализ в IDA

Примечание: далее будет использована IDA Freeware 8.4. Её хватит с головой :)Но вы также можете использовать Ghidra, Cutter (Radare2) или любой другой отладчик, который вам нравится. Код в декомпиляторах будет +- похожим.

3.1 Поиск сообщения о неправильном флаге

Попробовав запустить файл и ввести что-то в поля для ввода, мы наблюдаем вызов функции MessageBox с текстом о неправильном флаге.

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Запустим IDA и попробуем найти код, что вызывает этот MessageBox. Перейдём в окно Strings (Shift + F12).

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Русские символы по умолчанию не отображаются. Для этого нужно нажать правую кнопку мыши (далее ПКМ), затем Setup и выбрать такие пункты меню.

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг
Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Опция Unicode нужна, чтобы нормально отображались символы из 16-битных кодировок в стандарте Unicode. Строки в 16-битной кодировке активно используются в WinAPI.

Галочку со Strict ASCII мы убираем для поиска русских символов (в том числе для опции Unicode). Но для нормального отображения нам нужно зайти ещё сюда.

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг
Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Затем тут выставляем cp1251. Это стандартная 8-битная кодировка для русских символов в Windows.Далее нажимаем Rebuild и видим русские символы.

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг
Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Первых символов не видно, но понять можно. Жмём на "хх..." (от Эхх...).

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг
Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Нужно представить эти байты как строку. Жмём ALT+A, затем сюда.

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг
Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Байты представились как текст в кодировке UTF-16LE (UTF-16 Little Endian). Наводим курсор на название строки (aE) И нажимаем X, чтобы IDA показала нам, где используется эта строка. Жмём левой кнопкой мыши (далее ЛКМ) на пункт меню со скрина.

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Видим 2 блока, ведущих к MessageBoxW. Обычно это блоки о неправильном/правильном флаге. Переменные unk на скриншотах - это русские строки. Их тоже можно представить в нормальном виде.

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Через клавишу N можно поменять название ноды графа, наведя курсор на его первую инструкцию. Кроме этого можно задать цвет через ПКМ или специальную иконку.

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг
Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Более того мы сразу попали на функцию проверки ввода.

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг
Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

3.2 Анализ функции и начало сбора флага

Составим краткий "скелет" функции через декомпилятор - клавиша F5. В IDA Freeware онлайн-декомпилятор, поэтому иногда он может быть недоступен. Учтите это, если будете пробовать сами.

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Через клавишу N можно менять названия объектов.

Убедиться, что эта функция используется для проверки нашего ввода можно, если навести курсор на её название и нажать X. А далее посмотреть, где вызывается.

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг
Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг
Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Эту функцию можно назвать так. Она обрабатывает сообщения от GUI-окна. По коду 0x111 (WM_COMMAND) вызывается наша функция проверки ввода.

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

На check_input и сосредоточим внимание. Вывод декомпилятора:

int check_input() { __int64 v0; // rdi __int64 v1; // rax __int64 v2; // rsi WCHAR *v3; // rbx int v4; // esi int v5; // ebx int v6; // ebp int result; // eax int v8; // edx UINT v9; // r9d const WCHAR *v10; // r8 const WCHAR *v11; // rdx WCHAR String1[256]; // [rsp+20h] [rbp-418h] BYREF WCHAR String[256]; // [rsp+220h] [rbp-218h] BYREF memset(String, 0, sizeof(String)); GetWindowTextW(hWnd, String, 256); memset(String1, 0, sizeof(String1)); GetWindowTextW(qword_140005670, String1, 256); v0 = -1LL; v1 = -1LL; do ++v1; while ( String1[v1] ); if ( v1 == 27 && !wcsncmp(String1, L"CODEBY{", 7uLL) && String1[26] == 125 ) { v2 = 11LL; v3 = &String1[13]; while ( *(v3 - 2) == 45 && iswdigit(*(v3 - 1)) && iswdigit(*v3) && iswdigit(v3[1]) && iswdigit(v3[2]) ) { v2 += 5LL; v3 += 5; if ( v2 > 24 ) { v4 = wtoi(&String1[7]); v5 = wtoi(&String1[12]); v6 = wtoi(&String1[17]); result = wtoi(&String1[22]); v8 = result; while ( String[v0 + 1] == aMasterOfCodeby[v0 + 1] ) { v0 += 2LL; if ( v0 == 17 ) { if ( (((v4 ^ 0xDFAF7) + 22098798) ^ 0x23B97B) == 24947582 && (((v5 ^ 0x378) + 1361) ^ 0xB84C) == 40468 ) { result = (v6 - 9283) ^ 0xA808; if ( result == -47487 && (((v8 ^ 0xFD836) - 13112) ^ 0xBC3F) == 988548 ) { v9 = 0; v10 = L"Флаг!!!"; v11 = L"Флаг, поздравляю! :)"; return MessageBoxW(0LL, v11, v10, v9); } } return result; } result = String[v0]; if ( result != aMasterOfCodeby[v0] ) goto fail; } break; } } } fail: v9 = 16; v10 = L"Эхх..."; v11 = L"Ну, почти..."; return MessageBoxW(0LL, v11, v10, v9); }

Самые первые инструкции получают данные из полей с вводом имени и флага:

memset(String, 0, sizeof(String)); GetWindowTextW(hWnd, String, 256); memset(String1, 0, sizeof(String1)); GetWindowTextW(qword_140005670, String1, 256);

В дизассемблированном виде код выглядит так (вывод декомпилятора полезно проверять на листинге дизассемблера):

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Сразу дадим нормальные имена, чтобы повысить читаемость. Через отладку можно понять, где будет что.

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

В данном случае в поле имени было введено "qwe".

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Следующая часть кода выполняет подсчёт длины строки с флагом:

v0 = -1LL; v1 = -1LL; do ++v1; while ( flag[v1] ); if ( v1 == 27...

Листинг дизассемблера:

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Флаг должен быть длиною в 27 символов.

Следующий кусок кода:

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Первое условие из выделения со скрина проверяет, что начальные 7 символов флага - это CODEBY{. Второе условие из выделения проверяет, что код символа с индексом 26 (по счёту 27-ой) равен 125. В таблице ASCII это символ }.

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Листинг дизассемблера:

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Таким образом сейчас у нас есть следующее представление флага:

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Следующий блок кода:

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Листинг дизассемблера:

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Данный блок кода проверяет часть флага между фигурными скобками {}. Она должна быть такого вида:

-XXXX-XXXX-XXXX

Где X - это цифра в 10-ой системе.

Немного странно, что в этой проверке упущена часть перед первым -, это мы поймём далее.

Затем идёт такой блок:

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Он преобразует строку с числом между символами - в число.

Листинг дизассемблера:

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Следующий блок кода сравнивает введённое имя со строкой MASTER_OF_CODEBY. Это будет правильным именем для дальнейшего решения таска.

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг
Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Листинг дизассемблера:

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

3.3 Составляем "тело флага"

Посмотрим на следующий блок:

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Он выполняет шифрование чисел через простые операции и сравнивает их с нужными.

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Листинг дизассемблера:

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Перепишем это в более понятном виде:

first_decimal ^= 0xDFAF7; first_decimal += 0x151336E; first_decimal ^= 0x23B97B; // first_decimal должен быть 0x17CAB7E second_decimal ^= 0x378; second_decimal += 0x551; second_decimal ^= 0xB84C; // second_decimal должен быть 0x9E14 third_decimal -= 0x2443; third_decimal ^= 0xA808; // third_decimal должен быть -47487 (0xFFFF4681) fourth_decimal ^= 0xFD836; fourth_decimal -= 0x3338; fourth_decimal ^= 0xBC3F; // fourth_decimal должен быть 0xF1584

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

Теперь останется просто применить противоположные операции в обратном порядке к нужным значениям.

  • Для операции + обратная операция -.
  • Для операции - обратная операция +.
  • Для операции xor обратная операция xor.
first_decimal = 0x17CAB7E; first_decimal ^= 0x23B97B; first_decimal -= 0x151336E; first_decimal ^= 0xDFAF7; // (((0x17CAB7E ^ 0x23B97B) - 0x151336E) ^ 0xDFAF7) = 9312 second_decimal = 0x9E14; second_decimal ^= 0xB84C; second_decimal -= 0x551; second_decimal ^= 0x378; // (((0x9E14 ^ 0xB84C ) - 0x551 ) ^ 0x378) = 8831 third_decimal = 0xFFFF4681; third_decimal ^= 0xA808; third_decimal += 0x2443; // ((0xFFFF4681 ^ 0xA808) + 0x2443) = 4294972108 fourth_decimal = 0xF1584; fourth_decimal ^= 0xBC3F; fourth_decimal += 0x3338; fourth_decimal ^= 0xFD836; // (((0xF1584 ^ 0xBC3F) + 0x3338) ^ 0xFD836) = 1221

Итого наши части флага:

9312-8831-4294972108-1221

Общий флаг:

CODEBY{9312-8831-4294972108-1221}

Третья часть очень выделяется. И проверку не пройдёт. Если посмотреть листинг дизассемблера для функции шифрования, то данные там хранятся в 4-байтовых регистрах.

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Представим число 4294972108 в 16-ом виде:

0x1000012CC 0x01 00 00 12 CC (по байтам)

Это 5-байтовое число. А регистры 4-байтовые. 5-байтовое число в 4-байтовый регистр никак не влезает. Поэтому в процессе шифрования первые лишние байты просто отсекаются. В данном случае это байт 0x01 в самом начале:

0x00 00 12 CC (по байтам)

Получили 0x000012CC. Незначащие байты (0x0000) можно убрать и получить 16-ое число. Это 16-ое число переводим в 10-е. Далее оформляем флаг и сдаём.

Делиться открытым флагом не буду, дальше вы сами добьёте таск! :)

Райтап на таск «Крестики» с Codeby Games | CTF, реверс-инжиниринг

Спасибо за прочтение статьи! 😉

Начать дискуссию