CTFzone write-ups – Shall I reverse it too?

image
Друзі, ледь ми опублікували рішення на чотири завдання з категорії Reverse, як тут же від вас почали надходити запитання щодо того, де ж завдання на 1000. Ми вирішили вас довго не мучити і готові представити свіжий райтап :)
Як вже було сказано, таск на 1000 був одним з найскладніших завдань CTFzone, і він так і залишився невирішеним. Тому, якщо Ви не вирішили якісь завдання з цієї гілки, рекомендуємо для початку розібратися з ними ось тут, а вже потім приступати до завдання на максимальну кількість очок. Удачі!

Reverse_1000. Black hole

A. U. R. O. R. A.: Lieutenant, look! Our pilot is downstairs, you just have to come down. There are three doors – with Earth, Moon and Sun – and one of them leads to the stairs. Choose and go, but be careful – two other doors lead to the black hole which are impossible to climb out of.
Рішення:
Отже, в цьому завданні учасникам необхідно було зробити вибір – знайти вірну двері, щоб дістатися до пілота. Однак, ціна помилки велика — можна провалитися в чорну діру, так що нам варто бути гранично обережними.
Для початку запустимо сам файл «reverse1000.exe» на виконання і спробуємо що-небудь ввести:
image
У відповідь на введену рядок «123456» виводиться вікно з повідомленням «Access denied». Що ж робити?
Спробуємо подивитися файл у програмі Ida — Interactive Disassembler і пошукаємо, де зустрічається виклик вікна з повідомленням «Access denied»:
image
Як видно на скріншоті, після запуску програми відбувається виклик діалогової функції DialogBoxParamW, куди в якості аргументу lpDialogFunc передається покажчик на функцію DialogFunc; в ній відбувається отримання і перевірка вводу:
image
Отже, при перевірці введення викликається функція sub_41a980 – назвемо її check_input.
image
Спробуємо розібратися, що відбувається всередині функції check_input.
image
Отже, ми бачимо, що відбувається робота з якоїсь змінної dword_45E124, схожою на деякий глобальний контекст, з яким виконується подальша робота. Назвемо цю змінну cntx.
Також перейменуємо змінну this в InputString. Видно, що виконується конвертація вхідний рядки, отримання її довжини і передача у функцію sub_412100. Перш ніж йти далі, перевіримо, де ще може використовуватися cntx:
image
Виявляється, cntx також використовується в функції WinMain. Подивимося, що там:
image
Справді, виконується ініціалізація глобального контексту. Увагу може привернути мінлива unk_44E11D, що вказує на масив зміщень до різних рядках:
image
Пошукаємо в Google, може бути використовується якась стандартна бібліотека. Знайдені рядки «getmetatable», «getupvalue», «setmetatable» призводять до документації мови «Lua», на це ж вказують багато інші рядки в виконуваний файл програми. Є підстави підозрювати, що для перевірки введеного рядка використовується «Lua – інтерпретатор». Однак при перегляді в HEX – редакторі виконуваного файлу програми вихідного коду «Lua» на перший погляд не видно. Ймовірно, він зашифрований або закодований.
У документації «Lua», сказано, що для виконання коду в інтерпретаторі Lua необхідно:
  1. Ініціалізувати Lua з допомогою luaL_newstate(), що повертає контекст Lua:
    LUALIB_API lua_State *luaL_newstate (void) {
    lua_State *L = lua_newstate(l_alloc, NULL);
    if (L) lua_atpanic(L, &panic);
    return L;
    }
  2. Використовувати функції luaL_load() або luaL_loadbuffer(), завантажують вихідний код або скомпільовані шматки коду Lua.
  3. Викликати функцію luaL_openlibs() для ініціалізації вбудованих бібліотек Lua.
  4. Після цього можна викликати Lua – код за допомогою функції lua_pcall().
