macOS Universal binaries & Mach-O Format

Вивчайте хакінг AWS від нуля до героя з htARTE (HackTricks AWS Red Team Expert)!

Інші способи підтримки HackTricks:

Основна інформація

Бінарні файли Mac OS зазвичай компілюються як універсальні бінарні файли. Універсальний бінарний файл може підтримувати кілька архітектур у одному файлі.

Ці бінарні файли відповідають структурі Mach-O, яка в основному складається з:

  • Заголовка

  • Команд завантаження

  • Даних

Fat Header

Шукайте файл за допомогою: mdfind fat.h | grep -i mach-o | grep -E "fat.h$"

#define FAT_MAGIC	0xcafebabe
#define FAT_CIGAM	0xbebafeca	/* NXSwapLong(FAT_MAGIC) */

struct fat_header {
	uint32_t	magic;		/* FAT_MAGIC or FAT_MAGIC_64 */
	uint32_t	nfat_arch;	/* number of structs that follow */
};

struct fat_arch {
cpu_type_t	cputype;	/* cpu specifier (int) */
cpu_subtype_t	cpusubtype;	/* machine specifier (int) */
uint32_t	offset;		/* file offset to this object file */
uint32_t	size;		/* size of this object file */
uint32_t	align;		/* alignment as a power of 2 */
};

Заголовок містить магічні байти, за якими слідує кількість архітектур, які містить файл (nfat_arch), і кожна архітектура матиме структуру fat_arch.

Перевірте це за допомогою:

% file /bin/ls
/bin/ls: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64e:Mach-O 64-bit executable arm64e]
/bin/ls (for architecture x86_64):	Mach-O 64-bit executable x86_64
/bin/ls (for architecture arm64e):	Mach-O 64-bit executable arm64e

% otool -f -v /bin/ls
Fat headers
fat_magic FAT_MAGIC
nfat_arch 2
architecture x86_64
    cputype CPU_TYPE_X86_64
cpusubtype CPU_SUBTYPE_X86_64_ALL
capabilities 0x0
    offset 16384
    size 72896
    align 2^14 (16384)
architecture arm64e
    cputype CPU_TYPE_ARM64
cpusubtype CPU_SUBTYPE_ARM64E
capabilities PTR_AUTH_VERSION USERSPACE 0
    offset 98304
    size 88816
    align 2^14 (16384)

або використовуючи інструмент Mach-O View:

Як ви, можливо, мислите, зазвичай універсальний бінарний файл, скомпільований для 2 архітектур, подвоює розмір того, що скомпільовано лише для 1 архітектури.

Заголовок Mach-O

Заголовок містить базову інформацію про файл, таку як магічні байти для ідентифікації його як файлу Mach-O та інформацію про цільову архітектуру. Ви можете знайти його за допомогою: mdfind loader.h | grep -i mach-o | grep -E "loader.h$"

#define	MH_MAGIC	0xfeedface	/* the mach magic number */
#define MH_CIGAM	0xcefaedfe	/* NXSwapInt(MH_MAGIC) */
struct mach_header {
uint32_t	magic;		/* mach magic number identifier */
cpu_type_t	cputype;	/* cpu specifier (e.g. I386) */
cpu_subtype_t	cpusubtype;	/* machine specifier */
uint32_t	filetype;	/* type of file (usage and alignment for the file) */
uint32_t	ncmds;		/* number of load commands */
uint32_t	sizeofcmds;	/* the size of all the load commands */
uint32_t	flags;		/* flags */
};

#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */
#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */
struct mach_header_64 {
uint32_t	magic;		/* mach magic number identifier */
int32_t		cputype;	/* cpu specifier */
int32_t		cpusubtype;	/* machine specifier */
uint32_t	filetype;	/* type of file */
uint32_t	ncmds;		/* number of load commands */
uint32_t	sizeofcmds;	/* the size of all the load commands */
uint32_t	flags;		/* flags */
uint32_t	reserved;	/* reserved */
};

