Try   HackMD

Разбор задания Черный ящик с олимпиады "Ломоносов"

User1

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Спортпрог который мы заслужили @ch4nnel1

Предыстория

Когда-то (5-12 ноября) была олимпудка ломоносова по информатике (спортивное програмирование). Вот я её решаю, решаю, открываю очередное задание.
А оно начинается со слов: "Вам дан бинарный файл".
Я такой: "Прикольно звучит как реверс, было бы забавно. Хе-хе".
Дальше : " без исходного кода".
- "Все чудесатее и чудесатее"
Продолжение читайте в источнике

Разбор

Тут вообще должно было быть условие задания, но его нет(

Сам таск... А ссылки тоже нет(

Придется рассказать, если кратко: дан бинарный файл без исходного кода, сказано что нужно написать программу, которая по его выводу будет определять, что у него было на вводе. И так же сказано, что если Вы лох и не имеете под рукой линукс (бинарь дан под линукс), то существует какой-то jslinux и минимальная инструкция для него.
(а еще я вспомнил, что там вроде бы надо было фильтровать ввод, но это неточно и на соревах я про это успешно забыл).

Можем попробовать "поблекбоксить" таск, и поймем что скорее всего он принимает только чиселки, ведь на все остальное он возвращает "константный" ответ 1.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Но все таки надо начать реверс, ведь сомневаюсь что дали бы что-то ультрапростое. Открыл таск в иде, она все разреверсила. Получил, что там есть две функции: "encrypt" и "decrypt". Сначала число декриптится (байтовые сдвиги и ксор), потом к зашифрованному прибавляется 1, и это дело инкриптится (опять байтовые сдвиги и ксор).

Я подумал, что так это все слишком легко и пошел реверсить асм через радар хоть для минимального челенджа, но даже там все вышло ± легко. Единственное, что было интересненьким это работа с 8 байтным числом через 2 32 битных регистра (бинарь был 32 битным).

И тут мне в голову пришла "гениальная" мысль, а что если представить, что я не могу реверснуть эти функции, так как в них дана какая-нибудь мега ультра хардкор крипта, это же будет так весело чуть пострадать

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Ну и да, гора ошибок в будущем не заставила себя ждать.

Но начнем по порядку, моя идея заключалась в том чтобы в мою программу засунуть байты функций декода и инкода, пошаманить с памятью, сделать ее исполняемой и как-нибудь вызвать эти функции (и в теории эти функции должны работать так же, ведь сомневаюсь, что орги компилят под какой-то свой проц или делают еще что-то что должно помешать байтам исполниться так же как и у меня). Сделать что-то наподобие самоинжекта кода. Все звучит не слишком сложно если еще учитывать, что у меня оставались наработки для более комплексной штуки еще с лета.

Первым делом я проверил, что функции декода и инкода являются обратными: на все 10 вариантов, что я попробовал они сработали правильно.

Но дальше все оказалось не так радужно, первая же проблема с которой я столкнулся - бинарный файл был 32 битным и для вызова функций использовал соглашение cdecl (для меня это было проблемой так как я его просто несильно люблю).

(Тут я опущу как долго тупил и искал "правильный" компилятор в https://godbolt.org/).

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

Даже сначала проверил в https://godbolt.org/, что особой разницы в вызовах функций у gcc версии 11.1.1 и 9.3.0 (которой компилировался данный бинарь/та что стоит у меня) нету. Дальше попытался ее скомпилить данной командой

$ 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 и все скомпилилось довольно хорошо.
Код на данный момент:

#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);
}

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

$ gcc -m32 test.c
$ ./a.out 
131
Ошибка сегментирования (стек памяти сброшен на диск)

Я с подобным уже сталкивался, и тогда это было вызвано тем, что mprotect умеет изменять права памяти, только если ему дано начало страницы (раньше у меня malloc выделял память в начале страницы. Поэтому сразу и не пофиксил). Сделаем небольшие изменения, скомпилим И БАЦ, оно теперь не падает и даже возвращает верный результат.

$ ./prog
321213412
321213413
$ ./a.out 
321213413
321213412

Засылаем проверяющей системе, оно проходит один единственный тест -> скорее всего не падает, радуемся жизни.

Финальная версия кода:

#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);
}