Нужно обойти проверки, чтобы получить флаг.
nc 109.233.56.90 11572
Solution
Гляну строки в файле.

Воу. /dev/urandom я тут увидеть не ожидал.
Пойду реверсить.
Анализ read_secrer
Начну с функции, которая указывает на /dev/urandom:
int64_t read_secret()
{
secret = malloc(50);
FILE* fp = fopen("/dev/urandom", "r");
fread(secret, 32, 1, fp);
return fclose(fp);
}
О как. Значит secret - это буфер, который хранит рандомные значения из /dev/urandom.
/dev/urandom- генератор псевдослучайных числе вlinux.
Эта функция вызывается только в main. Перейду к ней.
Анализ main
Много понаписано:
int32_t main(int32_t argc, char** argv, char** envp)
{
void* fsbase;
int64_t rax = *(uint64_t*)((char*)fsbase + 0x28);
setvbuf(stdin, nullptr, 2, 0);
setvbuf(stdout, nullptr, 2, 0);
read_secret();
puts("How many your secrets you wanna store: ");
int32_t var_134;
__isoc99_scanf("%d", &var_134);
getchar();
int32_t result;
if (var_134 < 0 || var_134 > 0x20)
{
puts("Wrong input!\nBye!");
result = 0;
}
else
{
for (int32_t i = 0; i < var_134; i += 1)
{
puts("enter your secret: ");
*(uint64_t*)(((int64_t)i << 3) + &global) = malloc(0x20);
fgets(*(uint64_t*)(((int64_t)i << 3) + &global), 0x20, stdin);
}
puts("Enter secret idx to check: ");
int32_t var_130;
__isoc99_scanf("%d", &var_130);
getchar();
int64_t rax_15 = *(uint64_t*)(((int64_t)var_130 << 3) + &global);
int32_t rax_17;
if (rax_15)
rax_17 = memcmp(rax_15, secret, 0x32);
if (!rax_15 || rax_17)
{
puts("Nope...");
result = 0;
}
else
{
puts("YOU WIN!");
FILE* fp = fopen("flag.txt", "r");
char var_118[0x108];
memset(&var_118, 0, 0x100);
fread(&var_118, 0x100, 1, fp);
fclose(fp);
puts(&var_118);
result = 1;
}
}
if (rax == *(uint64_t*)((char*)fsbase + 0x28))
return result;
__stack_chk_fail();
/* no return */
}
Буду разбирать по кускам.
Программа просит нас ввести число секретов, которые мы хотим хранить. При этом есть проверка на максимальное значение:
puts("How many your secrets you wanna store: ");
int32_t input_count;
__isoc99_scanf("%d", &input_count);
getchar();
int32_t result;
if (input_count < 0 || input_count > 32)
{
puts("Wrong input!\nBye!");
result = 0;
}
Далее мы должны записать эти секреты:
for (int32_t i = 0; i < input_count; i += 1) {
puts("enter your secret: ");
*(uint64_t*)(((int64_t)i << 3) + &global) = malloc(32);
fgets(*(uint64_t*)(((int64_t)i << 3) + &global), 32, stdin);
}
А теперь идет самый интересный функционал - просмотр секретов.
puts("Enter secret idx to check: ");
int32_t indx;
__isoc99_scanf("%d", &indx);
getchar();
int64_t secret_p = *(uint64_t*)(((int64_t)indx << 3) + &global);
int32_t rax_16;
if (secret_p)
rax_16 = memcmp(secret_p, secret, 50);
А вот тут уже нет никакой проверки индексов. Если глянуть память, то можно увидеть, что global лежит чуть выше, чем secret. Так как в бинаре нет никой проверки индекса, то мы можем указать такой индекс, что укажет на secret в памяти. Подробнее про адресную арифметику я писал вот тут Easy Overflow 2.

Стряпаем пейлоад
Адрес global - 0x602040, адрес secret - 0x606140. Значит их разделяет всего каких-то 16640 байт. Так как указатель у нас занимает 8 байт памяти, тогда получаем индекс следующим образом 16640 / 8 = 2080.
Го тестить)
Не забываем, что нумирация массивов начинается с 0. А я забыл:


Со всеми бывает. Еще раз:

Успех. Пошел забирать флаг:

А вот и он:
spbctf{well_I_should_not_forget_to_check_indexes}