Типи файлів:

  • MH_EXECUTE (0x2): Стандартний виконавчий файл Mach-O

  • MH_DYLIB (0x6): Динамічна зв'язана бібліотека Mach-O (тобто .dylib)

  • MH_BUNDLE (0x8): Пакунок Mach-O (тобто .bundle)

# Checking the mac header of a binary
otool -arch arm64e -hv /bin/ls
Mach header
magic  cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64    ARM64          E USR00     EXECUTE    19       1728   NOUNDEFS DYLDLINK TWOLEVEL PIE

Або використовуючи Mach-O View:

Команди завантаження Mach-O

Макет файлу в пам'яті вказаний тут, деталізуючи розташування таблиці символів, контекст основного потоку під час початку виконання та необхідні спільні бібліотеки. Інструкції надаються динамічному завантажувачу (dyld) щодо процесу завантаження бінарного файлу в пам'ять.

Використовує структуру load_command, визначену в зазначеному loader.h:

struct load_command {
uint32_t cmd;           /* type of load command */
uint32_t cmdsize;       /* total size of command in bytes */
};

Існує близько 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.

Кожен сегмент може бути поділений на кілька секцій. Структура команди завантаження містить інформацію про ці секції у відповідному сегменті.

У заголовку спочатку ви знаходите заголовок сегмента:

struct segment_command_64 { /* для 64-бітних архітектур */
uint32_t	cmd;		/* LC_SEGMENT_64 */
uint32_t	cmdsize;	/* включає розмір структур section_64 */
char		segname[16];	/* назва сегмента */
uint64_t	vmaddr;		/* адреса пам'яті цього сегмента */
uint64_t	vmsize;		/* розмір пам'яті цього сегмента */
uint64_t	fileoff;	/* зміщення файлу цього сегмента */
uint64_t	filesize;	/* кількість для відображення з файлу */
int32_t		maxprot;	/* максимальний захист VM */
int32_t		initprot;	/* початковий захист VM */
	uint32_t	nsects;		/* кількість секцій у сегменті */
	uint32_t	flags;		/* прапорці */
};

Приклад заголовка сегмента:

Цей заголовок визначає кількість секцій, заголовки яких з'являються після нього:

struct section_64 { /* for 64-bit architectures */
char		sectname[16];	/* name of this section */
char		segname[16];	/* segment this section goes in */
uint64_t	addr;		/* memory address of this section */
uint64_t	size;		/* size in bytes of this section */
uint32_t	offset;		/* file offset of this section */
uint32_t	align;		/* section alignment (power of 2) */
uint32_t	reloff;		/* file offset of relocation entries */
uint32_t	nreloc;		/* number of relocation entries */
uint32_t	flags;		/* flags (section type and attributes)*/
uint32_t	reserved1;	/* reserved (for offset or index) */
uint32_t	reserved2;	/* reserved (for count or sizeof) */
uint32_t	reserved3;	/* reserved */
};

Приклад заголовка розділу:

Якщо ви додаєте зміщення розділу (0x37DC) + зміщення, де починається архітектура, у цьому випадку 0x18000 --> 0x37DC + 0x18000 = 0x1B7DC

Також можна отримати інформацію про заголовки з командного рядка за допомогою:

otool -lv /bin/ls

Загальні сегменти, завантажені цією командою:

  • __PAGEZERO: Це вказує ядру відобразити адресу нуль, щоб її не можна було читати, записувати або виконувати. Змінні maxprot та minprot у структурі встановлені на нуль, щоб показати, що на цій сторінці немає прав на читання-запис-виконання.

  • Це виділення важливе для пом'якшення вразливостей нульових вказівників.

  • __TEXT: Містить виконуваний код з правами на читання та виконання (без можливості запису). Загальні розділи цього сегмента:

  • __text: Скомпільований бінарний код

  • __const: Константні дані

  • __cstring: Рядкові константи

  • __stubs та __stubs_helper: Використовуються під час процесу завантаження динамічних бібліотек

  • __DATA: Містить дані, які можна читати та писати (без можливості виконання).

  • __data: Глобальні змінні (які були ініціалізовані)

  • __bss: Статичні змінні (які не були ініціалізовані)

  • __objc_* (__objc_classlist, __objc_protolist, тощо): Інформація, використовувана в середовищі виконання Objective-C

  • __LINKEDIT: Містить інформацію для лінкера (dyld), таку як "запис, рядок та записи таблиці перенесення."

  • __OBJC: Містить інформацію, використовувану в середовищі виконання Objective-C. Хоча цю інформацію також можна знайти в сегменті __DATA, у різних розділах __objc_*.