Ось тут можна більш детально ознайомитися з мовою Lua.
Далі, дослідивши функцію WinMain, ми бачимо виклик luaL_load(), що приймає на вхід статичний буфер містить код (змінна compressed_code):
image
Однак, якщо перейти за адресою 0044E778, то ніякого Lua – коду (навіть скомпільованого), там немає. Судячи з усього, функція luaL_load() змінено для того, щоб завантажувати стиснутий або зашифрований Lua – код. В цьому випадку є два варіанти: ми можемо спробувати отримати цей буфер після розпакування в налагоджувач або спробувати з'ясувати, чого означає сигнатура на початку буфера:
image
З'ясуватися, що 5D 00 00 10 – це заголовок архівів LZMA. Скористаємося програмою 7 – Zip, щоб розпакувати цей блок даних:
image
Справді, тепер цей блок даних схожий на скомпільований код на Lua. Спробуємо використовувати декомпілятор, наприклад, luadec. Однак при спробі декомпіляції декомпілятор видає помилку. Придивімося уважніше: заголовок файлу починається з CA FE BA BE, в той час як заголовок скомпільованого Lua байткода починається з 1B 4C 75 61. Поміняємо заголовок файлу в HEX – редакторі:
image
І ще раз запустимо декомпілятор:
image
Ура! Файл успішно декомпилирован. В результаті видно, що у функції «a» здійснюється виклик деякої функції «string.a», з вбудованої бібліотеки Lua String Library, і перевіряється повертається їй значення. Якщо воно дорівнює рядку деякої константної рядку, то повертається значення 1, інакше повертається значення 0.
Тепер нам потрібно знайти функцію «a» в бібліотеці Lua String. Ця функція повинна знаходиться десь поруч з іменами і покажчиками на інші функції Lua String Library, про які можна дізнатися в документації.
Пошукаємо в IDA рядок «gfind» (так називається одна із стандартних функцій Lua String Library, завдяки імені її нескладно знайти):
image
Ось вона! І всі інші імена функцій для роботи з рядками тут же. Перейдемо тепер до рядку майже в самому низу списку, за адресою 0045D03, з ім'ям «a» — шуканої функції. Визначимо перехресні посилання:
image
Перейдемо до масиву зміщень на назви функцій і самі функції:
image
Після цього перейдемо до тіла функції sub_4124F0. Як видно, тут відбувається шифрування введеного рядка на ключі «starforcestrikesback». Зашифрована таким чином рядок потім порівнюється з шифрованого рядком, що знаходиться всередині Lua – скрипта:
image
image
Знаючи алгоритм, ми можемо розшифрувати рядок з Lua – коду:
image
Приклад програми на мові C, призначеної для розшифровки шуканого рядка:
#include < stdio.h>
#include <stdlib.h>
#include < string.h>

int main(int argc, char *argv[]) {

unsigned char ciphertext[36] = {
0x10, 0x63, 0x73, 0x6E, 0x73, 0x6E, 0x79, 0x7D, 0x2E, 0x33, 0x37, 0x2F,
0x58, 0x03, 0x3A, 0x28, 0x0E, 0x6E, 0x03, 0x3F, 0x48, 0x49, 0x37, 0x3F,
0x40, 0x54, 0x26, 0x63, 0x27, 0x34, 0x1E, 0x7E, 0x0B, 0x28, 0x26, 0x3F
};

int len = sizeof(ciphertext);

unsigned char key[] = "starforcestrikesback"; 

int keylen = strlen(key);

char *plaintext = malloc(len + 1);

memset(plaintext, 0, len + 1);

plaintext[0] = ciphertext[0] ^ key[0];

for (int i = 1; i < len; i++) {
plaintext[i] = ciphertext[i] ^ plaintext[i - 1] ^ key[i % keylen];
}

for(int i = 0; i < len; i++)
printf("%c", plaintext[i]);
puts("\n"); 

return 0;
}

Скомпілювавши зазначену вище програму і запустити її на виконання, отримаємо шуканий рядок: ctfzone{0p3n_7h3_P0d_b4y_d00r5_S1r1}.
Тепер введемо цю рядок в поле вводу програми і натиснувши «Ok», побачимо підтвердження того, що введена рядок вірна:
image
Ось і все!
Відповідь: ctfzone{0p3n_7h3_P0d_b4y_d00r5_S1r1}
У цьому завданні використовується досить рідкісний мову, але в іншому все цілком реально, чи не правда? Якщо у вас залишилися питання, залишайте коментарі і добавляйтесь в наш чат в Telegram.
Також не забувайте про завдання на нашому порталі. Проявіть себе до 15.12, і, можливо, на наступному CTFzone Ви будете вже не гравцем ;).
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.