PWN com 1 byte

Introdução

É comum em desafios de pwn termos um buffer grande e com isso não precisamos nos preocupar com espaço para escrever nosso shellcode, além disso temos muitos desafios que precisamos enviar nosso payload em duas partes por conta do tamanho, mas isso deixo para outro dia.

Hoje estava fazendo a challenge Phoenix – Stack Six do Exploit Education, por mais que o nome passe uma visão de desafios simples não se engane, eu fui tão confiante e acabei tombando nessa challenge, recomendo fazer essas challenges de iniciante que ensinam muitas técnicas interessantes, não pense que pwn é apenas um simples buffer overflow !

Eu coloquei muitos junks acredite em mim, o x/40gx $rsp mostra 40 ocorrências a partir de RSP, o gx é basicamente 8 bytes em hexadecimal

Enfim, Olhe em 0x7fffffffeaa0, o primeiro offset tem 0x41 no LSB (no byte mais para a direita), coloquei mais ou menos mil ‘A’s e conseguimos sobrescrever apenas 1 byte do RBP, como vamos conseguir fazer alguma coisa nisso se nem o endereço de retorno (0x7fffffffeaa8) conseguimos manipular?

Simples, 0x7fffffffea41 é o endereço que manipulamos apenas o byte LSB correto? Perceba que podemos manipular o offset dele, se colocarmos 42 ali já será outro endereço, presumo que saiba sobre Stack Frame e o mínimo de assembly, o que acontece é o seguinte: Quando temos uma nova função, uma nova stack frame é criada, para isso ocorre o prólogo:

push rbp
mov rbp, rsp

E quando a função está terminando temos o epílogo:

mov esp, ebp
pop ebp
ret

Perceba que usei registradores de x86 no epílogo, porque em x86_64 temos a função leave ; ret que faz a mesma coisa

Exploit Time

O que nos interessa é o epílogo, após o mov esp, ebp, os dois vão estar apontando para o mesmo lugar e o pop ebp irá restaurar o endereço original de ebp, após o pop o esp é somado para continuar apontando para o topo da stack, o ret irá pegar o endereço que estiver no RSP e colocar em RIP para dar continuação na execução do programa

Certo, mas como vamos conseguir fazer alguma coisa com isso? Imagine que o endereço 0x7fffffffeeb5 é um ponteiro para o nosso buffer, lembra que o nosso RBP é 0x7fffffffeaa0 : 0x7fffffffea41 ?

Perceba que 0x7fffffffeaa0 aponta para 0x7fffffffea41, por que simplesmente não pegamos um ponteiro que aponte para 0x7ffffffffeeb5 ?

Por que teríamos que ter mais bytes sobrando? Um argumento plausível, mas podemos alterar apenas o último byte do ponteiro antigo, ou seja, alterar o a0 para c0 que é um ponteiro para o valor que queremos, olhe:

O que vamos fazer é simples, vamos apenas colocar c0 em RBP e ele passará a apontar para 0x7fffffffeac0, beleza, mas não adianta nada se o nosso endereço de retorno (0x7fffffffeeb5) não foi atingido, mas ele vai ser atingido pelo ret, perceba que até aqui controlamos apenas o RBP, e sabemos que após o RBP temos nosso endereço de retorno

O leave do epílogo irá colocar RBP em seu endereço original e adicionar + 8 para alinhar o RSP, ou seja, em RSP vai estar 0x7fffffffeac0 + 8 = 0x7fffffffeac8 que é justamente o endereço que aponta para o 0x7fffffffeeb5 (e lembre-se, esse endereço aponta para o buffer que estará nosso shellcode), o ret irá pegar esse endereço e jogar para RIP, e pronto, nosso shellcode começa a ser executado.

Em d0 você colocaria o LSB do ponteiro que aponta para o endereço que aponta para o seu buffer, não se preocupe com (126 – len(buf)) isso era um cálculo de compensação da challenge, perceba que com 1 byte correto conseguimos redirecionar o fluxo do programa e fazer ele apontar para o nosso shellcode que está no buffer.

Last updated