LC_MAIN

Містить точку входу в атрибуті entryoff. Під час завантаження dyld просто додає це значення до (в пам'яті) бази бінарного файлу, а потім переходить до цієї інструкції для початку виконання коду бінарного файлу.

LC_CODE_SIGNATURE

Містить інформацію про підпис коду файлу Mach-O. Він містить лише зсув, який вказує на блоб підпису. Зазвичай це знаходиться в самому кінці файлу. Однак деяку інформацію про цей розділ можна знайти в цьому блозі та цьому gists.

LC_LOAD_DYLINKER

Містить шлях до виконавчого файлу динамічного лінкера, який відображає спільні бібліотеки в адресний простір процесу. Значення завжди встановлене на /usr/lib/dyld. Важливо зауважити, що в macOS відображення dylib відбувається в режимі користувача, а не в режимі ядра.

LC_LOAD_DYLIB

Ця команда завантаження описує залежність динамічної бібліотеки, яка вказує завантажувачу (dyld) завантажити та зв'язати цю бібліотеку. Існує команда завантаження LC_LOAD_DYLIB для кожної бібліотеки, яку потребує бінарний файл Mach-O.

  • Ця команда завантаження є структурою типу dylib_command (яка містить структуру dylib, що описує фактичну залежну динамічну бібліотеку):

struct dylib_command {
uint32_t        cmd;            /* LC_LOAD_{,WEAK_}DYLIB */
uint32_t        cmdsize;        /* includes pathname string */
struct dylib    dylib;          /* the library identification */
};

struct dylib {
union lc_str  name;                 /* library's path name */
uint32_t timestamp;                 /* library's build time stamp */
uint32_t current_version;           /* library's current version number */
uint32_t compatibility_version;     /* library's compatibility vers number*/
};

Ви також можете отримати цю інформацію з командного рядка за допомогою:

otool -L /bin/ls
/bin/ls:
/usr/lib/libutil.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0, current version 5.4.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1319.0.0)

Деякі потенційно пов'язані з шкідливим ПЗ бібліотеки:

  • DiskArbitration: Моніторинг USB-накопичувачів

  • AVFoundation: Захоплення аудіо та відео

  • CoreWLAN: Сканування Wifi.

Mach-O бінарний файл може містити один або більше конструкторів, які будуть виконані перед адресою, вказаною в LC_MAIN. Зміщення будь-яких конструкторів зберігаються в розділі __mod_init_func сегмента __DATA_CONST.

Дані Mach-O

У основі файлу знаходиться область даних, яка складається з кількох сегментів, які визначені в області команд завантаження. У кожному сегменті можуть міститися різні секції даних, причому кожна секція містить код або дані, що специфічні для типу.

Дані - це в основному частина, що містить всю інформацію, яка завантажується командами завантаження LC_SEGMENTS_64

Це включає:

  • Таблиця функцій: Яка містить інформацію про функції програми.

  • Таблиця символів: Яка містить інформацію про зовнішню функцію, використану бінарним файлом

  • Також може містити внутрішні функції, назви змінних та інше.

Для перевірки цього можна використовувати інструмент Mach-O View:

Або з командного рядка:

size -m /bin/ls
Вивчайте хакінг AWS від нуля до героя з htARTE (HackTricks AWS Red Team Expert)!

Інші способи підтримки HackTricks:

Last updated