Ви працюєте в кібербезпеці компанії? Хочете побачити, як ваша компанія рекламується на HackTricks? або хочете мати доступ до останньої версії PEASS або завантажити HackTricks у форматі PDF? Перевірте ПЛАНИ ПІДПИСКИ!
Відкрийте для себе Сім'ю PEASS, нашу колекцію ексклюзивних NFT
Попередня програма має 9 заголовків програм, після чого відображення сегментів показує, в якому заголовку програми (від 00 до 08) розташовано кожний розділ.
PHDR - Заголовок програми
Містить таблиці заголовків програм та самі метадані.
INTERP
Вказує шлях до завантажувача, який використовується для завантаження бінарного файлу в пам'ять.
LOAD
Ці заголовки використовуються для вказівки способу завантаження бінарного файлу в пам'ять.
Кожен заголовок LOAD вказує на область пам'яті (розмір, дозволи та вирівнювання) та вказує байти ELF бінарного файлу для копіювання туди.
Наприклад, другий має розмір 0x1190, повинен бути розташований за адресою 0x1fc48 з дозволами на читання та запис, і буде заповнений 0x528 з зміщенням 0xfc48 (він не заповнює всі зарезервовані місця). Ця пам'ять буде містити розділи .init_array .fini_array .dynamic .got .data .bss.
DYNAMIC
Цей заголовок допомагає зв'язувати програми з їх залежностями від бібліотек та застосовувати перерозташування. Перевірте розділ .dynamic.
NOTE
Тут зберігаються вендорські метадані про бінарний файл.
GNU_EH_FRAME
Визначає місце таблиць розгортання стеку, які використовуються відладчиками та функціями обробки винятків C++.
GNU_STACK
Містить конфігурацію захисту від виконання стеку. Якщо ввімкнено, бінарний файл не зможе виконувати код зі стеку.
GNU_RELRO
Вказує на конфігурацію RELRO (Relocation Read-Only) бінарного файлу. Цей захист позначатиме як доступний тільки для читання певні розділи пам'яті (наприклад, GOT або таблиці init та fini) після завантаження програми та перед початком її виконання.
У попередньому прикладі копіюється 0x3b8 байтів за адресою 0x1fc48 як доступний тільки для читання, що впливає на розділи .init_array .fini_array .dynamic .got .data .bss.
Зауважте, що RELRO може бути частковим або повним, часткова версія не захищає розділ .plt.got, який використовується для лінивого зв'язування та потребує, щоб цей простір пам'яті мав дозволи на запис для запису адреси бібліотек вперше, коли їх місцезнаходження шукається.
TLS
Визначає таблицю записів TLS, яка зберігає інформацію про змінні локального потоку.
Заголовки розділів
Заголовки розділів надають більш детальний огляд ELF-бінарного файлу.
Таблиця рядків: Вона містить всі рядки, необхідні для файлу ELF (але не ті, які фактично використовуються програмою). Наприклад, вона містить назви секцій, такі як .text або .data. І якщо .text знаходиться на зсуві 45 у таблиці рядків, то вона використовуватиме число 45 у полі name.
Для того щоб знайти, де знаходиться таблиця рядків, ELF містить вказівник на таблицю рядків.
Таблиця символів: Вона містить інформацію про символи, таку як назва (зсув у таблиці рядків), адреса, розмір та інші метадані про символ.
Основні секції
.text: Інструкції програми для виконання.
.data: Глобальні змінні з визначеним значенням у програмі.
.bss: Глобальні змінні, які залишилися неініціалізованими (або ініційовані нулем). Змінні тут автоматично ініціалізуються нулем, тим самим запобігаючи додаванню непотрібних нулів до бінарного файлу.
.rodata: Константні глобальні змінні (секція тільки для читання).
.tdata та .tbss: Подібно до .data та .bss, коли використовуються змінні, що локалізовані для потоку (__thread_local у C++ або __thread у C).
.dynamic: Див. нижче.
Символи
Символ - це іменоване місце в програмі, яке може бути функцією, глобальним об'єктом даних, змінними, локальними для потоку...
readelf -s lnstat
Symbol table '.dynsym' contains 49 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000001088 0 SECTION LOCAL DEFAULT 12 .init
2: 0000000000020000 0 SECTION LOCAL DEFAULT 23 .data
3: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strtok@GLIBC_2.17 (2)
4: 0000000000000000 0 FUNC GLOBAL DEFAULT UND s[...]@GLIBC_2.17 (2)
5: 0000000000000000 0 FUNC GLOBAL DEFAULT UND strlen@GLIBC_2.17 (2)
6: 0000000000000000 0 FUNC GLOBAL DEFAULT UND fputs@GLIBC_2.17 (2)
7: 0000000000000000 0 FUNC GLOBAL DEFAULT UND exit@GLIBC_2.17 (2)
8: 0000000000000000 0 FUNC GLOBAL DEFAULT UND _[...]@GLIBC_2.34 (3)
9: 0000000000000000 0 FUNC GLOBAL DEFAULT UND perror@GLIBC_2.17 (2)
10: 0000000000000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterT[...]
11: 0000000000000000 0 FUNC WEAK DEFAULT UND _[...]@GLIBC_2.17 (2)
12: 0000000000000000 0 FUNC GLOBAL DEFAULT UND putc@GLIBC_2.17 (2)
[...]
Кожен запис символу містить:
Ім'я
Атрибути зв'язування (слабкий, локальний або глобальний): Локальний символ може бути доступний лише програмою самою по собі, тоді як глобальні символи діляться за межами програми. Слабкий об'єкт, наприклад, - це функція, яку можна перевизначити іншою.
Тип: NOTYPE (тип не вказаний), OBJECT (глобальна змінна даних), FUNC (функція), SECTION (секція), FILE (файл із вихідним кодом для відлагоджувачів), TLS (змінна локального потоку), GNU_IFUNC (індиректна функція для перерозташування)
Директорія NEEDED вказує на те, що програмі необхідно завантажити зазначену бібліотеку, щоб продовжити виконання. Директорія NEEDED завершується, коли спільна бібліотека повністю функціональна і готова до використання.
Переміщення
Завантажувач також повинен перемістити залежності після їх завантаження. Ці переміщення вказані в таблиці переміщень у форматах REL або RELA, а кількість переміщень вказана в динамічних розділах RELSZ або RELASZ.
Якщо програма завантажується в інше місце відповідно до бажаної адреси (зазвичай 0x400000) через те, що адреса вже використовується або через ASLR або з будь-якої іншої причини, статичне переналагодження виправляє вказівники, які мали значення, очікуючи, що бінарний файл буде завантажено за бажаною адресою.
Наприклад, будь-який розділ типу R_AARCH64_RELATIV повинен змінити адресу на зміщення переналагодження плюс значення доданка.
Динамічні переналагодження та GOT
Переналагодження також може посилатися на зовнішній символ (наприклад, функцію залежності), наприклад, функцію malloc з libC. Потім, при завантаженні libC за адресою, перевіряючи, де завантажена функція malloc, вона запише цю адресу в таблицю GOT (Global Offset Table) (вказано в таблиці переналагодження), де повинна бути вказана адреса malloc.
Таблиця зв'язку процедур
Розділ PLT дозволяє виконувати ліниве зв'язування, що означає, що розрішення місця розташування функції буде виконано вперше, коли до неї звернуться.
Таким чином, коли програма викликає malloc, вона фактично викликає відповідне місце malloc в PLT (malloc@plt). Перший раз, коли вона викликається, вона вирішує адресу malloc і зберігає її, тому наступного разу, коли викликається malloc, використовується ця адреса замість коду PLT.
Ініціалізація програми
Після того, як програма була завантажена, настав час для її запуску. Однак перший код, який виконується, не завжди є функцією main. Це тому, що, наприклад, у C++, якщо глобальна змінна є об'єктом класу, цей об'єкт повинен бути ініціалізованийперед запуском main, як у:
Зауважте, що ці глобальні змінні розташовані в .data або .bss, але у списках __CTOR_LIST__ та __DTOR_LIST__ об'єкти для ініціалізації та деініціалізації зберігаються у відповідному порядку для відстеження їх.
З коду на мові C можна отримати той самий результат, використовуючи розширення GNU:
__attributte__((constructor)) //Add a constructor to execute before__attributte__((destructor)) //Add to the destructor list
З точки зору компілятора, для виконання цих дій до та після виконання функції main можливо створити функцію init та fini, які будуть посилатися на динамічний розділ як INIT та FIN і розміщені в розділах init та fini у ELF.
Інша опція, як зазначено, полягає в посиланні на списки __CTOR_LIST__ та __DTOR_LIST__ у записах INIT_ARRAY та FINI_ARRAY у динамічному розділі, а їхню довжину вказується за допомогою INIT_ARRAYSZ та FINI_ARRAYSZ. Кожен запис є вказівником на функцію, яка буде викликана без аргументів.
Крім того, можливо мати PREINIT_ARRAY з вказівниками, які будуть виконані перед вказівниками INIT_ARRAY.
Порядок Ініціалізації
Програма завантажується в пам'ять, статичні глобальні змінні ініціалізуються в .data, а незініціалізовані обнуляються в .bss.
Усі залежності для програми або бібліотек ініціалізуються, і виконується динамічне зв'язування.
Виконуються функції PREINIT_ARRAY.
Виконуються функції INIT_ARRAY.
Якщо є запис INIT, він викликається.
Якщо це бібліотека, dlopen закінчується тут, якщо програма, час викликати справжню точку входу (функцію main).
Локальне сховище потоків (TLS)
Вони визначаються за допомогою ключового слова __thread_local в C++ або розширення GNU __thread.
Кожен потік буде зберігати унікальне місце для цієї змінної, тому лише потік може отримати доступ до своєї змінної.
При використанні цього використовуються розділи .tdata та .tbss в ELF. Це схоже на .data (ініціалізовано) та .bss (не ініціалізовано), але для TLS.
Кожна змінна матиме запис у заголовку TLS, вказуючи розмір та зсув TLS, який вона використовуватиме в області локальних даних потоку.
__TLS_MODULE_BASE - це символ, який використовується для посилання на базову адресу локального сховища потоків і вказує на область в пам'яті, яка містить усі дані локального сховища потоків модуля.