diff --git a/src/binary-exploitation/basic-stack-binary-exploitation-methodology/elf-tricks.md b/src/binary-exploitation/basic-stack-binary-exploitation-methodology/elf-tricks.md index 151b25790..0e2c960d9 100644 --- a/src/binary-exploitation/basic-stack-binary-exploitation-methodology/elf-tricks.md +++ b/src/binary-exploitation/basic-stack-binary-exploitation-methodology/elf-tricks.md @@ -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 // 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 +#include +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}}