macOS Universal binaries & Mach-O Format
Основна інформація
Бінарні файли Mac OS зазвичай компілюються як універсальні бінарні файли. Універсальний бінарний файл може підтримувати кілька архітектур у одному файлі.
Ці бінарні файли відповідають структурі Mach-O, яка в основному складається з:
Заголовка
Команд завантаження
Даних
Fat Header
Шукайте файл за допомогою: mdfind fat.h | grep -i mach-o | grep -E "fat.h$"
Заголовок містить магічні байти, за якими слідує кількість архітектур, які містить файл (nfat_arch
), і кожна архітектура матиме структуру fat_arch
.
Перевірте це за допомогою:
або використовуючи інструмент Mach-O View:
Як ви, можливо, мислите, зазвичай універсальний бінарний файл, скомпільований для 2 архітектур, подвоює розмір того, що скомпільовано лише для 1 архітектури.
Заголовок Mach-O
Заголовок містить базову інформацію про файл, таку як магічні байти для ідентифікації його як файлу Mach-O та інформацію про цільову архітектуру. Ви можете знайти його за допомогою: mdfind loader.h | grep -i mach-o | grep -E "loader.h$"
Типи файлів Mach-O
Існують різні типи файлів, ви можете знайти їх визначені в вихідному коді, наприклад, тут. Найважливіші з них:
MH_OBJECT
: Файл об'єкта, який можна переміщати (проміжні продукти компіляції, ще не виконувані файли).MH_EXECUTE
: Виконувані файли.MH_FVMLIB
: Файл фіксованої бібліотеки VM.MH_CORE
: Відкладені кодиMH_PRELOAD
: Передзавантажений виконуваний файл (більше не підтримується в XNU)MH_DYLIB
: Динамічні бібліотекиMH_DYLINKER
: Динамічний лінкерMH_BUNDLE
: "Файли плагінів". Створені за допомогою -bundle в gcc та явно завантажені за допомогоюNSBundle
абоdlopen
.MH_DYSM
: Супутній файл.dSym
(файл з символами для налагодження).MH_KEXT_BUNDLE
: Розширення ядра.
Або використовуючи Mach-O View:
Прапорці Mach-O
Вихідний код також визначає кілька корисних прапорців для завантаження бібліотек:
MH_NOUNDEFS
: Немає невизначених посилань (повністю зв'язаний)MH_DYLDLINK
: Посилання DyldMH_PREBOUND
: Динамічні посилання попередньо зв'язані.MH_SPLIT_SEGS
: Файл розділяє сегменти r/o та r/w.MH_WEAK_DEFINES
: У бінарному файлі є слабкі визначені символиMH_BINDS_TO_WEAK
: Бінарний файл використовує слабкі символиMH_ALLOW_STACK_EXECUTION
: Зробити стек виконавчимMH_NO_REEXPORTED_DYLIBS
: Бібліотека не містить команд LC_REEXPORTMH_PIE
: Виконавчий файл з незалежними від положення адресиMH_HAS_TLV_DESCRIPTORS
: Є розділ зі змінними локального потокуMH_NO_HEAP_EXECUTION
: Відсутнє виконання для сторінок купи/данихMH_HAS_OBJC
: У бінарному файлі є розділи oBject-CMH_SIM_SUPPORT
: Підтримка симулятораMH_DYLIB_IN_CACHE
: Використовується на dylibs/frameworks у спільному кеші бібліотек.
Команди завантаження Mach-O
Тут вказано розташування файлу в пам'яті, деталізуючи розташування таблиці символів, контекст основного потоку при початку виконання та необхідні спільні бібліотеки. Надаються інструкції для динамічного завантажувача (dyld) щодо процесу завантаження бінарного файлу в пам'ять.
Використовує структуру load_command, визначену в зазначеному loader.h
:
Існує близько 50 різних типів команд завантаження, які система обробляє по-різному. Найпоширеніші з них: LC_SEGMENT_64
, LC_LOAD_DYLINKER
, LC_MAIN
, LC_LOAD_DYLIB
та LC_CODE_SIGNATURE
.
LC_SEGMENT/LC_SEGMENT_64
Фактично, цей тип команди завантаження визначає, як завантажувати сегменти __TEXT (виконавчий код) та __DATA (дані для процесу) згідно з вказаними зміщеннями в розділі Дані при виконанні бінарного файлу.
Ці команди визначають сегменти, які відображаються в віртуальному просторі пам'яті процесу під час його виконання.
Існують різні типи сегментів, такі як сегмент __TEXT, який містить виконавчий код програми, та сегмент __DATA, який містить дані, використовувані процесом. Ці сегменти розташовані в розділі даних файлу Mach-O.
Кожен сегмент може бути поділений на кілька секцій. Структура команди завантаження містить інформацію про ці секції у відповідному сегменті.
У заголовку спочатку ви знаходите заголовок сегмента:
Приклад заголовка сегмента:
Цей заголовок визначає кількість секцій, заголовки яких з'являються після нього:
Приклад заголовка розділу:
Якщо ви додаєте зміщення розділу (0x37DC) + зміщення, де починається архітектура, у цьому випадку 0x18000
--> 0x37DC + 0x18000 = 0x1B7DC
Також можна отримати інформацію про заголовки з командного рядка за допомогою:
Спільні сегменти, завантажені цією командою:
__PAGEZERO
: Це вказує ядру відобразити адресу нуль, щоб її не можна було читати, записувати або виконувати. Змінні maxprot та minprot у структурі встановлені на нуль, щоб показати, що на цій сторінці немає прав на читання-запис-виконання.Це виділення важливе для пом'якшення вразливостей нульового вказівника. Це тому, що XNU застосовує жорстку сторінку нуль, яка забезпечує, що перша сторінка (тільки перша) пам'яті недоступна (крім в i386). Бінарний файл може відповісти цим вимогам, створивши невеликий __PAGEZERO (використовуючи
-pagezero_size
) для охоплення перших 4 кб та зробивши решту пам'яті 32-біт доступною як у режимі користувача, так і в режимі ядра.__TEXT
: Містить виконуваний код з дозволами на читання та виконання (без можливості запису). Загальні розділи цього сегмента:__text
: Скомпільований бінарний код__const
: Константні дані (тільки для читання)__[c/u/os_log]string
: Константи рядків C, Unicode або os logs__stubs
та__stubs_helper
: Використовуються під час процесу завантаження динамічних бібліотек__unwind_info
: Дані розгортання стеку.Зверніть увагу, що весь цей вміст підписаний, але також позначений як виконуваний (створюючи більше варіантів для експлуатації розділів, які не обов'язково потребують цієї привілеї, наприклад, розділів, присвячених рядкам).
__DATA
: Містить дані, які можна читати та писати (без можливості виконання).__got:
Глобальна таблиця зміщень__nl_symbol_ptr
: Вказівник на символи Non lazy (bind at load)__la_symbol_ptr
: Вказівник на символи Lazy (bind on use)__const
: Повинні бути дані тільки для читання (насправді ні)__cfstring
: Строки CoreFoundation__data
: Глобальні змінні (які були ініціалізовані)__bss
: Статичні змінні (які не були ініціалізовані)__objc_*
(__objc_classlist, __objc_protolist, тощо): Інформація, використовувана середовищем виконання Objective-C__DATA_CONST
: __DATA.__const не гарантується, що воно є постійним (права на запис), так само як і інші вказівники та таблиця GOT. Цей розділ робить__const
, деякі ініціалізатори та таблицю GOT (після вирішення) тільки для читання за допомогоюmprotect
.__LINKEDIT
: Містить інформацію для лінкера (dyld), таку як, символи, рядки та записи таблиці перенесення. Це загальний контейнер для вмісту, який не знаходиться ані в__TEXT
, ані в__DATA
, а його вміст описаний в інших командах завантаження.Інформація dyld: Rebase, опкоди для не-лінивого/лінивого/слабкого зв'язування та інформація про експорт
Початок функцій: Таблиця початкових адрес функцій
Дані у коді: Дані на островах в __text
Таблиця символів: Символи в бінарному файлі
Таблиця непрямих символів: Вказівники/заглушки символів
Таблиця рядків
Підпис коду
__OBJC
: Містить інформацію, використовувану середовищем виконання Objective-C. Хоча цю інформацію також можна знайти в сегменті __DATA, в різних розділах __objc_*.__RESTRICT
: Сегмент без вмісту з одним розділом під назвою__restrict
(також порожній), який забезпечує, що під час виконання бінарного файлу будуть ігноруватися змінні середовища DYLD.
Як можна було побачити в коді, сегменти також підтримують прапорці (хоча вони не використовуються дуже часто):
SG_HIGHVM
: Тільки для ядра (не використовується)SG_FVMLIB
: Не використовуєтьсяSG_NORELOC
: Сегмент не має перерозташуванняSG_PROTECTED_VERSION_1
: Шифрування. Використовується, наприклад, Finder для шифрування тексту в сегменті__TEXT
.
LC_UNIXTHREAD/LC_MAIN
LC_UNIXTHREAD/LC_MAIN
LC_MAIN
містить точку входу в атрибуті entryoff. Під час завантаження dyld просто додає це значення до (в пам'яті) бази бінарного файлу, а потім переходить до цієї інструкції для початку виконання коду бінарного файлу.
LC_UNIXTHREAD
містить значення, які повинні мати регістри при запуску головного потоку. Це вже застаріло, але dyld
все ще використовує його. Можна побачити значення регістрів, встановлені цим, за допомогою:
LC_CODE_SIGNATURE
LC_CODE_SIGNATURE
Містить інформацію про підпис коду файлу Mach-O. Він містить лише зсув, який вказує на блоб підпису. Зазвичай це знаходиться в самому кінці файлу. Однак ви можете знайти деяку інформацію про цей розділ у цьому дописі у блозі та у цьому фрагменті.
LC_ENCRYPTION_INFO[_64]
LC_ENCRYPTION_INFO[_64]
Підтримка шифрування бінарного коду. Однак, звісно, якщо зловмисник здатний скомпрометувати процес, він зможе витягнути пам'ять в розшифрованому вигляді.
LC_LOAD_DYLINKER
LC_LOAD_DYLINKER
Містить шлях до виконуваного файлу динамічного зв'язувача, який відображає спільні бібліотеки в адресний простір процесу. Значення завжди встановлено на /usr/lib/dyld
. Важливо зауважити, що в macOS відображення dylib відбувається в режимі користувача, а не в режимі ядра.
LC_IDENT
LC_IDENT
Застарілий, але якщо налаштовано на генерацію дампів при паніці, створюється дамп ядра Mach-O, і версія ядра встановлюється в команді LC_IDENT
.
LC_UUID
LC_UUID
Випадковий UUID. Він корисний для чого завгодно, але XNU кешує його разом з іншою інформацією про процес. Він може бути використаний у звітах про аварії.
LC_DYLD_ENVIRONMENT
LC_DYLD_ENVIRONMENT
Дозволяє вказати змінні середовища для dyld перед виконанням процесу. Це може бути дуже небезпечно, оскільки це може дозволити виконати довільний код всередині процесу, тому ця команда завантаження використовується лише в dyld, побудованому з #define SUPPORT_LC_DYLD_ENVIRONMENT
та додатково обмежує обробку лише змінних у формі DYLD_..._PATH
, що вказують шляхи завантаження.
LC_LOAD_DYLIB
LC_LOAD_DYLIB
Ця команда завантаження описує залежність динамічної бібліотеки, яка вказує завантажувачу (dyld) завантажити та зв'язати цю бібліотеку. Існує команда завантаження LC_LOAD_DYLIB
для кожної бібліотеки, яку потребує бінарний файл Mach-O.
Ця команда завантаження є структурою типу
dylib_command
(яка містить структуру dylib, що описує фактичну залежну динамічну бібліотеку):
Ви також можете отримати цю інформацію з командного рядка за допомогою:
Деякі потенційно пов'язані з шкідливим ПЗ бібліотеки:
DiskArbitration: Моніторинг USB-накопичувачів
AVFoundation: Захоплення аудіо та відео
CoreWLAN: Сканування Wifi.
Mach-O бінарний файл може містити один або більше конструкторів, які будуть виконані перед адресою, вказаною в LC_MAIN. Зміщення будь-яких конструкторів зберігаються в розділі __mod_init_func сегмента __DATA_CONST.
Дані Mach-O
У основі файлу знаходиться область даних, яка складається з кількох сегментів, які визначені в області команд завантаження. В кожному сегменті можуть міститися різні секції даних, причому кожна секція містить код або дані, що специфічні для типу.
Дані - це в основному частина, що містить всю інформацію, яка завантажується командами завантаження LC_SEGMENTS_64
Це включає:
Таблиця функцій: Яка містить інформацію про функції програми.
Таблиця символів: Яка містить інформацію про зовнішню функцію, використану бінарним файлом
Також може містити внутрішні функції, назви змінних та інше.
Для перевірки цього можна використовувати інструмент Mach-O View:
Або з командного рядка:
Загальні розділи Objective-C
У сегменті __TEXT
(r-x):
__objc_classname
: Імена класів (рядки)__objc_methname
: Назви методів (рядки)__objc_methtype
: Типи методів (рядки)
У сегменті __DATA
(rw-):
__objc_classlist
: Вказівники на всі класи Objective-C__objc_nlclslist
: Вказівники на неліниві класи Objective-C__objc_catlist
: Вказівник на категорії__objc_nlcatlist
: Вказівник на неліниві категорії__objc_protolist
: Список протоколів__objc_const
: Константні дані__objc_imageinfo
,__objc_selrefs
,objc__protorefs
...
Swift
_swift_typeref
,_swift3_capture
,_swift3_assocty
,_swift3_types, _swift3_proto
,_swift3_fieldmd
,_swift3_builtin
,_swift3_reflstr
Last updated