CTFzone write-ups – Shall I reverse it?

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 in the worm 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 грудня.
Всім удачі і до нових зустрічей!
Джерело: Хабрахабр

0 коментарів

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