--- tags: writeup, rev --- # **Разбор задания Черный ящик с олимпиады "Ломоносов"** > [name=User1] ![](https://i.imgur.com/j37uWA4.png) > [color=#1cefda] Спортпрог который мы заслужили [@ch4nnel1](https://t.me/ch4nnel1) ## Предыстория Когда-то (5-12 ноября) была олимпудка ломоносова по информатике (спортивное програмирование). Вот я её решаю, решаю, открываю очередное задание. А оно начинается со слов: "Вам дан бинарный файл...". Я такой: "Прикольно звучит как реверс, было бы забавно. Хе-хе". Дальше : "... без исходного кода...". \- "Все чудесатее и чудесатее" *[Продолжение читайте в источнике](#Разбор)* ## Разбор Тут вообще должно было быть условие задания, но его нет( <p style="text-align: center;">Сам таск... А ссылки тоже нет(</p> Придется рассказать, если кратко: дан [бинарный файл](https://drive.google.com/file/d/1eauMNnJmKzPIRCZUWl4R5OuZ_sDMF1Jp/view?usp=sharing) без исходного кода, сказано что нужно написать программу, которая по его выводу будет определять, что у него было на вводе. И так же сказано, что если Вы ~~лох и~~ не имеете под рукой линукс (бинарь дан под линукс), то существует какой-то jslinux и минимальная инструкция для него. (а еще я вспомнил, что там вроде бы надо было фильтровать ввод, но это неточно и на соревах я про это успешно забыл). Можем попробовать "поблекбоксить" таск, и поймем что скорее всего он принимает только чиселки, ведь на все остальное он возвращает "константный" ответ 1. ![](https://i.imgur.com/6XP3RBp.png) Но все таки надо начать реверс, ведь сомневаюсь что дали бы что-то ультрапростое. Открыл таск в иде, она все разреверсила. Получил, что там есть две функции: "encrypt" и "decrypt". Сначала число декриптится (байтовые сдвиги и ксор), потом к зашифрованному прибавляется 1, и это дело инкриптится (опять байтовые сдвиги и ксор). Я подумал, что так это все слишком легко и пошел реверсить асм через радар хоть для минимального челенджа, но даже там все вышло +- легко. Единственное, что было интересненьким это работа с 8 байтным числом через 2 32 битных регистра (бинарь был 32 битным). И тут мне в голову пришла "гениальная" мысль, а что если представить, что я не могу реверснуть эти функции, так как в них дана какая-нибудь мега ультра хардкор крипта, это же будет так весело чуть пострадать... ![](https://i.imgur.com/cZM5xen.png) Ну и да, гора ошибок в будущем не заставила себя ждать. Но начнем по порядку, моя идея заключалась в том чтобы в мою программу засунуть байты функций декода и инкода, пошаманить с памятью, сделать ее исполняемой и как-нибудь вызвать эти функции (и в теории эти функции должны работать так же, ведь сомневаюсь, что орги компилят под какой-то свой проц или делают еще что-то что должно помешать байтам исполниться так же как и у меня). Сделать что-то наподобие самоинжекта кода. Все звучит не слишком сложно если еще учитывать, что у меня оставались наработки для более комплексной штуки еще с лета. Первым делом я проверил, что функции декода и инкода являются обратными: на все 10 вариантов, что я попробовал они сработали правильно. Но дальше все оказалось не так радужно, первая же проблема с которой я столкнулся - бинарный файл был 32 битным и для вызова функций использовал соглашение **cdecl** (для меня это было проблемой так как я его просто несильно люблю). (Тут я опущу как долго тупил и искал "правильный" компилятор в https://godbolt.org/). После некоторого времени до меня дошло, что можно было бы воспользоваться тем же компилятором, которым была скомпилен данный мне файл. И тогда и функции должны будут вызываться полностью так же как и в данном мне файле. Недолго думая, с помощью любимого радара достал байты нужных функций, и написал прогу, которая должна была работать. Даже сначала проверил в https://godbolt.org/, что особой разницы в вызовах функций у gcc версии 11.1.1 и 9.3.0 (которой компилировался данный бинарь/та что стоит у меня) нету. Дальше попытался ее скомпилить данной командой ```bash $ gcc -m32 test.cpp In file included from /usr/include/c++/9/stdlib.h:36, from test.cpp:4: /usr/include/c++/9/cstdlib:41:10: fatal error: bits/c++config.h: Нет такого файла или каталога 41 | #include <bits/c++config.h> | ^~~~~~~~~~~~~~~~~~ compilation terminated. ``` С этим всем я тоже успел намучиться, оказывается gcc "умный" и если видит у файла на конце cpp, то пытается его компилить как c++, а для него у меня не установленны какие-то нужные 32 битные зависимости. В результате достаточно было переименовать файл в test.c и все скомпилилось довольно хорошо. **Код на данный момент:** ```c #include <stdio.h> #include <sys/mman.h> #include <stdlib.h> #include <string.h> typedef long long ll; typedef unsigned char u_char; typedef ll(__attribute__((__cdecl__))* en_de)(ll buf); // говорим что en_de == long long(*)(long long) // __attribute__((__cdecl__)) - говорим что функция должна вызываться с cdecl, это необязательно gcc вроде подефолту и так компилит с ним, но для красоты + подстраховки сделал с ним u_char* create_fun(u_char* source, size_t len_of_source) { u_char* res = (u_char*)malloc(len_of_source); // выделяем память // использовал malloc, так как рабочий вариант, в теории можно было и просто в глобальную память сразу функции поместить, но не был уверен в работоспособности. mprotect((void*)res, len_of_source, PROT_READ | PROT_WRITE | PROT_EXEC); // делаем этот кусок исполняемым memcpy(res, source, len_of_source); // помещаем в него нашу функцию return res; } int main(){ u_char encode[] = { 0x55, /* push ebp */ // много байтиков инкод функции 0xc3, /* ret */ }; u_char decode[] = { 0x55, /* push ebp */ // много байтиков декод функции 0xc3, /* ret */ }; ll buf; scanf("%lld", &buf); en_de decoder = (en_de)create_fun(decode, sizeof(decode)/sizeof(u_char)); en_de encoder = (en_de)create_fun(encode, sizeof(encode)/sizeof(u_char)); buf = decoder(buf); buf--; buf = encoder(buf); printf("%lld\n", buf); } ``` Дальше запускаем, пробуем, что-то ввести, и оно падает ```bash $ gcc -m32 test.c $ ./a.out 131 Ошибка сегментирования (стек памяти сброшен на диск) ``` Я с подобным уже сталкивался, и тогда это было вызвано тем, что `mprotect` умеет изменять права памяти, только если ему дано начало страницы (раньше у меня malloc выделял память в начале страницы. Поэтому сразу и не пофиксил). Сделаем небольшие изменения, скомпилим... И БАЦ, оно теперь не падает и даже возвращает верный результат. ```bash $ ./prog 321213412 321213413 $ ./a.out 321213413 321213412 ``` Засылаем проверяющей системе, оно проходит один единственный тест -> скорее всего не падает, радуемся жизни. ## Финальная версия кода: ```c #include <stdio.h> #include <sys/mman.h> #include <stdlib.h> #include <string.h> typedef long long ll; typedef unsigned char u_char; typedef ll(__attribute__((__cdecl__))* en_de)(ll buf); // говорим что en_de == long long(*)(long long) // __attribute__((__cdecl__)) - говорим что функция должна вызываться с cdecl, это необязательно gcc вроде подефолту и так компилит с ним, но для красоты + подстраховки сделал с ним u_char* create_fun(u_char* source, size_t len_of_source) { int page_size = getpagesize(); u_char* res = (u_char*)malloc(len_of_source+page_size); // выделяем память + добавляем еще размер страницы, тк следующим выравниванием мы переместимся на страницу вперед. // использовал malloc, так как рабочий вариант, в теории можно было и просто в глобальную память сразу функции поместить, но не был уверен в работоспособности. res = (char *)(((int) res + page_size-1) & ~(page_size-1)); // выравниваем по границе страницы mprotect((void*)res, len_of_source, PROT_READ | PROT_WRITE | PROT_EXEC); // делаем этот кусок исполняемым memcpy(res, source, len_of_source); // помещаем в него нашу функцию return res; } int main(){ u_char encode[] = { 0x55, /* push ebp */ 0x89, 0xe5, /* mov ebp, esp */ 0x56, /* push esi */ 0x53, /* push ebx */ 0x83, 0xec, 0x08, /* sub esp, 8 */ 0x8b, 0x45, 0x08, /* mov eax, dword [arg_8h] */ 0x89, 0x45, 0xf0, /* mov dword [var_10h], eax */ 0x8b, 0x45, 0x0c, /* mov eax, dword [arg_ch] */ 0x89, 0x45, 0xf4, /* mov dword [var_ch], eax */ 0x8b, 0x45, 0xf0, /* mov eax, dword [var_10h] */ 0x8b, 0x55, 0xf4, /* mov edx, dword [var_ch] */ 0x0f, 0xac, 0xd0, 0x01, /* shrd eax, edx, 1 */ 0xd1, 0xea, /* shr edx, 1 */ 0x89, 0xc6, /* mov esi, eax */ 0x33, 0x75, 0xf0, /* xor esi, dword [var_10h] */ 0x89, 0xf1, /* mov ecx, esi */ 0x89, 0xd0, /* mov eax, edx */ 0x33, 0x45, 0xf4, /* xor eax, dword [var_ch] */ 0x89, 0xc3, /* mov ebx, eax */ 0x89, 0xc8, /* mov eax, ecx */ 0x89, 0xda, /* mov edx, ebx */ 0x83, 0xc4, 0x08, /* add esp, 8 */ 0x5b, /* pop ebx */ 0x5e, /* pop esi */ 0x5d, /* pop ebp */ 0xc3, /* ret */ }; u_char decode[] = { 0x55, /* push ebp */ 0x89, 0xe5, /* mov ebp, esp */ 0x57, /* push edi */ 0x56, /* push esi */ 0x53, /* push ebx */ 0x83, 0xec, 0x1c, /* sub esp, 0x1c */ 0x8b, 0x4d, 0x08, /* mov ecx, dword [arg_8h] */ 0x89, 0x4d, 0xd8, /* mov dword [var_28h], ecx */ 0x8b, 0x4d, 0x0c, /* mov ecx, dword [arg_ch] */ 0x89, 0x4d, 0xdc, /* mov dword [var_24h], ecx */ 0xc7, 0x45, 0xe8, 0x00, 0x00, 0x00, 0x00, /* mov dword [var_18h], 0 */ 0xc7, 0x45, 0xec, 0x00, 0x00, 0x00, 0x00, /* mov dword [var_14h], 0 */ 0xeb, 0x28, /* jmp 0x804cdcb */ 0x8b, 0x4d, 0xe8, /* mov ecx, dword [var_18h] */ 0x33, 0x4d, 0xd8, /* xor ecx, dword [var_28h] */ 0x89, 0xc8, /* mov eax, ecx */ 0x8b, 0x4d, 0xec, /* mov ecx, dword [var_14h] */ 0x33, 0x4d, 0xdc, /* xor ecx, dword [var_24h] */ 0x89, 0xca, /* mov edx, ecx */ 0x89, 0x45, 0xe8, /* mov dword [var_18h], eax */ 0x89, 0x55, 0xec, /* mov dword [var_14h], edx */ 0x8b, 0x4d, 0xd8, /* mov ecx, dword [var_28h] */ 0x8b, 0x5d, 0xdc, /* mov ebx, dword [var_24h] */ 0x0f, 0xac, 0xd9, 0x01, /* shrd ecx, ebx, 1 */ 0xd1, 0xeb, /* shr ebx, 1 */ 0x89, 0x4d, 0xd8, /* mov dword [var_28h], ecx */ 0x89, 0x5d, 0xdc, /* mov dword [var_24h], ebx */ 0x8b, 0x4d, 0xd8, /* mov ecx, dword [var_28h] */ 0x80, 0xf5, 0x00, /* xor ch, 0 */ 0x89, 0xce, /* mov esi, ecx */ 0x8b, 0x4d, 0xdc, /* mov ecx, dword [var_24h] */ 0x80, 0xf5, 0x00, /* xor ch, 0 */ 0x89, 0xcf, /* mov edi, ecx */ 0x89, 0xf9, /* mov ecx, edi */ 0x09, 0xf1, /* or ecx, esi */ 0x85, 0xc9, /* test ecx, ecx */ 0x75, 0xc0, /* jne 0x804cda3 */ 0x8b, 0x45, 0xe8, /* mov eax, dword [var_18h] */ 0x8b, 0x55, 0xec, /* mov edx, dword [var_14h] */ 0x83, 0xc4, 0x1c, /* add esp, 0x1c */ 0x5b, /* pop ebx */ 0x5e, /* pop esi */ 0x5f, /* pop edi */ 0x5d, /* pop ebp */ 0xc3, /* ret */ }; ll buf; scanf("%lld", &buf); en_de decoder = (en_de)create_fun(decode, sizeof(decode)/sizeof(u_char)); en_de encoder = (en_de)create_fun(encode, sizeof(encode)/sizeof(u_char)); buf = decoder(buf); buf--; buf = encoder(buf); printf("%lld\n", buf); } ```