Rating:


# **Write-up Simple Protocol**

![](https://i.imgur.com/LGh6HSP.png)

> Здесь могла быть ваша реклама, но пока тут реклама [@ch4nnel1](https://t.me/ch4nnel1)

Original write-up: https://hackmd.io/@osogi/Simple_Protocol

## Разбор + история
![](https://i.imgur.com/940xSH7.png)

Сам таск(https://game.ctfcup.ru/tasks/948157b9-8d44-477d-afac-4b46488d89d0)

И еще гитхаб таска -> https://github.com/acisoru/ctfcup-21-quals/blob/main/tasks/reverse/simple-protocol/

### Пролог (поиск бирника)
Можем попробовать запустить бинарник, но нам это мало что даст, так как он просто читает что-то с ввода и потом отдает несколько байт.
![](https://i.imgur.com/Tr7jUMs.png)

Давайте посмотрим, что находится у него внутри

```clike
[0x000012d2]> pdf
; DATA XREF from entry0 @ 0x10a1
┌ 27: int main (int argc, char **argv, char **envp);
│ 0x000012d2 f30f1efa endbr64
│ 0x000012d6 55 push rbp
│ 0x000012d7 4889e5 mov rbp, rsp
│ 0x000012da 488d3d230d00. lea rdi, [0x00002004] ; "Hello world!"
│ 0x000012e1 e87afdffff call fcn.00001060
│ 0x000012e6 b800000000 mov eax, 0
│ 0x000012eb 5d pop rbp
└ 0x000012ec c3 ret
[0x000012d2]> s fcn.00001060
[0x00001060]> pdf
; CALL XREF from main @ 0x12e1
┌ 11: fcn.00001060 ();
│ 0x00001060 f30f1efa endbr64
└ 0x00001064 f2ff255d2f00. bnd jmp qword [reloc.puts] ; [0x3fc8:8]=0x1030 fcn.00001030 ; "0\x10"
[0x00001060]>
```

Странно, судя по main'у бинарь должен просто вывести hello word, однако ничего подобного не происходит и близко. Попробуем поставить бряку на entry point и запустить. Иииии... прога не дойдет до нашей бряки, но будет выполнять какой-то код, а известных мне инитов или чего-то что должно запускаться перед entry нет (на самом деле есть читайте [Эпилог](#Эпилог)), вот так мистика.
Ладно допустим, что путем странных манипуляций еще до захода в entry исполняется какой-то другой код. Попробуем запустить приложуху под strace, чтобы хотя бы примерно понять что и как.

По логам последнего видим, что бинарь выполняет как-то слишком много работы с памятью вначале (использует syscall'ы `mmap` и `mprotect`), что может подвести нас к идее, что он запакован. Но проверив с помощью die, ничего интересного не получаем.

![](https://i.imgur.com/trHH7dN.png)

Хммм... Окей, попробуем продебажить радаром до тех пор, пока бинарь не начнет читать наш инпут. Я использовал для этого команду `dcs read`, которая запускает бинарь до вызова сискола read и ждал пока первый операнд будет равен 0, что означает что он читает из `stdin`.

После окажемся где-то в районе 0x7f... адресов, давайте проверим в каком "мапе" мы очутились

```clike
[0x7fb2b1ac72ee]> dm
0x00007fb2b1846000 - 0x00007fb2b1886000 - usr 256K s rw- unk0 unk0
0x00007fb2b1886000 - 0x00007fb2b18ab000 - usr 148K s r-- /usr/lib/x86_64-linux-gnu/libc-2.31.so /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007fb2b18ab000 - 0x00007fb2b1a23000 - usr 1.5M s r-x /usr/lib/x86_64-linux-gnu/libc-2.31.so /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007fb2b1a23000 - 0x00007fb2b1a6d000 - usr 296K s r-- /usr/lib/x86_64-linux-gnu/libc-2.31.so /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007fb2b1a6d000 - 0x00007fb2b1a6e000 - usr 4K s --- /usr/lib/x86_64-linux-gnu/libc-2.31.so /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007fb2b1a6e000 - 0x00007fb2b1a71000 - usr 12K s r-- /usr/lib/x86_64-linux-gnu/libc-2.31.so /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007fb2b1a71000 - 0x00007fb2b1a74000 - usr 12K s rw- /usr/lib/x86_64-linux-gnu/libc-2.31.so /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007fb2b1a74000 - 0x00007fb2b1a78000 - usr 16K s rw- unk1 unk1
0x00007fb2b1a92000 - 0x00007fb2b1a93000 - usr 4K s r-- /memfd: (deleted) /memfd: (deleted)
0x00007fb2b1a93000 - 0x00007fb2b1a94000 - usr 4K s r-- /usr/lib/x86_64-linux-gnu/ld-2.31.so /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x00007fb2b1a94000 - 0x00007fb2b1ab7000 - usr 140K s r-x /usr/lib/x86_64-linux-gnu/ld-2.31.so /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x00007fb2b1ab7000 - 0x00007fb2b1abf000 - usr 32K s r-- /usr/lib/x86_64-linux-gnu/ld-2.31.so /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x00007fb2b1abf000 - 0x00007fb2b1ac0000 - usr 4K s --- unk2 unk2
0x00007fb2b1ac0000 - 0x00007fb2b1ac1000 - usr 4K s r-- /usr/lib/x86_64-linux-gnu/ld-2.31.so /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x00007fb2b1ac1000 - 0x00007fb2b1ac2000 - usr 4K s rw- /usr/lib/x86_64-linux-gnu/ld-2.31.so /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x00007fb2b1ac2000 - 0x00007fb2b1ac3000 - usr 4K s rw- unk3 unk3
0x00007fb2b1ac4000 - 0x00007fb2b1ac6000 - usr 8K s rw- unk4 unk4
0x00007fb2b1ac6000 - 0x00007fb2b1ac7000 - usr 4K s r-- unk5 unk5
0x00007fb2b1ac7000 - 0x00007fb2b1aca000 * usr 12K s r-x unk6 unk6
0x00007fb2b1aca000 - 0x00007fb2b1acc000 - usr 8K s r-- unk7 unk7
0x00007fb2b1acc000 - 0x00007fb2b1ad0000 - usr 16K s rw- unk8 unk8
0x00007ffc88eda000 - 0x00007ffc88efb000 - usr 132K s rw- [stack] [stack]
0x00007ffc88f49000 - 0x00007ffc88f4d000 - usr 16K s r-- [vvar] [vvar]
0x00007ffc88f4d000 - 0x00007ffc88f4f000 - usr 8K s r-x [vdso] [vdso]
0xffffffffff600000 - 0xffffffffff601000 - usr 4K s --x [vsyscall] [vsyscall] ; map.vsyscall_.__x
```

Вау, сколько тут всего намапилось интересного еще до выполнения основного бинаря.
Пока дебажил, еще случайно нашел пару строчек, которые содержали "upx", что приводит к мысле что в какой-то момент тут распаковывался upx файл и все эти подозрительные сегменты/мапы вызваны частично и им. Ну что же, поищем elf header

```clike
[0x7fb2b1ac72ee]> / \x7fELF
0x7fb2b1886000 hit11_0 .\u007fELF>.
0x7fb2b1a92000 hit11_1 .\u007fELF>.
0x7fb2b1a92110 hit11_2 .!\u007fELF>`wv.
0x7fb2b1a93000 hit11_3 .v%4!15\u007fELF>.
0x7fb2b1a9b41e hit11_4 .?vH?TH\u007fELFI9FpA.
0x7fb2b1a9b451 hit11_5 .A9~A~\u007fELFA~r.
0x7fb2b1a9b7a2 hit11_6 .jDfH\u007fELFI9F|.
0x7fb2b1a9c1c9 hit11_7 .?vH?H\u007fELFI9FA.
0x7fb2b1a9c1fc hit11_8 .A9~A~\u007fELFgA~.
0x7fb2b1a9c57a hit11_9 .DDH\u007fELFI9FOg.
0x7fb2b1ab74c0 hit11_10 .0-00\u007fELF.
0x7fb2b1ac6000 hit11_11 .\u007fELF>.
0x7ffc88ef9678 hit11_12 .@\u007fELF>.
0x7ffc88f4d000 hit11_13 .\u007fELF>.
```

Сопоставив эти данные с известными мапами, придем к выводу, что нам скорее всего нужны мапы с unk5 по unk8, так как в них и находится бинарник, который выполняет всю "полезную" работу. Сдампим их

```clike
[0x7fb2b1acf6c0]> pr 0x00007fb2b1ad0000-0x00007fb2b1ac6000 @ 0x00007fb2b1ac6000 > unpack_server.elf
```

Попытка запуска распакованного бинаря ничего не даст,
![](https://i.imgur.com/vU3bPct.png)

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

```clike
[0x000013c8]> afl
0x00001060 1 47 entry0
0x000013c8 41 1152 main
0x00001040 1 11 fcn.00001040
0x00001090 4 41 -> 34 fcn.00001090
0x00001209 6 122 fcn.00001209
0x00001185 11 132 fcn.00001185
0x000012a9 1 43 fcn.000012a9
0x000012d4 1 35 fcn.000012d4
0x00001316 1 44 fcn.00001316
0x000012f7 1 31 fcn.000012f7
0x00001149 1 60 fcn.00001149
0x00001342 4 134 fcn.00001342
0x00003894 15 1264 fcn.00003894
0x000019a4 1 22 fcn.000019a4
0x000019ba 4 75 fcn.000019ba
0x000018c0 1 32 fcn.000018c0
0x000018e0 1 24 fcn.000018e0
0x00001961 4 67 fcn.00001961
0x00001848 4 120 fcn.00001848
0x00002e6f 15 1241 fcn.00002e6f
0x000018f8 4 105 fcn.000018f8
0x00003d84 1 22 fcn.00003d84
0x00001a05 1 28 fcn.00001a05
0x00001a21 4 286 fcn.00001a21
0x00001050 1 11 fcn.00001050
0x00001fc1 37 1250 fcn.00001fc1
0x00001f60 1 97 fcn.00001f60
0x00001e99 4 199 fcn.00001e99
0x00001b3f 4 288 fcn.00001b3f
0x000024a3 34 1152 fcn.000024a3
0x00001d7b 4 286 fcn.00001d7b
0x00001c5f 4 284 fcn.00001c5f
0x00001000 3 27 segment.LOAD1
0x00001287 1 34 fcn.00001287
0x00002927 21 1352 fcn.00002927
0x0000334c 21 1352 fcn.0000334c
0x00001030 2 31 -> 28 fcn.00001030
0x00001100 5 57 -> 54 fcn.00001100
0x00001140 5 137 -> 60 fcn.00001140
0x00001283 1 4 fcn.00001283
0x00002923 1 4 fcn.00002923
0x00003348 1 4 fcn.00003348
0x00003da0 4 101 fcn.00003da0
0x00003e10 1 5 fcn.00003e10
0x00003e18 1 13 fcn.00003e18
```

[Анпакнутый elf](https://github.com/osogi/writeups/blob/main/ctfcup2021/Simple_Server/attachment/unpack_serv.elf), если кому-то нужно

### Завязка (реверсинг бинарника)
Я конечно мазохист, но не настолько чтобы реверсить что-то в радаре, если это можно сделать в иде, так что погнали закинем распакованный бин в иду, и чуть пореверсим.
Тут я быстро расскажу о реверсинге некоторых функций и как это делалось
#### sub_1185 (imul)
```clike
unsigned __int64 __fastcall sub_1185(unsigned __int64 a1, unsigned __int64 a2)
{
unsigned __int64 v5; // [rsp+10h] [rbp-10h]

v5 = 0LL;
while ( a2 )
{
if ( (a2 & 1) != 0 )
{
if ( v5 < -59LL - a1 )
v5 += a1;
else
v5 -= -59LL - a1;
}
if ( a1 < -59LL - a1 )
a1 *= 2LL;
else
a1 = 2 * a1 + 59;
a2 >>= 1;
}
return v5;
}
```
Функция небольшая, но с наскоку трудно понять, что она делает. Но если же подправить и засунуть этот код в какую-нибудь gcc, чтобы потыкать его, то можно быстро определить, что это простое умножение двух чисел. Обзовем ее `imul`.

#### sub_1209 (pow)
```clike
__int64 __fastcall pow(__int64 a1, unsigned __int64 a2)
{
__int64 v3; // rax
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

if ( a2 == 1 )
return a1;
v3 = pow(a1, a2 >> 1);
v4 = imul(v3, v3);
if ( (a2 & 1) != 0 )
v4 = imul(v4, a1);
return v4;
}
```
Просто видно, что это бинарное возведение в степень.

#### Syscall funs
```clike
__int64 __fastcall read(int fd, void *buf, size_t count)
{
__asm { syscall; LINUX - }
return 1LL;
}
```
В бинаре есть ряд функций имеющих похожую структуру, просто смотрим асм, какой сискол вызывают, и настраиваем функцию под его аргументы.

#### sub_1149 (malloc)
```clike
void *__fastcall _malloc(int a1)
{
void *v2; // [rsp+10h] [rbp-8h]

v2 = (void *)((unsigned int)offset_for_empty_data + some_adr_for_malloc);
offset_for_empty_data += a1;
return v2;
}
```
По тому где вызывается функция и что делает, можно легко провести параллели с сишным маллоком.

#### sub_3894 и sub_2E6F(decode и encdode)
```clike
unsigned __int64 __fastcall decode(unsigned int *a1, __int64 a2, _BYTE *a3, unsigned int a4)
{
__int64 v4; // rdx
unsigned __int64 result; // rax
int i; // [rsp+28h] [rbp-258h]
int j; // [rsp+2Ch] [rbp-254h]
int k; // [rsp+30h] [rbp-250h]
unsigned int v10; // [rsp+34h] [rbp-24Ch]
__int64 v11; // [rsp+40h] [rbp-240h]
__int64 v12; // [rsp+48h] [rbp-238h]
int v13[134]; // [rsp+50h] [rbp-230h] BYREF
unsigned __int64 v14; // [rsp+268h] [rbp-18h]

v14 = __readfsqword(0x28u);
memset(v13, 0, 0x210uLL);
crypt_keygen((__int64)v13, a2, (__int64)a3, a4);
v11 = 0LL;
v12 = 0LL;
for ( i = 31; i >= 0; --i )
{
if ( i > 30 )
{
*a1 ^= v13[128];
a1[1] ^= v13[129];
a1[2] ^= v13[130];
a1[3] ^= v13[131];
}
else
{
a1[2] = a1[3] ^ rol4(a1[2], 10) ^ (a1[1] << 7);
*a1 = a1[3] ^ rol4(*a1, 27) ^ a1[1];
a1[3] = rol4(a1[3], 25);
a1[1] = rol4(a1[1], 31);
a1[3] ^= a1[2] ^ (8 * *a1);
a1[1] ^= *a1 ^ a1[2];
a1[2] = rol4(a1[2], 29);
*a1 = rol4(*a1, 19);
}
for ( j = 0; j <= 31; ++j )
{
v10 = dword_6820[16 * (i % 8)
+ ((4 * (unsigned __int8)(a1[2] >> j)) & 4 | (2 * (unsigned __int8)(a1[1] >> j)) & 2 | (*a1 >> j) & 1 | (8 * (unsigned __int8)(a1[3] >> j)) & 8)];
LODWORD(v11) = v11 | ((v10 & 1) << j);
HIDWORD(v11) |= ((v10 >> 1) & 1) << j;
LODWORD(v12) = v12 | (((v10 >> 2) & 1) << j);
HIDWORD(v12) |= ((v10 >> 3) & 1) << j;
}
for ( k = 0; k <= 3; ++k )
{
a1[k] = *((_DWORD *)&v11 + k) ^ v13[4 * i + k];
*((_DWORD *)&v11 + k) = 0;
}
}
v4 = *((_QWORD *)a1 + 1);
*(_QWORD *)a3 = *(_QWORD *)a1;
*((_QWORD *)a3 + 1) = v4;
result = __readfsqword(0x28u) ^ v14;
if ( result )
result = stack_fail();
return result;
}
```

```clike
unsigned __int64 __fastcall encdode(unsigned int *mes, __int64 key, _BYTE *dest, unsigned int len)
{
unsigned int v4; // ebx
int v5; // ebx
int v6; // eax
unsigned int v7; // ebx
int v8; // ebx
int v9; // eax
int v10; // eax
int v11; // eax
__int64 v12; // rdx
unsigned __int64 result; // rax
int i; // [rsp+28h] [rbp-258h]
int j; // [rsp+2Ch] [rbp-254h]
int k; // [rsp+30h] [rbp-250h]
unsigned int v18; // [rsp+34h] [rbp-24Ch]
__int64 v19; // [rsp+40h] [rbp-240h]
__int64 v20; // [rsp+48h] [rbp-238h]
int main_key[134]; // [rsp+50h] [rbp-230h] BYREF
unsigned __int64 v22; // [rsp+268h] [rbp-18h]

v22 = __readfsqword(0x28u);
memset(main_key, 0, 0x210uLL);
crypt_keygen((__int64)main_key, key, (__int64)dest, len);
v19 = 0LL;
v20 = 0LL;
for ( i = 0; i <= 31; ++i )
{
for ( j = 0; j <= 3; ++j )
{
*((_DWORD *)&v19 + j) = main_key[4 * i + j] ^ mes[j];
mes[j] = 0;
}
for ( k = 0; k <= 31; ++k )
{
v18 = dword_6020[16 * (i % 8)
+ ((4 * (unsigned __int8)((unsigned int)v20 >> k)) & 4 | (2 * (unsigned __int8)(HIDWORD(v19) >> k)) & 2 | ((unsigned int)v19 >> k) & 1 | (8 * (unsigned __int8)(HIDWORD(v20) >> k)) & 8)];
*mes |= (v18 & 1) << k;
mes[1] |= ((v18 >> 1) & 1) << k;
mes[2] |= ((v18 >> 2) & 1) << k;
mes[3] |= ((v18 >> 3) & 1) << k;
}
if ( i > 30 )
{
*mes ^= main_key[128];
mes[1] ^= main_key[129];
mes[2] ^= main_key[130];
mes[3] ^= main_key[131];
}
else
{
v4 = mes[1];
v5 = rol4(*mes, 13) ^ v4;
v6 = v5 ^ rol4(mes[2], 3);
mes[1] = rol4(v6, 1);
v7 = mes[3];
v8 = rol4(mes[2], 3) ^ v7;
v9 = v8 ^ (8 * rol4(*mes, 13));
mes[3] = rol4(v9, 7);
v10 = rol4(*mes, 13);
*mes = rol4(v10 ^ mes[1] ^ mes[3], 5);
v11 = rol4(mes[2], 3);
mes[2] = rol4(v11 ^ mes[3] ^ (mes[1] << 7), 22);
}
}
v12 = *((_QWORD *)mes + 1);
*(_QWORD *)dest = *(_QWORD *)mes;
*((_QWORD *)dest + 1) = v12;
result = __readfsqword(0x28u) ^ v22;
if ( result )
result = stack_fail();
return result;
}
```
Эти две функции существуют, выполняют какую-то крипту. И так как одна похожа на перевернутую другую, то я решил проверить с помощью дебага радаром, обратные ли они, и это оказалось так. Так что было решено что одна криптит, а другая декриптит.

#### Main
После реверса всех "дочерних" функций получаем примерно такой main

```clike
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // edx
int v4; // eax
int v5; // esi
_BYTE dest[2]; // [rsp+6h] [rbp-5Ah] BYREF
_BYTE count_1[4]; // [rsp+8h] [rbp-58h] BYREF
unsigned int count; // [rsp+Ch] [rbp-54h] BYREF
__int64 first_inp; // [rsp+10h] [rbp-50h] BYREF
unsigned __int64 rand; // [rsp+18h] [rbp-48h] BYREF
__int64 mes; // [rsp+20h] [rbp-40h] BYREF
unsigned __int64 a2; // [rsp+28h] [rbp-38h]
_BYTE *buf; // [rsp+30h] [rbp-30h]
_BYTE *important_data; // [rsp+38h] [rbp-28h]
_BYTE *user_secret; // [rsp+40h] [rbp-20h]
void *out; // [rsp+48h] [rbp-18h]
char *pathname; // [rsp+50h] [rbp-10h]
unsigned __int64 v18; // [rsp+58h] [rbp-8h]

v18 = __readfsqword(0x28u);
first_inp = 0LL;
read(0, &first_inp, 8uLL);
rand = 0LL;
get_random(&rand, 8uLL, v3);
mes = pow(0xF549E9B5207189BCLL, rand);
write(1, &mes, 8uLL);
a2 = 0LL;
a2 = pow(first_inp, rand);
key_ = _malloc(16);
crypt_xor(key_, a2);
while ( 1 )
{
while ( 1 )
{
buf = _malloc(32);
important_data = _malloc(32);
read(0, buf, 0x20uLL);
decode((unsigned int *)buf, (__int64)key_, important_data, 0x10u);
decode((unsigned int *)buf + 4, (__int64)key_, important_data + 16, 0x10u);
if ( *important_data != 0x22 || important_data[1] != 0x33 )
exit();
v4 = (unsigned __int8)important_data[2];
if ( v4 != 4 )
break;
if ( fd )
exit();
if ( !flag_ok )
exit();
count = 0;
_memcpy(&count, important_data + 3, sizeof(count));
if ( count > 0x18 )
exit();
user_secret = _malloc(count);
_memcpy(user_secret, important_data + 7, count);
if ( !check_secret(user_secret, count) )
exit();
out = encode_flag(glob_for_flag, leng_of_flag);
write(1, out, (leng_of_flag & 0xFFFFFFF0) + 16);
}
if ( (unsigned __int8)important_data[2] > 4u )
break;
switch ( v4 )
{
case 3:
if ( !fd || fd == -1 )
exit();
close(fd);
fd = 0;
break;
case 1:
count = 0;
_memcpy(&count, important_data + 3, sizeof(count));
if ( count > 0x18 )
exit();
pathname = (char *)_malloc(count);
v5 = (_DWORD)important_data + 7;
_memcpy(pathname, important_data + 7, count);
if ( fd )
exit();
fd = fopen(pathname, v5);
if ( fd == -1 )
exit();
break;
case 2:
*(_WORD *)dest = 0;
*(_DWORD *)count_1 = 0;
count = 0;
_memcpy(count_1, important_data + 3, sizeof(count_1));
if ( *(_DWORD *)count_1 > 0x18u )
exit();
_memcpy(dest, important_data + 7, sizeof(dest));
_memcpy(&count, important_data + 9, sizeof(count));
if ( count > 0x80 )
exit();
glob_for_flag = (unsigned int *)_malloc(count);
if ( !fd || fd == -1 )
exit();
read(fd, glob_for_flag, count);
flag_ok = 1;
leng_of_flag = count;
break;
default:
goto LABEL_38;
}
}
LABEL_38:
exit();
}
```

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

| Operation\Offset | +2 (operation code) | +3 | +7 | +9 |
|----------------------------------------------|---------------------|-------------------------------|---------------------------|-------------------------|
| fopen | 1 | len of path to file (4 bytes) | filepath (len bytes) | - |
| read file | 2 | some padding <=0x18 (4 bytes) | another padding (2 bytes) | how much read (4 bytes) |
| close
(simply close file, need for last) | 3 | - | - | - |
| write data from file | 4 | len of secret (4 bytes) | secret (len bytes) | |

[ДБ иды с этим файлом](https://github.com/osogi/writeups/blob/main/ctfcup2021/Simple_Server/attachment/unpack_serv.i64)
### Кульминация (кодинг "клиента")
Вообще зная протокол, написать клиент не должно составить труда. Могут возникнуть только проблемы с тем, что мы не знаем степень, в которую возводится число, используемое для генерации ключа. Но ее можно либо попробовать высчитать, на основе известных данных, либо же заслать 1 или 0, так как что первое, что последнее в любой степени дают сами себя -> мы всегда будем знать, число которое использовалось для генерации ключа.

И так же возникают трудности с автоматическим декриптом и инкриптом сообщений. Что бы решить эту проблему у меня в голове было 3 способа:
- воспользоваться фридой
- заинжектить so
- воспользоваться ~~r2pipe~~ rzpipe

Первый вариант я не выбрал так как, до сих пор не могу в фриду. Второй вариант мне не понравился, так как нужно писать сокеты внутри so, либо шаманить с обменом инфой между so и сторонним приложением. Так что я выбрал самый безболезненный, но костыльный вариант - rzpipe. И конечно буду кодить на питоне так как это быстрее всего. Разберем как же это все было реализовано.
Для начала разберем конструктор и какие переменные вводятся
```python
class Crypter:
# заводим класс криптор
def __init__(self, file_name):
rflags = ["-dw"]
r = rzpipe.open(file_name, flags=rflags)
# запускаем ризин в дебаг и райт моде
for _ in range(5):
r.cmd("dcs read")
# ждем пятого рида, так как пятый рид всегда наш
# тот который находится внутри распакованого бинарника

a=r.cmd("dr rip")
a = int(a, 16)
# получаем текущий рип, чтобы посчитать смещения
# # + основные адреса

self.off = a-0x12EE
# в бинаре рид назодится по адресу 0x12EE
# путем несложной математики находим смещение
self.r=r


self.encode_addr=0x2E6F+self.off
self.encode_addr_end = self.encode_addr+0x4d8

self.decode_addr=0x3894+self.off
self.decode_addr_end=self.decode_addr+0x4ef

self.keyfun_addr=0x1342+self.off
self.keyfun_addr_end=0x13C7+self.off
# высчитываем адреса нужных функций и их конца

m =0x9038+self.off
self.mem_addr=int(r.cmdj(f"?j [{m}]")["uint64"])
# находим адрес используемый кастомным malloc'ом
# будем использовать его, чтобы знать куда можно писать
```

Дальше разберу реализацию одной из функций, остальные работают схожим образом

```python
def encrypt(self, message, key):
r=self.r
# получаем туже сессию ризина

r.cmd(f"dr rip={self.encode_addr}")
# прыгаем на адрес функции

r.cmd(f"dr rcx=0x10") #len
# передаем в аргументы длину декодируемого сообщения

dest_addr = self.mem_addr+0x1000
r.cmd(f"dr rdx={dest_addr}")
# передаем адрес куда сохранить расшифрованное сообщение

key_addr = self.mem_addr+0x1100
r.cmd(f"dr rsi={key_addr}")
# передаем адрес по которому будет находится ключ

hexkey=key.to_bytes(16, byteorder=bo).hex()
r.cmd(f"wx {hexkey} @ {key_addr}")
# записываем ключ по этому адресу

mes_addr = self.mem_addr+0x1200
r.cmd(f"dr rdi={mes_addr}")
# передаем адрес зашифрованного сообщения

hexmes=message.hex()
r.cmd(f"wx {hexmes} @ {mes_addr}")
# записывем зашифрованное сообщение по этому адресу

r.cmd(f"dcu {self.encode_addr_end}")
# продолжаем выполнение до конца функции

b = r.cmd(f"p8 16 @ {dest_addr}")
# достаем значение по адресу куда попадает расшифрованное сообщение
return bytes.fromhex(b)
```

Полный код клиента можно найти [тут](https://github.com/osogi/writeups/blob/main/ctfcup2021/Simple_Server/solution/rizin_serv.py)
*P.S. Чейнд не бей за использрвание pwntools, я хотел заfb'шить таск, а это единственное, что я помнил*

### Развязка (получение "секрета")
Вообще, изначально когда реверсил таск на ctf, я пропустил, что в протоколе идет проверка секрета и все такое, и заметил это в самом конце. Так что в райтапе тоже было решено это отложить на финал.

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

```clike
__int64 __fastcall secret_sub(_BYTE *secr, int len, unsigned int result)
{
while ( len-- )
result = (result << 8) ^ some_array[(unsigned __int8)(HIBYTE(result) ^ *secr++)];
//some_array - массив из 256 dword'ов
return result;
}

_BOOL8 __fastcall check_secret(_BYTE *secr, unsigned int len)
{
return (unsigned int)secret_sub(secr, len, 0xFFFFFFFF) == 0xDF705098;
}
```

В принципе тут почти сразу видно, что это можно взломать, так как нам известен желаемый результат (`0xDF705098`) и при `result << 8` последний байт 0, то можно найти среди `some_array` элемент, который тоже оканчивается на тот же байт, что и желаемый результат. Дальше сделать так 4 раза (так как 4 байта) + сделать поправочку на ксор, оформить это все в [скрипт](https://github.com/osogi/writeups/blob/main/ctfcup2021/Simple_Server/attachment/hack_secret.py) и вуаля мы подобрали нужный секрет, и им оказался `\xca\xdb\x88\x2d`

Добавляем наш секрет в клиент, запускаем его. На локалке все робит, отлично, запускаем на сервер и получаем флаг.

![](https://i.imgur.com/oKT420I.png)

## Решение

- понять, что по умолчанию в данном нам бинаре, почти нет ничего интересного
- найти, расшифровать, сдампить или еще каким-то образом получить ["настоящий" бинарь]((https://github.com/osogi/writeups/blob/main/ctfcup2021/Simple_Server/attachment/unpack_serv.elf) )
- разреверсить его ([файл для иды с +- красивыми именами]((https://github.com/osogi/writeups/blob/main/ctfcup2021/Simple_Server/attachment/unpack_serv.i64) ))
- разобраться в протоколе
- понять, что крипто-функции взаимно обратные
- написать [клиент](https://github.com/osogi/writeups/blob/main/ctfcup2021/Simple_Server/solution/rizin_serv.py) для получения флага
- каким-то образом реализовать крипто-функции сервера
- реализовать протокол
- найти секрет для получения флага ([скрипт]((https://github.com/osogi/writeups/blob/main/ctfcup2021/Simple_Server/attachment/hack_secret.py)))
- получить флаг

Авторское решение туть -> https://github.com/acisoru/ctfcup-21-quals/blob/main/tasks/reverse/simple-protocol/README.md

## Эпилог
Я посмотрел, потыкал почему у нас запускается какой-то другой код в начале бинаря, и прога даже не доходит до entry point'а. Как оказалось это вызвано тем что в коде был преинит участок (подробнее о всяких штуках происходящих перед загрузкой main'а можно почитать тут https://habr.com/ru/post/339698/), и радар хоть его и обнаружил, но справился плоховато: никак не обозвал эту функцию + плохо ее проанализировал - сильно ее обрезал (обрезал прям всю), в отличие от ризина, который справился с этим делом лучше
![](https://i.imgur.com/uRZO70G.png)

В очередной раз доказано, что ризин>радара. На самом деле у меня иногда тупит и тот и другой, так что я пользуюсь ими посменно, на одном таске могу по приколу юзать то одно, то другое.

---
Кого-то еще могло заинтересовать почему на картинке, превьюшке, какие-то аниме чувачки и как это связано с таском. Ну вот, это все из-за того, что я загуглил "protocol" нашел такую пикчу к какой-то игре, и мне понравилось как это выглядит.
![](https://i.imgur.com/o0yx6vq.jpg)

Original writeup (https://hackmd.io/@osogi/Simple_Protocol).