mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/binary-exploitation/common-binary-protections-and-bypas
This commit is contained in:
parent
a77d13eb43
commit
b3545eb964
@ -4,30 +4,96 @@
|
||||
|
||||
## Relro
|
||||
|
||||
**RELRO** означає **Relocation Read-Only**, і це функція безпеки, що використовується в бінарних файлах для зменшення ризиків, пов'язаних з переписуванням **GOT (Global Offset Table)**. Існує два типи захисту **RELRO**: (1) **Частковий RELRO** і (2) **Повний RELRO**. Обидва вони реорганізовують **GOT** і **BSS** з ELF файлів, але з різними результатами та наслідками. Конкретно, вони розміщують секцію **GOT** _перед_ **BSS**. Тобто, **GOT** знаходиться за нижчими адресами, ніж **BSS**, що унеможливлює переписування записів **GOT** шляхом переповнення змінних у **BSS** (пам'ять записується з нижчих адрес до вищих).
|
||||
**RELRO** означає **Relocation Read-Only** і є заходом, реалізованим компоновником (`ld`), який перетворює підмножину сегментів даних ELF на **тільки для читання після застосування всіх перенесень**. Мета полягає в тому, щоб зупинити зловмисника від перезапису записів у **GOT (Global Offset Table)** або інших таблицях, пов'язаних з перенесеннями, які розіменовуються під час виконання програми (наприклад, `__fini_array`).
|
||||
|
||||
Давайте розглянемо концепцію в її двох різних типах для ясності.
|
||||
Сучасні компоновники реалізують RELRO, **переставляючи** **GOT** (і кілька інших секцій) так, щоб вони знаходилися **перед** **.bss** і – що найважливіше – створюючи спеціальний сегмент `PT_GNU_RELRO`, який перенаправляється `R–X` відразу після того, як динамічний завантажувач закінчує застосування перенесень. Внаслідок цього типові переповнення буфера в **.bss** більше не можуть досягти GOT, і примітиви довільного запису не можуть бути використані для перезапису вказівників функцій, які знаходяться всередині захищеної сторінки RELRO.
|
||||
|
||||
### **Частковий RELRO**
|
||||
Існує **два рівні** захисту, які може випустити компоновник:
|
||||
|
||||
**Частковий RELRO** використовує простіший підхід для підвищення безпеки без значного впливу на продуктивність бінарного файлу. Частковий RELRO робить **.got тільки для читання (не-PLT частина секції GOT)**. Майте на увазі, що решта секції (як .got.plt) все ще може бути записуваною і, отже, підлягає атакам. Це **не запобігає зловживанню GOT** **з вразливостей довільного запису**.
|
||||
### Partial RELRO
|
||||
|
||||
Примітка: За замовчуванням, GCC компілює бінарні файли з Частковим RELRO.
|
||||
* Виробляється з прапором `-Wl,-z,relro` (або просто `-z relro`, коли викликається `ld` безпосередньо).
|
||||
* Тільки **не-PLT** частина **GOT** (частина, що використовується для перенесень даних) поміщається в сегмент тільки для читання. Секції, які потрібно змінювати під час виконання – найважливіше **.got.plt**, що підтримує **lazy binding** – залишаються записуваними.
|
||||
* Через це примітив **довільного запису** все ще може перенаправити потік виконання, перезаписуючи запис PLT (або виконуючи **ret2dlresolve**).
|
||||
* Вплив на продуктивність незначний, тому **майже кожен дистрибутив протягом багатьох років постачає пакунки з принаймні Partial RELRO (це стандарт GCC/Binutils з 2016 року)**.
|
||||
|
||||
### **Повний RELRO**
|
||||
### Full RELRO
|
||||
|
||||
**Повний RELRO** підвищує захист, **роблячи всю GOT (як .got, так і .got.plt) та секцію .fini_array** повністю **тільки для читання.** Як тільки бінарний файл запускається, всі адреси функцій вирішуються і завантажуються в GOT, після чого GOT позначається як тільки для читання, ефективно запобігаючи будь-яким змінам під час виконання.
|
||||
* Виробляється з **обома** прапорами `-Wl,-z,relro,-z,now` (також відомий як `-z relro -z now`). `-z now` змушує динамічний завантажувач вирішувати **всі** символи заздалегідь (eager binding), щоб **.got.plt** більше не потрібно було записувати і його можна було безпечно відобразити як тільки для читання.
|
||||
* Весь **GOT**, **.got.plt**, **.fini_array**, **.init_array**, **.preinit_array** та кілька додаткових внутрішніх таблиць glibc потрапляють у сегмент тільки для читання `PT_GNU_RELRO`.
|
||||
* Додає вимірюване навантаження при запуску (всі динамічні перенесення обробляються під час запуску), але **немає накладних витрат під час виконання**.
|
||||
|
||||
Однак, компроміс з Повним RELRO полягає в продуктивності та часі запуску. Оскільки потрібно вирішити всі динамічні символи під час запуску перед тим, як позначити GOT як тільки для читання, **бінарні файли з увімкненим Повним RELRO можуть мати довший час завантаження**. Це додаткове навантаження під час запуску є причиною, чому Повний RELRO не увімкнено за замовчуванням у всіх бінарних файлах.
|
||||
З 2023 року кілька основних дистрибутивів перейшли на компіляцію **системного інструментального набору** (і більшості пакунків) з **Full RELRO за замовчуванням** – наприклад, **Debian 12 “bookworm” (dpkg-buildflags 13.0.0)** та **Fedora 35+**. Як пентестер, ви повинні очікувати зустріти бінарні файли, де **кожен запис GOT є тільки для читання**.
|
||||
|
||||
Можна перевірити, чи **увімкнено** Повний RELRO у бінарному файлі за допомогою:
|
||||
---
|
||||
|
||||
## Як перевірити статус RELRO бінарного файлу
|
||||
```bash
|
||||
readelf -l /proc/ID_PROC/exe | grep BIND_NOW
|
||||
$ checksec --file ./vuln
|
||||
[*] '/tmp/vuln'
|
||||
Arch: amd64-64-little
|
||||
RELRO: Full
|
||||
Stack: Canary found
|
||||
NX: NX enabled
|
||||
PIE: No PIE (0x400000)
|
||||
```
|
||||
`checksec` (частина [pwntools](https://github.com/pwncollege/pwntools) та багатьох дистрибутивів) аналізує заголовки `ELF` і виводить рівень захисту. Якщо ви не можете використовувати `checksec`, покладайтеся на `readelf`:
|
||||
```bash
|
||||
# Partial RELRO → PT_GNU_RELRO is present but BIND_NOW is *absent*
|
||||
$ readelf -l ./vuln | grep -E "GNU_RELRO|BIND_NOW"
|
||||
GNU_RELRO 0x0000000000600e20 0x0000000000600e20
|
||||
```
|
||||
## Bypass
|
||||
|
||||
Якщо увімкнено Full RELRO, єдиний спосіб обійти його - знайти інший спосіб, який не потребує запису в таблицю GOT для отримання довільного виконання.
|
||||
```bash
|
||||
# Full RELRO → PT_GNU_RELRO *and* the DF_BIND_NOW flag
|
||||
$ readelf -d ./vuln | grep BIND_NOW
|
||||
0x0000000000000010 (FLAGS) FLAGS: BIND_NOW
|
||||
```
|
||||
Якщо двійковий файл працює (наприклад, допоміжна програма з set-uid root), ви все ще можете перевірити виконуваний файл **через `/proc/$PID/exe`**:
|
||||
```bash
|
||||
readelf -l /proc/$(pgrep helper)/exe | grep GNU_RELRO
|
||||
```
|
||||
---
|
||||
|
||||
Зверніть увагу, що **GOT LIBC зазвичай є Partial RELRO**, тому його можна змінити за допомогою довільного запису. Більше інформації в [Targetting libc GOT entries](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#1---targetting-libc-got-entries)**.**
|
||||
## Увімкнення RELRO під час компіляції власного коду
|
||||
```bash
|
||||
# GCC example – create a PIE with Full RELRO and other common hardenings
|
||||
$ gcc -fPIE -pie -z relro -z now -Wl,--as-needed -D_FORTIFY_SOURCE=2 main.c -o secure
|
||||
```
|
||||
`-z relro -z now` працює для обох **GCC/clang** (передано після `-Wl,`) і **ld** безпосередньо. Коли ви використовуєте **CMake 3.18+**, ви можете запитати повний RELRO за допомогою вбудованого пресету:
|
||||
```cmake
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) # LTO
|
||||
set(CMAKE_ENABLE_EXPORTS OFF)
|
||||
set(CMAKE_BUILD_RPATH_USE_ORIGIN ON)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "-Wl,-z,relro,-z,now")
|
||||
```
|
||||
---
|
||||
|
||||
## Техніки обходу
|
||||
|
||||
| Рівень RELRO | Типовий примітив | Можливі техніки експлуатації |
|
||||
|--------------|------------------|------------------------------|
|
||||
| Немає / Частковий | Довільний запис | 1. Перезаписати запис **.got.plt** і змінити виконання.<br>2. **ret2dlresolve** – створити підроблені `Elf64_Rela` та `Elf64_Sym` у записуваному сегменті та викликати `_dl_runtime_resolve`.<br>3. Перезаписати вказівники функцій у списку **.fini_array** / **atexit()**. |
|
||||
| Повний | GOT є тільки для читання | 1. Шукати **інші записувані вказівники коду** (C++ vtables, `__malloc_hook` < glibc 2.34, `__free_hook`, зворотні виклики в кастомних секціях `.data`, JIT-сторінки).<br>2. Зловживати *відносними читаннями* для витоку libc та виконання **SROP/ROP у libc**.<br>3. Впровадити зловмисний спільний об'єкт через **DT_RPATH**/`LD_PRELOAD` (якщо середовище контролюється атакуючим) або **`ld_audit`**.<br>4. Використати **форматний рядок** або частковий перезапис вказівника для зміни потоку управління без торкання GOT. |
|
||||
|
||||
> 💡 Навіть з Повним RELRO **GOT завантажених спільних бібліотек (наприклад, сама libc)** є **тільки Частковим RELRO**, оскільки ці об'єкти вже відображені, коли завантажувач застосовує перенесення. Якщо ви отримали **довільний запис** примітив, який може націлюватися на сторінки іншого спільного об'єкта, ви все ще можете змінити виконання, перезаписавши записи GOT libc або стек `__rtld_global`, техніка, яка регулярно використовується в сучасних CTF-завданнях.
|
||||
|
||||
### Приклад обходу в реальному світі (2024 CTF – *pwn.college “enlightened”*)
|
||||
|
||||
Завдання постачалося з Повним RELRO. Експлуатація використовувала **off-by-one** для корупції розміру частини купи, витекла libc з `tcache poisoning`, і нарешті перезаписала `__free_hook` (поза сегментом RELRO) з одним гаджетом для отримання виконання коду. Запис у GOT не був потрібен.
|
||||
|
||||
---
|
||||
|
||||
## Останні дослідження та вразливості (2022-2025)
|
||||
|
||||
* **glibc 2.40 знецінює `__malloc_hook` / `__free_hook` (2025)** – Більшість сучасних експлуатацій купи, які зловживали цими символами, тепер повинні переходити на альтернативні вектори, такі як **`rtld_global._dl_load_jump`** або таблиці виключень C++. Оскільки гачки живуть **поза** RELRO, їх видалення ускладнює обходи Повного RELRO.
|
||||
* **Виправлення “max-page-size” Binutils 2.41 (2024)** – Помилка дозволила останнім кільком байтам сегмента RELRO ділити сторінку з записуваними даними на деяких збірках ARM64, залишаючи маленький **RELRO-проміжок**, який можна було записати після `mprotect`. Тепер upstream вирівнює `PT_GNU_RELRO` до меж сторінок, усуваючи цей крайній випадок.
|
||||
|
||||
---
|
||||
|
||||
## Посилання
|
||||
|
||||
* Документація Binutils – *`-z relro`, `-z now` та `PT_GNU_RELRO`*
|
||||
* *“RELRO – Повний, Частковий та Техніки обходу”* – допис у блозі @ wolfslittlered 2023
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
Loading…
x
Reference in New Issue
Block a user