mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/binary-exploitation/basic-stack-binary-exploitation-met
This commit is contained in:
parent
6c0a2433a6
commit
2fd5f76815
@ -2,7 +2,7 @@
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
## Заголовки програми
|
||||
## Заголовки Програм
|
||||
|
||||
Вони описують завантажувачу, як завантажити **ELF** в пам'ять:
|
||||
```bash
|
||||
@ -37,7 +37,7 @@ Segment Sections...
|
||||
07
|
||||
08 .init_array .fini_array .dynamic .got
|
||||
```
|
||||
Попередня програма має **9 заголовків програми**, тоді як **відображення сегментів** вказує, в якому заголовку програми (з 00 до 08) **знаходиться кожен розділ**.
|
||||
Попередня програма має **9 заголовків програми**, тоді як **відображення сегментів** вказує, в якому заголовку програми (з 00 по 08) **знаходиться кожен розділ**.
|
||||
|
||||
### PHDR - Заголовок програми
|
||||
|
||||
@ -47,12 +47,14 @@ Segment Sections...
|
||||
|
||||
Вказує шлях до завантажувача, який потрібно використовувати для завантаження бінарного файлу в пам'ять.
|
||||
|
||||
> Порада: Статично зв'язані або статичні PIE бінарні файли не матимуть запису `INTERP`. У таких випадках немає динамічного завантажувача, що відключає техніки, які на нього покладаються (наприклад, `ret2dlresolve`).
|
||||
|
||||
### LOAD
|
||||
|
||||
Ці заголовки використовуються для вказівки **як завантажити бінарний файл в пам'ять.**\
|
||||
Кожен **LOAD** заголовок вказує на область **пам'яті** (розмір, дозволи та вирівнювання) і вказує байти ELF **бінарного файлу, які потрібно скопіювати туди**.
|
||||
|
||||
Наприклад, другий має розмір 0x1190, повинен бути розташований за адресою 0x1fc48 з дозволами на читання та запис і буде заповнений 0x528 з офсету 0xfc48 (не заповнює весь зарезервований простір). Ця пам'ять міститиме розділи `.init_array .fini_array .dynamic .got .data .bss`.
|
||||
Наприклад, другий має розмір 0x1190, повинен бути розташований за адресою 0x1fc48 з дозволами на читання та запис і буде заповнений 0x528 з офсету 0xfc48 (він не заповнює весь зарезервований простір). Ця пам'ять міститиме розділи `.init_array .fini_array .dynamic .got .data .bss`.
|
||||
|
||||
### DYNAMIC
|
||||
|
||||
@ -62,21 +64,35 @@ Segment Sections...
|
||||
|
||||
Це зберігає інформацію про метадані постачальника бінарного файлу.
|
||||
|
||||
- На x86-64, `readelf -n` покаже прапори `GNU_PROPERTY_X86_FEATURE_1_*` всередині `.note.gnu.property`. Якщо ви бачите `IBT` і/або `SHSTK`, бінарний файл був створений з CET (відстеження непрямих переходів і/або тіньовий стек). Це впливає на ROP/JOP, оскільки цілі непрямих переходів повинні починатися з інструкції `ENDBR64`, а повернення перевіряються проти тіньового стека. Дивіться сторінку CET для деталей та приміток про обходи.
|
||||
|
||||
{{#ref}}
|
||||
../common-binary-protections-and-bypasses/cet-and-shadow-stack.md
|
||||
{{#endref}}
|
||||
|
||||
### GNU_EH_FRAME
|
||||
|
||||
Визначає місцезнаходження таблиць розгортання стеку, які використовуються відладчиками та функціями обробки виключень C++.
|
||||
Визначає місце розташування таблиць розгортання стеку, які використовуються відладчиками та функціями обробки виключень C++.
|
||||
|
||||
### GNU_STACK
|
||||
|
||||
Містить конфігурацію захисту від виконання коду зі стеку. Якщо увімкнено, бінарний файл не зможе виконувати код зі стеку.
|
||||
|
||||
- Перевірте за допомогою `readelf -l ./bin | grep GNU_STACK`. Щоб примусово переключити його під час тестів, ви можете використовувати `execstack -s|-c ./bin`.
|
||||
|
||||
### GNU_RELRO
|
||||
|
||||
Вказує конфігурацію RELRO (Relocation Read-Only) бінарного файлу. Цей захист позначить як тільки для читання певні розділи пам'яті (як `GOT` або таблиці `init` та `fini`) після завантаження програми та перед її виконанням.
|
||||
Вказує конфігурацію RELRO (Relocation Read-Only) бінарного файлу. Цей захист позначить як тільки для читання певні розділи пам'яті (як `GOT` або таблиці `init` і `fini`) після завантаження програми і перед її виконанням.
|
||||
|
||||
У попередньому прикладі копіюється 0x3b8 байтів до 0x1fc48 як тільки для читання, що впливає на розділи `.init_array .fini_array .dynamic .got .data .bss`.
|
||||
|
||||
Зверніть увагу, що RELRO може бути частковим або повним, часткова версія не захищає розділ **`.plt.got`**, який використовується для **лінивої прив'язки** і потребує цього простору пам'яті для надання **дозволів на запис**, щоб записати адресу бібліотек під час першого пошуку їхнього місцезнаходження.
|
||||
Зверніть увагу, що RELRO може бути частковим або повним, часткова версія не захищає розділ **`.plt.got`**, який використовується для **лінивої прив'язки** і потребує цього простору пам'яті для надання **дозволів на запис**, щоб записати адресу бібліотек під час першого пошуку їхнього місця розташування.
|
||||
|
||||
> Для технік експлуатації та актуальних приміток про обходи, перевірте спеціалізовану сторінку:
|
||||
|
||||
{{#ref}}
|
||||
../common-binary-protections-and-bypasses/relro.md
|
||||
{{#endref}}
|
||||
|
||||
### TLS
|
||||
|
||||
@ -145,7 +161,7 @@ CONTENTS, READONLY
|
||||
25 .gnu_debuglink 00000034 0000000000000000 0000000000000000 000101bc 2**2
|
||||
CONTENTS, READONLY
|
||||
```
|
||||
Це також вказує на місцезнаходження, зсув, дозволи, а також **тип даних**, який має його секція.
|
||||
Це також вказує на місцезнаходження, зсув, дозволи, але також і **тип даних**, який має його секція.
|
||||
|
||||
### Мета секції
|
||||
|
||||
@ -164,7 +180,7 @@ CONTENTS, READONLY
|
||||
|
||||
## Символи
|
||||
|
||||
Символи - це іменоване місце в програмі, яке може бути функцією, глобальним об'єктом даних, локальними для потоку змінними...
|
||||
Символи - це назване місцезнаходження в програмі, яке може бути функцією, глобальним об'єктом даних, локальними для потоку змінними...
|
||||
```
|
||||
readelf -s lnstat
|
||||
|
||||
@ -188,12 +204,16 @@ Num: Value Size Type Bind Vis Ndx Name
|
||||
Кожен запис символу містить:
|
||||
|
||||
- **Ім'я**
|
||||
- **Атрибути зв'язування** (слабкий, локальний або глобальний): Локальний символ може бути доступний лише самою програмою, тоді як глобальний символ спільний за межами програми. Слабкий об'єкт, наприклад, це функція, яку можна переопределити іншою.
|
||||
- **Тип**: NOTYPE (тип не вказано), OBJECT (глобальна змінна даних), FUNC (функція), SECTION (секція), FILE (файл вихідного коду для налагоджувачів), TLS (змінна локального потоку), GNU_IFUNC (непряма функція для релокації)
|
||||
- **Атрибути зв'язування** (слабкий, локальний або глобальний): Локальний символ може бути доступний лише програмою, тоді як глобальний символ спільний за межами програми. Слабкий об'єкт, наприклад, це функція, яку можна переоприділити іншим.
|
||||
- **Тип**: NOTYPE (тип не вказано), OBJECT (глобальна змінна даних), FUNC (функція), SECTION (секція), FILE (файл вихідного коду для налагоджувачів), TLS (змінна локального потоку), GNU_IFUNC (непряма функція для перенесення)
|
||||
- **Індекс секції**, де він розташований
|
||||
- **Значення** (адреса в пам'яті)
|
||||
- **Розмір**
|
||||
|
||||
#### Версія символів GNU (dynsym/dynstr/gnu.version)
|
||||
|
||||
Сучасний glibc використовує версії символів. Ви побачите записи в `.gnu.version` та `.gnu.version_r`, а також імена символів, такі як `strlen@GLIBC_2.17`. Динамічний зв'язувач може вимагати конкретну версію при розв'язанні символу. При створенні ручних перенесень (наприклад, ret2dlresolve) ви повинні надати правильний індекс версії, інакше розв'язання не вдасться.
|
||||
|
||||
## Динамічна секція
|
||||
```
|
||||
readelf -d lnstat
|
||||
@ -229,11 +249,28 @@ Tag Type Name/Value
|
||||
0x000000006ffffff9 (RELACOUNT) 15
|
||||
0x0000000000000000 (NULL) 0x0
|
||||
```
|
||||
Директорія NEEDED вказує на те, що програма **потребує завантаження згаданої бібліотеки** для продовження. Директорія NEEDED завершується, коли спільна **бібліотека повністю функціонує і готова** до використання.
|
||||
Директорія NEEDED вказує, що програма **потребує завантажити згадану бібліотеку** для продовження. Директорія NEEDED завершується, коли спільна **бібліотека повністю функціонує і готова** до використання.
|
||||
|
||||
## Переміщення
|
||||
### Порядок пошуку динамічного завантажувача (RPATH/RUNPATH, $ORIGIN)
|
||||
|
||||
Завантажувач також повинен перемістити залежності після їх завантаження. Ці переміщення вказані в таблиці переміщень у форматах REL або RELA, а кількість переміщень вказується в динамічних секціях RELSZ або RELASZ.
|
||||
Записи `DT_RPATH` (застарілий) та/або `DT_RUNPATH` впливають на те, де динамічний завантажувач шукає залежності. Орієнтовний порядок:
|
||||
|
||||
- `LD_LIBRARY_PATH` (ігнорується для програм setuid/sgid або інших "програм безпечного виконання")
|
||||
- `DT_RPATH` (тільки якщо `DT_RUNPATH` відсутній)
|
||||
- `DT_RUNPATH`
|
||||
- `ld.so.cache`
|
||||
- стандартні директорії, такі як `/lib64`, `/usr/lib64` тощо.
|
||||
|
||||
`$ORIGIN` може бути використаний всередині RPATH/RUNPATH для посилання на директорію основного об'єкта. З точки зору атакуючого це важливо, коли ви контролюєте структуру файлової системи або середовище. Для захищених бінарних файлів (AT_SECURE) більшість змінних середовища ігнорується завантажувачем.
|
||||
|
||||
- Перевірте за допомогою: `readelf -d ./bin | egrep -i 'r(path|unpath)'`
|
||||
- Швидкий тест: `LD_DEBUG=libs ./bin 2>&1 | grep -i find` (показує рішення щодо пошукового шляху)
|
||||
|
||||
> Порада з приводу привілеїв: Віддавайте перевагу зловживанню записуваними RUNPATH або неправильно налаштованими шляхами, що відносяться до `$ORIGIN`, які належать вам. LD_PRELOAD/LD_AUDIT ігноруються в контекстах безпечного виконання (setuid).
|
||||
|
||||
## Релокації
|
||||
|
||||
Завантажувач також повинен релокувати залежності після їх завантаження. Ці релокації вказані в таблиці релокацій у форматах REL або RELA, а кількість релокацій вказується в динамічних секціях RELSZ або RELASZ.
|
||||
```
|
||||
readelf -r lnstat
|
||||
|
||||
@ -274,7 +311,6 @@ Offset Info Type Sym. Value Sym. Name + Addend
|
||||
00000001fea0 000900000402 R_AARCH64_JUMP_SL 0000000000000000 perror@GLIBC_2.17 + 0
|
||||
00000001fea8 000b00000402 R_AARCH64_JUMP_SL 0000000000000000 __cxa_finalize@GLIBC_2.17 + 0
|
||||
00000001feb0 000c00000402 R_AARCH64_JUMP_SL 0000000000000000 putc@GLIBC_2.17 + 0
|
||||
00000001feb8 000d00000402 R_AARCH64_JUMP_SL 0000000000000000 opendir@GLIBC_2.17 + 0
|
||||
00000001fec0 000e00000402 R_AARCH64_JUMP_SL 0000000000000000 fputc@GLIBC_2.17 + 0
|
||||
00000001fec8 001100000402 R_AARCH64_JUMP_SL 0000000000000000 snprintf@GLIBC_2.17 + 0
|
||||
00000001fed0 001200000402 R_AARCH64_JUMP_SL 0000000000000000 __snprintf_chk@GLIBC_2.17 + 0
|
||||
@ -306,25 +342,43 @@ Offset Info Type Sym. Value Sym. Name + Addend
|
||||
00000001ffa0 002f00000402 R_AARCH64_JUMP_SL 0000000000000000 __assert_fail@GLIBC_2.17 + 0
|
||||
00000001ffa8 003000000402 R_AARCH64_JUMP_SL 0000000000000000 fgets@GLIBC_2.17 + 0
|
||||
```
|
||||
### Статичні перенесення
|
||||
### Статичні Релокації
|
||||
|
||||
Якщо **програма завантажується в місце, відмінне** від бажаної адреси (зазвичай 0x400000) через те, що адреса вже використовується або через **ASLR**, або з будь-якої іншої причини, статичне перенесення **виправляє вказівники**, які мали значення, очікуючи, що бінарник буде завантажено за бажаною адресою.
|
||||
Якщо **програма завантажена в місце, відмінне** від бажаної адреси (зазвичай 0x400000) через те, що адреса вже використовується або через **ASLR**, або з будь-якої іншої причини, статична релокація **виправляє вказівники**, які мали значення, очікуючи, що бінарний файл буде завантажено за бажаною адресою.
|
||||
|
||||
Наприклад, будь-який розділ типу `R_AARCH64_RELATIV` повинен модифікувати адресу за зміщенням перенесення плюс значення доданка.
|
||||
Наприклад, будь-який розділ типу `R_AARCH64_RELATIV` повинен мати змінену адресу з урахуванням зміщення релокації плюс значення доданка.
|
||||
|
||||
### Динамічні перенесення та GOT
|
||||
### Динамічні Релокації та GOT
|
||||
|
||||
Перенесення також може посилатися на зовнішній символ (наприклад, функцію з залежності). Як функція malloc з libC. Тоді завантажувач, завантажуючи libC за адресою, перевіряючи, де завантажена функція malloc, запише цю адресу в таблицю GOT (Global Offset Table) (вказану в таблиці перенесення), де повинна бути вказана адреса malloc.
|
||||
Релокація також може посилатися на зовнішній символ (наприклад, функцію з залежності). Як функція malloc з libC. Тоді завантажувач, завантажуючи libC за адресою, перевіряючи, де завантажена функція malloc, запише цю адресу в таблицю GOT (Global Offset Table) (вказану в таблиці релокацій), де повинна бути вказана адреса malloc.
|
||||
|
||||
### Таблиця зв'язків процедур
|
||||
### Таблиця Зв'язків Процедур
|
||||
|
||||
Розділ PLT дозволяє виконувати ліниве зв'язування, що означає, що розв'язання місця розташування функції буде виконано перший раз, коли до неї звертаються.
|
||||
Розділ PLT дозволяє виконувати ліниве зв'язування, що означає, що розв'язання місця функції буде виконано перший раз, коли до неї звертаються.
|
||||
|
||||
Отже, коли програма викликає malloc, вона фактично викликає відповідне місце `malloc` в PLT (`malloc@plt`). Перший раз, коли його викликають, він розв'язує адресу `malloc` і зберігає її, тому наступного разу, коли викликається `malloc`, використовується ця адреса замість коду PLT.
|
||||
|
||||
## Ініціалізація програми
|
||||
#### Сучасні поведінки зв'язування, які впливають на експлуатацію
|
||||
|
||||
Після того, як програма була завантажена, настав час для її виконання. Однак перший код, який виконується, **не завжди є функцією `main`**. Це тому, що, наприклад, у C++, якщо **глобальна змінна є об'єктом класу**, цей об'єкт повинен бути **ініціалізований** **перед** виконанням main, як у:
|
||||
- `-z now` (Повний RELRO) вимикає ліниве зв'язування; записи PLT все ще існують, але GOT/PLT відображається як тільки для читання, тому такі техніки, як **GOT overwrite** і **ret2dlresolve**, не спрацюють проти основного бінарного файлу (бібліотеки можуть залишатися частково RELRO). Дивіться:
|
||||
|
||||
{{#ref}}
|
||||
../common-binary-protections-and-bypasses/relro.md
|
||||
{{#endref}}
|
||||
|
||||
- `-fno-plt` змушує компілятор викликати зовнішні функції через **запис GOT безпосередньо** замість того, щоб проходити через PLT stub. Ви побачите послідовності викликів, такі як `mov reg, [got]; call reg` замість `call func@plt`. Це зменшує зловживання спекулятивним виконанням і трохи змінює полювання на ROP gadget навколо PLT stubs.
|
||||
|
||||
- PIE проти статичного-PIE: PIE (ET_DYN з `INTERP`) потребує динамічного завантажувача і підтримує звичайну механіку PLT/GOT. Статичне-PIE (ET_DYN без `INTERP`) має релокації, застосовані завантажувачем ядра, і немає `ld.so`; очікуйте відсутності розв'язання PLT під час виконання.
|
||||
|
||||
> Якщо GOT/PLT не є варіантом, переключіться на інші записувані вказівники коду або використовуйте класичний ROP/SROP у libc.
|
||||
|
||||
{{#ref}}
|
||||
../arbitrary-write-2-exec/aw2exec-got-plt.md
|
||||
{{#endref}}
|
||||
|
||||
## Ініціалізація Програми
|
||||
|
||||
Після того, як програма була завантажена, настав час для її виконання. Однак перший код, який виконується, **не завжди є функцією `main`**. Це пов'язано з тим, що, наприклад, у C++, якщо **глобальна змінна є об'єктом класу**, цей об'єкт повинен бути **ініціалізований** **перед** виконанням main, як у:
|
||||
```cpp
|
||||
#include <stdio.h>
|
||||
// g++ autoinit.cpp -o autoinit
|
||||
@ -345,9 +399,9 @@ printf("Main\n");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
Зверніть увагу, що ці глобальні змінні розташовані в `.data` або `.bss`, але в списках `__CTOR_LIST__` і `__DTOR_LIST__` об'єкти для ініціалізації та деструкції зберігаються, щоб відстежувати їх.
|
||||
Зверніть увагу, що ці глобальні змінні розташовані в `.data` або `.bss`, але в списках `__CTOR_LIST__` та `__DTOR_LIST__` об'єкти для ініціалізації та деструкції зберігаються в порядку, щоб відстежувати їх.
|
||||
|
||||
З C-коду можливо отримати той же результат, використовуючи розширення GNU:
|
||||
З коду C можливо отримати той же результат, використовуючи розширення GNU:
|
||||
```c
|
||||
__attributte__((constructor)) //Add a constructor to execute before
|
||||
__attributte__((destructor)) //Add to the destructor list
|
||||
@ -358,14 +412,24 @@ __attributte__((destructor)) //Add to the destructor list
|
||||
|
||||
Більше того, також можливо мати **`PREINIT_ARRAY`** з **вказівниками**, які будуть виконані **перед** вказівниками **`INIT_ARRAY`**.
|
||||
|
||||
#### Примітка щодо експлуатації
|
||||
|
||||
- У режимі Partial RELRO ці масиви знаходяться на сторінках, які все ще можна записувати, перш ніж `ld.so` змінить `PT_GNU_RELRO` на тільки для читання. Якщо ви отримаєте довільний запис досить рано або зможете націлити записувані масиви бібліотеки, ви можете перехопити контрольний потік, перезаписавши запис функцією на ваш вибір. У режимі Full RELRO вони є тільки для читання під час виконання.
|
||||
|
||||
- Для зловживання лінивою прив'язкою динамічного зв'язувача для вирішення довільних символів під час виконання, дивіться спеціальну сторінку:
|
||||
|
||||
{{#ref}}
|
||||
../rop-return-oriented-programing/ret2dlresolve.md
|
||||
{{#endref}}
|
||||
|
||||
### Порядок ініціалізації
|
||||
|
||||
1. Програма завантажується в пам'ять, статичні глобальні змінні ініціалізуються в **`.data`** та неініціалізовані обнуляються в **`.bss`**.
|
||||
2. Всі **залежності** для програми або бібліотек **ініціалізуються**, і виконується **динамічне зв'язування**.
|
||||
2. Всі **залежності** програми або бібліотек **ініціалізуються**, і виконується **динамічне зв'язування**.
|
||||
3. Виконуються функції **`PREINIT_ARRAY`**.
|
||||
4. Виконуються функції **`INIT_ARRAY`**.
|
||||
5. Якщо є запис **`INIT`**, він викликається.
|
||||
6. Якщо це бібліотека, dlopen закінчується тут, якщо програма, час викликати **реальну точку входу** (функцію `main`).
|
||||
6. Якщо це бібліотека, dlopen закінчується тут, якщо програма, настав час викликати **реальну точку входу** (функцію `main`).
|
||||
|
||||
## Локальне зберігання потоків (TLS)
|
||||
|
||||
@ -375,8 +439,35 @@ __attributte__((destructor)) //Add to the destructor list
|
||||
|
||||
Коли це використовується, секції **`.tdata`** та **`.tbss`** використовуються в ELF. Вони подібні до `.data` (ініціалізовані) та `.bss` (не ініціалізовані), але для TLS.
|
||||
|
||||
Кожна змінна матиме запис у заголовку TLS, що вказує на розмір та зсув TLS, який є зсувом, який вона використовуватиме в локальній області даних потоку.
|
||||
Кожна змінна матиме запис у заголовку TLS, що вказує на розмір та зсув TLS, який буде використовуватися в локальній області даних потоку.
|
||||
|
||||
`__TLS_MODULE_BASE` - це символ, що використовується для посилання на базову адресу локального зберігання потоків і вказує на область пам'яті, що містить усі локальні дані потоку модуля.
|
||||
`__TLS_MODULE_BASE` - це символ, що використовується для посилання на базову адресу локального зберігання потоків і вказує на область пам'яті, що містить всі дані локального потоку модуля.
|
||||
|
||||
## Допоміжний вектор (auxv) та vDSO
|
||||
|
||||
Ядро Linux передає допоміжний вектор процесам, що містить корисні адреси та прапори для виконання:
|
||||
|
||||
- `AT_RANDOM`: вказує на 16 випадкових байтів, які використовуються glibc для канарки стеку та інших насіння PRNG.
|
||||
- `AT_SYSINFO_EHDR`: базова адреса відображення vDSO (зручно для знаходження системних викликів `__kernel_*` та гаджетів).
|
||||
- `AT_EXECFN`, `AT_BASE`, `AT_PAGESZ` тощо.
|
||||
|
||||
Як атакуючий, якщо ви можете читати пам'ять або файли під `/proc`, ви часто можете витікати ці дані без витоку інформації в цільовому процесі:
|
||||
```bash
|
||||
# Show the auxv of a running process
|
||||
cat /proc/$(pidof target)/auxv | xxd
|
||||
|
||||
# From your own process (helper snippet)
|
||||
#include <sys/auxv.h>
|
||||
#include <stdio.h>
|
||||
int main(){
|
||||
printf("AT_RANDOM=%p\n", (void*)getauxval(AT_RANDOM));
|
||||
printf("AT_SYSINFO_EHDR=%p\n", (void*)getauxval(AT_SYSINFO_EHDR));
|
||||
}
|
||||
```
|
||||
Витік `AT_RANDOM` дає вам значення канарки, якщо ви можете розіменувати цей вказівник; `AT_SYSINFO_EHDR` дає вам базу vDSO для пошуку гаджетів або для прямого виклику швидких системних викликів.
|
||||
|
||||
## References
|
||||
|
||||
- ld.so(8) – Порядок пошуку динамічного завантажувача, RPATH/RUNPATH, правила безпечного виконання (AT_SECURE): https://man7.org/linux/man-pages/man8/ld.so.8.html
|
||||
- getauxval(3) – Допоміжний вектор та константи AT_*: https://man7.org/linux/man-pages/man3/getauxval.3.html
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
Loading…
x
Reference in New Issue
Block a user