Ret2win

O que é Ret2Win

Ret2win é um exploit que envolve a pilha (stack), de forma que nós reescrevemos os valores armazenados nela para alterarmos o endereço de retorno para uma função win (por isso o nome ret2win). Mas como isso funciona? Bom, quando chamamos uma função o endereço da próxima instrução é salvo na pilha (rsp armazena esse endereço), e no início de toda função nós temos uma sequência de código em assembly que são conhecidos como prólogo da função. Eles são o push rbp que salva na pilha o conteúdo de rbp, e o mov rbp, rsp que faz o rbp ser igual ao rsp, essas duas instruções basicamente criam um novo segmento de pilha para aquela função. Agora que toda a pilha para a função está criada, ela começa a alocar espaço para as variáveis (se houver) subtraindo o valor de rsp pelo tamanho da variável (note que a pilha cresce de cima para baixo). Com tudo isso feito, se nós conseguirmos fazer uma variável armazenar um valor maior que o seu tamanho, nós na verdade começaremos a sobrescrever todos os valores que já estavam na pilha, e o objetivo é fazer isso até chegarmos no local onde o endereço da instrução foi armazenado, e assim rescrevemos ele para ser o endereço para a função que queremos ir.

Analisando o código

Normalmente você nunca recebe um arquivo de código para que você simplesmente olhe diretamente para ele e veja as vulnerabilidades, você geralmente vai receber um arquivo executável (na maioria dos casos .ELF). Para nós vermos o código é necessário usar alguma ferramenta que desmonte o executável em seu código assembly, e aqui nós usaremos duas ferramentas: o Ghidra que consegue mostrar o código assembly compilado para pseudo C ou C++, e também permite uma fácil visualização de todo o código, e o PwnDbg que também permite uma visualização do código assembly, mas usaremos ele principalmente por causa da sua capacidade de debugar.

Abrindo o ELF disponibilizado no Ghidra podemos ter acesso a função main:

main.c
undefined8 main(void)
{
  vulnFunction();
  return 0;
}

Podemos notar que a main chama outra função, vamos dar uma olhada nela.

vulnFunction.c
void vulnFunction(void)
{
  undefined local_18 [16];
  
  __isoc99_scanf(&DAT_00102021,local_18);
  printf("Buffer: %s",local_18);
  return;
}

Certo, temos uma função que usa o scanf para armezar o input do usuário em uma variável de tamanho 16 (vale notar que o scanf não tem limite de leitura, esse limite deve ser específicado nos operadores de formato, e nesse caso não há nenhum limitador). Já identificamos a possível vulnerabilidade, mas e a função win? Bom temos que olhar no código para termos certeza que ela existe, para fazermos isso é só usar o Ghidra.

Nota: Normalmente não vai existir uma função win, como neste caso, mas isso será abordado nos próximos exploits.

Segue a função win:

Debugando com PwnDbg

Agora que sabemos todas as funções, vamos começar a debugar:

Solução

Esse é o estado da pilha após ela criar espaço para a nossa variável:

Logo para chegarmos no endereço de retorno, teremos que escrever 24 caracteres (0x18 -> 0x7fffffffddd8 - 0x7fffffffddc0). Antes de fazermos um script para isso, nós temos que dar uma olhada na função win para acharmos seu endereço:

Seu endereço é 0x0000000000401146, vamos para o script:

Esse script é bem simples, nós usamos a biblioteca pwn que nos permite acessar "programas" por meio do script. Com ela nós abrimos o nosso processo (ret2win), criamos uma mensagem em bytes contendo os 24 caracteres mais o endereço da win (em little endian), e enviamos essa mensagem para o programa. Para pegarmos a mensagem do terminal nós usamos a p.recvuntil(b"?").decode(), essa função retorna tudo até o caractere ?, em um formato de array de bytes, e por isso usamos o decode() para traduzir para nós. O único problema desse script é que a função recv da biblioteca pwn pode gerar erros se ela não encontrar o critério de parada, a outra opção para ela é usar o log.info().

Resultado

Atualizado