zero_to_hero

Descrição do Desafio:

Autor: Claude Plataforma: PicoCTF Categoria: Binary Exploitation Dificuldade: Difícil Data: 2019 Descrição:

Now you're really cooking. Can you pwn this service?

Passo a Passo da Solução

1. Análise do arquivo fornecido

O binário fornecido vem com as seguintes proteções: Full RELRO, Canary, e NX. Com isso já podemos descartar a opção de reescrever a tabela .GOT do binário.

Analisando pelo Ghidra, vemos que as funções não vem bem definidas, mas com um pouco de análise já é possível achar a função de entrada e as outras principais. Olhando a função principal, já identificamos uma dica, que é o vazamento do endereço da system.

main.c
void main(void)

{
  ssize_t sVar1;
  long in_FS_OFFSET;
  int local_2c;
  char local_28 [24];
  undefined8 local_10;
  
  local_10 = *(undefined8 *)(in_FS_OFFSET + 0x28);
  setvbuf(stdin,(char *)0x0,2,0);
  setvbuf(stdout,(char *)0x0,2,0);
  setvbuf(stderr,(char *)0x0,2,0);
  puts("From Zero to Hero");
  puts("So, you want to be a hero?");
  sVar1 = read(0,local_28,20);
  local_28[sVar1] = '\0';
  if (local_28[0] != 'y') {
    puts("No? Then why are you even here?");
                    /* WARNING: Subroutine does not return */
    exit(0);
  }
  puts("Really? Being a hero is hard.");
  puts("Fine. I see I can\'t convince you otherwise.");
  printf("It\'s dangerous to go alone. Take this: %p\n",system); <-- Endereço da system vazando.
  while( true ) {
    while( true ) {
      menu();
      printf("> ");
      local_2c = 0;
      __isoc99_scanf(&%d,&local_2c);
      getchar();
      if (local_2c != 2) break;
      removePower();
    }
    if (local_2c == 3) break;
    if (local_2c != 1) goto LAB_00400dce;
    addPower();
  }
  puts("Giving up?");
LAB_00400dce:
                    /* WARNING: Subroutine does not return */
  exit(0);
}

Tirando a função menu(), temos outras duas funções principais, a addPower() e a removePower().

Olhando para elas podemos assumir algumas coisas:

  • removePower: aqui verificamos que podemos ter 7 super poderes ao mesmo tempo, podemos ver que os poderes (na verdade o endereço do chunk deles) é armazenado em uma variável global que eu renomeei como powers, e o mais importante, temos uma vulnerabilidade em que após liberarmos o chunk, com a função free(), o endereço na variável powers não é anulado, fazendo com que seja possível realizar um double-free.

  • addPower: aqui verificamos que podemos digitar o tamanho do super poder (chunk), sendo o máximo 1032 (importante), e podemos escrever nesse tamanho. E aí está outra vulnerabilidade, quando escrevemos uma string, o final dela sempre deve ser um byte nulo \0, e no código nós podemos escrever exatamente a quantidade que pedimos, e em seguida o \0 é colocado. Então, se escrevermos no tamanho total, o byte nulo é colocado em uma região que não é da string.

2. Exploit

Pelo o que foi analisado, já podemos ter uma ideia que o exploit deve ser algo relacionado a HEAP, então o mais importante é saber em qual versão da libc nós estamos. No caso, ela já vem junto com o binário, e é a versão 2.29.

Resumindo essa versão, a tcachebin já foi implementada, e já contém suas verificações de segurança contra double-free. Aqui já podemos assumir algumas coisas:

  • Não podemos realizar double-free na mesma bin da tcache.

  • Não podemos utilizar a fastbin pois só podemos ter 7 poderes.

Então como vamos realizar o exploit? Analisando um pouco, é possível chegar na conclusão que podemos fazer um chunk ter 2 tamanhos ao mesmo tempo, como?

Bom, foi visto que podemos escrever o tamanho total do chunk, e com isso o \0 é colocado no próximo chunk, alterando o campo size dele. Mas temos que ficar atentos a algumas coisas:

  • O chunk que será escrito totalmente deve ser do tamanho máximo, 1032, pois dessa forma a malloc() não irá realizar o alinhamento (aumentar o tamanho para os metadados).

  • O segundo chunk deve ter tamanho acima de 0x200 e abaixo de 0x300, pois quando ele for envenenado, o seu tamanho passará a ser 0x200, que será devolvido pela malloc() quando for requisitado um chunk com tamanho menor do que 512 (não tão menor).

  • É necessário alocar o primeiro chunk, depois alocar o segundo, e só após isso começar a realizar os free(), para garantir que eles estarão em sequência na memória.

Com isso conseguimos realizar um double-free, forçando o chunk ter 2 tamanhos diferentes, e também conseguimos manipular o fd (ponteiro para o próximo chunk na bin). Mas como vamos conseguir chegar na função win()?

É aí que entra uma carta na manga, os ponteiros __free_hook e __malloc_hook da libc. Esses ponteiros são usados nas suas respectivas funções, com a função de garantir a execução de uma outra função específica, dessa forma, se alterarmos eles (apenas 1), podemos alterar o fluxo de execução para alguma função win(). Isso só será possível, pois há um vazamento da system no início, permitindo quebrar a proteção ASLR.

Nota: Os ponteiros __free_hook e __malloc_hook foram removidos na versão 2.34.

3. Solução

Primeiramente alocamos os dois chunks, um de 1032 e outro de 510, e liberamos eles, após isso alocamos novamente o de 1032, preenchendo ele totalmente. Agora, nosso segundo chunk está com outro tamanho em seus metadados, 0x200, então liberamos ele novamente.

Por fim, alocamos o segundo chunk novamente (510) e colocamos como valor o __free_hook, e se tudo deu certo a tcache estará assim:

E pronto, estamos com a faca e o queijo na mão, basta apenas alocar o chunk_envenenado, e alocar o __free_hook passando como valor o endereço da win(). Agora quando chamarmos a função free(), iremos para a função win() e a flag será popada.

3. Solução com Python

Flag

picoCTF{i_th0ught_2.29_f1x3d_d0ubl3_fr33?_qiviwkbl}

Autor da WriteUp

Membro de Exploitation - HenriUz

Atualizado