IST/MEIC – MERC Tagus / Alameda Segurança Informática em Redes e Sistemas 2008/2009 Trabalho 4 Buffer Overflows Objectivos • Explorar algumas vulnerabilidades associadas a buffer overflows. 1 Introdução Os exercícios propostos demonstram como explorar buffer overflows. Simula-se a exploração de tais vulnerabilidades em aplicações que se executam temporariamente com privilégios root para poderem efectuar determinados serviços (e.g. ping ou passwd) utilizando um conjunto de programas exemplo. Se os ataques forem bem sucedidos, um utilizador normal (e.g. utilizador fireman) consegue executar um interpretador com privilégios root e, portanto, ter total controlo sobre a máquina. Comece por autenticar-se na máquina virtual como utilizador fireman (password “inseguro”). Todos os exercícios devem realizar-se numa consola fireman. Sempre que necessitar de executar comandos privilegiados (como por exemplo mount), deve executar o comando su, introduzir a password de root e executar os comandos. Quando terminar, faça exit. Todos os programas de exemplo usados nesta aula se encontram na imagem buffer_overflow.iso. Prepare o ambiente de trabalho: • Na máquina virtual, capture a imagem buffer_overflow.iso. • Execute o comando privilegiado seguinte para aceder aos respectivos programas. # mount /media/cdrom • • Crie o directório trab4 na home do utilizador fireman e copie para lá os programas exemplo. Execute todos os exercícios no directório trab4. Sempre que for pedido para compilar um programa exemplo x.c fazer: gcc –g –o x x.c Sempre que for pedido para instalar um programa exemplo x.c, deve executar a seguinte sequência de comandos (em modo privilegiado): • Compilar x.c. • Alterar as permissões para o programa correr com privilégios root ( ‘4’ activa flag SUID). # chmod 4755 x • Colocar o programa x na directoria /tmp, acessível ao utilizador fireman. 1 2 Buffer Overflows 2.1 Alteração do endereço de retorno Através dum buffer overflow é possível alterar o endereço de retorno de uma função. • Compilar e executar o programa overflow.c. • Executar agora dentro do gdb. • Fazer > bt e verificar qual o endereço de retorno das funções. Porquê o valor 0x41414141? O comando seguinte mostra o conteúdo do stack pointer : > x $esp 2.2 Buffer overflow na pilha O programa vuln.c é vulnerável a um buffer overflow. • Verificar o que faz o programa vuln.c. • Instalar o programa vuln.c (ver Introdução). • Alterar o exploit.c para executar o programa /tmp/vuln. • Compilar e executar o programa. • Fazer: # whoami • Verificar o que faz o programa exploit.c. O perl é uma boa ferramenta para injectar strings noutros programas. • Executar a linha que se encontra no ficheiro exploit.txt. • Fazer: # whoami • 2.3 Alterar o endereço de retorno da função se necessário. Verificar o que faz esse comando. Buffer overflow usando as variáveis do ambiente Nem sempre o buffer tem o tamanho suficiente para colocar lá o código shell. O programa vuln2.c é um exemplo disso. No entanto pode ser explorado o buffer overflow fazendo correr o código shell na posição de memória onde se encontram as variáveis do ambiente. • • Instalar o programa vuln2.c (ver Introdução). Alterar env_exploit.c para executar o programa /tmp/vuln2 (implica alteração do código em dois locais). Compilar e executar env_exploit.c. Fazer: • Verificar o que faz o programa env_exploit.c. • • # whoami Utilize agora o pearl: • Criar uma variável do ambiente com o comando que se encontra em env_exploit.txt $ source env_exploit.txt 2 • No gdb verificar onde se encontra a variável em /tmp/vuln2. Para tal colocar um breakpoint no main usando o comando seguinte e execute o programa até ao main: • Ver o que encontra em memória na pilha: • Procurar pelas variáveis do ambiente pressionando “Enter” sucessivamente até encontrar o endereço do código da shell. Ter em conta o nome da variável de ambiente para cálculo do endereço onde começa o código da shell. Esse endereço será então usado como endereço para onde o main vai retornar. Se número 10 não for suficiente para obter uma shell de root deve alterá-lo: > b main > x/20s $esp $ /tmp/vuln2 `perl –e ‘print “<endereço>”x10’` Nota: supondo que o endereço obtido é 0xbffff7c4, este deve ser passado na linha de comandos como \xc4\xf7\xff\xbf pois os valores estão representados em memória em little endian. 3 Format strings (opcional) É possível explorar um programa que faça uso do printf da forma: printf(str). No ficheiro fmt_vuln.c encontra-se um exemplo em que a string é impressa da forma correcta e da forma incorrecta. Instalar o programa fmt_vuln.c (ver Introdução). Como utilizador fireman executar os seguintes passos: • Determinar onde se encontra a própria string. o A string encontra-se mais à frente na pilha. Logo para a encontrar fazer o seguinte: # /tmp/fmt_vuln `printf “AAAA”`%x. Acrescentar %x. ao comando até encontrar a string. Ao encontrar início da string significa que o último parâmetro da string acede ao ‘AAAA’ Escolher um endereço para alterar o seu conteúdo. o Vamos escolher o endereço do test_val para assim verificarmos que estamos a alterar a posição certa: o o • $ /tmp/fmt_vuln test para saber qual o endereço do test_val o o o “\x01\x02\x03\x04”`%x<.%x suficientes> em que 0x04030201 é o endereço do test_val (“.%x suficientes” quando permite observar o valor inserido). $ /tmp/fmt_vuln `printf “\x01\x02\x03\x04”`%x<.%x suficientes-1>%n Verificar que test_val foi alterado. Que valor é esse? O que faz a opção %n? Considere a alteração de %x e %n para %N\$x ou %M\$n em que N e M são os números do parâmetro da string. Admitindo que o número de %x suficientes é 8, verifique que o comando seguinte colocaria o mesmo valor em test_val. Porquê? $ /tmp/fmt_vuln `printf $ /tmp/fmt_vuln `printf • “\x01\x02\x03\x04”`%7\$34x%8\$n Com o procedimento anterior podemos colocar qualquer valor em qualquer posição de memória. Podemos por exemplo alterar o valor de retorno que está na pilha para apontar para o código shell que se encontra numa variável do ambiente. Comecemos por colocar o valor do endereço do código shell na variável test_val para verificar que está correcto: o Fazer export da variável SHELLCODE o Ver com gdb /tmp/fmt_vuln onde se encontra a variável SHELLCODE 3 o o o o • Para colocar o endereço na variável test_val, é necessário fazê-lo byte a byte. Usando %N\$x e %M\$n, passamos a ter um par %x%n por cada byte que queremos escrever: # /tmp/fmt_test `printf “\x01\x02\x03\x04”`%N\$x%M\$n # /tmp/fmt_test `printf “\x01\x02\x03\x04\x02\x02\x03\x04\x03\x02\x03\x04\x04\x02\x03\x04”` %N\$x%M\$n Em que 0x04030201, 0x04030202, 0x04030203, 0x04030204 são os endereços do inteiro test_val. Acrescentar agora o valor L ao parâmetro %x: %N\$Lx em que L é o número de caracteres que ocupa o parâmetro x. Isto permite aumentar a string por forma a escrevermos o valor certo no test_val. O valor que queremos escrever será o byte menos significativo do endereço do código shell. Verificar que se escreve o byte menos significativo correctamente no test_val. Acrescentar agora um novo %N\$Lx%(M+1)\$n. Isto permite escrever no 2º byte de test_val. Fazer o mesmo para os restantes 2 bytes. E temos o endereço pronto. A figura seguinte mostra um exemplo de como colocar o endereço 0xc4f7ffbf na variável test_val. Só falta agora colocá-lo na posição de memória certa. Visto que não é fácil saber onde está o endereço de retorno de uma função vamos escolher outra posição. Em C é possível definir funções destrutoras. Essas funções encontram-se na secção .dtors no array que começa com 0xffffffff e acaba com 0x00000000. Sendo essas funções sempre chamadas basta alterar o ponteiro para essa função para o valor do código shell: $ objdump –s –j .dtors /tmp/fmt_vuln • Isto permite-nos ter a posição de memória onde está o endereço para onde o programa vai saltar quando sair. Esta posição de memória é o endereço imediatamente após ao endereço onde se encontra o valor 0xffffffff. Colocar esse valor em vez do endereço do test_val Fazer: # whoami 4