Нужно обойти проверки, чтобы получить флаг.

nc 109.233.56.90 11572

Solution

Гляну строки в файле.

IMG

Воу. /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.

IMG

Стряпаем пейлоад

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

Го тестить)

Не забываем, что нумирация массивов начинается с 0. А я забыл:

IMG

IMG

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

IMG

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

IMG

А вот и он:

spbctf{well_I_should_not_forget_to_check_indexes}