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