Автор перевода: Norbert Beaver
Переведено специально для форума xss.pro
Источник: github.com/Karmaz95/Snake_Apple/blob/main/README.md
Детальный разбор файлов Mach-O на архитектуре x64 MacOS с помощью Python.
ВВЕДЕНИЕ
Добро пожаловать в первую часть цикла статей о средствах безопасности MacOS.
В данной статье предоставляется детальная информация о формате файлов Mach-O, а также кратко описывается процесс его загрузки в виртуальную память. В данной статье вы узнаете о: определении подлинного Mach-O файла; порядке следования его байтов; различных типов файлов Mach-O; сегментации; концепции последовательных исправлений и многом другом.
К тому-же, мы расскажем о том, каким образом можно извлечь важную информацию из рассматриваемых файлов в читабельном виде с помощью Python и других инструментов.
Mach-O
Формат файла, используемый на системах macOS в следующих видах:
Mach-O состоит из Заголовка, которому предшествует последовательность Команд загрузки, а затем один или более Сегментов, разделяемых на Секции.
Основная структура содержит в себе Команды загрузки переменной длины, указывающие на страницы данных в файле. Ниже представлена упрощенная схема:
Универсальный бинарный формат – это концепт, в котором используется файл Mach-O в системах macOS для создания единого исполняемого файла, содержащего в себе скомпилированный код для множества архитектур. Вы можете рассмотреть его структуру ниже:
Страницы памяти
Все биты из заголовков, сегментов, секций и бинарных данных хранятся в страницах памяти, - самых маленьких единицах в дисциплине управления памятью.
Это блок памяти фиксированной длины, который в среднем весит несколько килобайт. Для macOS и 64-разрядных архитектур, их вес составляет 16 килобайт. Однако macOS может содержать в себе как 4 так и 16-килобайтные страницы. Сама информация хранится в vm_param.h:
Загрузка в память
Когда пользователь запускает приложение на macOS, то файл Mach-O загружается в память с помощью ядра XNU, чтобы процессор смог выполнить его инструкции.
Следующие шаги описывают общий процесс работы XNU ядра:
Последнюю версию исходного кода dyld и XNU можно найти на apple-oss-distributions.
Заголовок
Структурой
Это используется для определения Mach-O файла и предоставления информации о том, каким образом следует загрузить его в памяти. Снизу приведены примеры подлинных заголовков Mach:
Вы можете использовать команду
Magic
Поле
Если двоичный файл был скомпилирован для машины с «младшеконечным» форматом следования байтов, и затем переведен на машину со «старшеконечным» форматом следования байтов, то значение magic будет
XNU читает значение magic с помощью собственного порядка байтов хоста. В случае, если значение совпадает с
CPU Type
В поле
Переменная типа
Значение
В machine.h описано множество subtypes (подтипов), но для 64-разрядной архитектуры подходят лишь немногие. Снизу показаны доступные:
К тому-же,
File Types
Flags
В 32-битном поле
Команды загрузки
Структура
К примеру, размер типа команды
В 64-разрядной архитектуре все части Mach-O должны быть кратны 8 байтам и дополняться
Продолжая эту логику, поскольку команды загрузки отвечают за указание Dynamic Linker-у (dyld), как загрузить весь файл Mach-O в память, поле
Защита памяти на 64-разрядной архитектуре
Файл Mach-O, загружаемый с помощью XNU в виртуальную память, разделен на сегменты, связанные с определенными атрибутами защиты памяти.
Они устанавливаются с помощью
Первыми командами запуска в файле Mach-O являются сегменты:
Различные сегменты. Менее распространенные отмечены серым:
Секции
Сегменты подразделяются на секции (
Каждая секция имеет свой собственный размер и содержимое загружаемого файла, но она наследует защиту памяти от сегмента, к которому принадлежит. Разделы с суффиксами
Существует также множество других секций. Вот некоторые из самых распространенных
__PAGEZERO
Указывает ядру зарезервировать начальные 4гб (
Поскольку к данной (
__TEXT
Поскольку размер файла сегмента
В виртуальной памяти, он начинается с
__DATA_CONST
Данный сегмент хранит глобальные переменные, которые помечены как постоянные данные. Во время выполнения
__DATA
Содержит данные для чтения и записи. Является хранилищем для глобальных и статичных переменных, а также для неисполняемых структур данных во время выполнения.
__RESTRICT
Пустой сегмент с пустой секцией
__LINKEDIT
Не имеет секций и описывается командами запуска, запускаемыми после него, таких как:
Сегмент
Цепные исправления
После множества команд загрузки (
К сожалению, MachOView пока не поддерживает цепные исправления. До macOS 12 информация, необходимая для выполнения этой задачи, использовалась
Значение
Обратите внимание на пример
Благодаря выравниванию,
Структура указывает количество сегментов в бинарном файле и местоположение их
Каждый
Изображение ниже показывает эту структуру, разобранную с помощью команды
В каждом
Переведено специально для форума xss.pro
Источник: github.com/Karmaz95/Snake_Apple/blob/main/README.md
Детальный разбор файлов Mach-O на архитектуре x64 MacOS с помощью Python.
ВВЕДЕНИЕ
Добро пожаловать в первую часть цикла статей о средствах безопасности MacOS.
В данной статье предоставляется детальная информация о формате файлов Mach-O, а также кратко описывается процесс его загрузки в виртуальную память. В данной статье вы узнаете о: определении подлинного Mach-O файла; порядке следования его байтов; различных типов файлов Mach-O; сегментации; концепции последовательных исправлений и многом другом.
К тому-же, мы расскажем о том, каким образом можно извлечь важную информацию из рассматриваемых файлов в читабельном виде с помощью Python и других инструментов.
Mach-O
Формат файла, используемый на системах macOS в следующих видах:
- Исполняемый файл. Формат, который понимается системой и может быть исполнен. Конечный вид ПО, используемый пользователями.
clang -o hello hello.c - Код объекта (.o). Скомпилированный исходный код, который должен быть связан с другими кодами и библиотеками для создания исполняемого файла или разделяемой библиотеки.
clang -c -o hello.o hello.c - Динамическая библиотека (.dylib). Предварительно компилированный код, доступный многим программам, загружаемым в память в момент запуска программы.
clang -shared -o libmylib.dylib mylib.c - Пакет загрузки (плагин). Пакеты исполняемого кода и соответствующих ресурсов, которые могут быть запущены с помощью NSBundle и dlopen в момент исполнения программы.
clang -bundle MyBundle.c -o MyBundle - dSYM. Файл, генерируемый, когда Strip Debug Symbols включены в настройки проекта, содержащего в себе символы отладки.
clang -g -o hello_dsym hello.c
Пять самых распространенных типов файлов
Структура Mach-OMach-O состоит из Заголовка, которому предшествует последовательность Команд загрузки, а затем один или более Сегментов, разделяемых на Секции.
Основная структура содержит в себе Команды загрузки переменной длины, указывающие на страницы данных в файле. Ниже представлена упрощенная схема:
Упрощенная структура Mach-O
- Заголовок – метаданные, такие как тип файла и архитектура процессора.
- Команды загрузки – информируют XNU и dyld о том, как загружать Mach-O файл.
- Сегменты – информируют ядро о том, каким образом следует отображать Mach-O в виртуальной памяти и как указывать средства защиты памяти.
- Секции – Данные в Сегментах, которые наследуют средства защиты памяти.
Универсальный бинарный формат – это концепт, в котором используется файл Mach-O в системах macOS для создания единого исполняемого файла, содержащего в себе скомпилированный код для множества архитектур. Вы можете рассмотреть его структуру ниже:
Пример Универсального бинарного формата
Ядро загружает самый подходящий для архитектуры машины бинарный формат. Для загрузки другой архитектуры из Толстого двоичного формата используйте команду arch:
Python:
arch -x86_64 PATH
Страницы памяти
Все биты из заголовков, сегментов, секций и бинарных данных хранятся в страницах памяти, - самых маленьких единицах в дисциплине управления памятью.
Это блок памяти фиксированной длины, который в среднем весит несколько килобайт. Для macOS и 64-разрядных архитектур, их вес составляет 16 килобайт. Однако macOS может содержать в себе как 4 так и 16-килобайтные страницы. Сама информация хранится в vm_param.h:
Обычно, macOS работает с 16-килобайтными страницами памяти. Однако, существуют ситуации, в которых он работает и с 4-килобайтными страницами, например, вычисление хэша для подписи кода.
Загрузка в память
Когда пользователь запускает приложение на macOS, то файл Mach-O загружается в память с помощью ядра XNU, чтобы процессор смог выполнить его инструкции.
Следующие шаги описывают общий процесс работы XNU ядра:
- Вычисляет случайные сдвиги для загружающихся адресов (ALSR)
- Преобразовывает Mach-O файл, чтобы тот мог читать определенные команды запуска в ядре XNU
- Преобразовывает данные из файла Mach-O в отделы памяти
- Преобразовывает dyld в память на основе
LC_LOAD_DYLINKER - Преобразовывает совместную кэш-память Dyld
- Преобразовывает стек потока в
0x16FE0000+ASLR
- Преобразовывает Mach-O файл, чтобы тот мог читать определенные команды запуска в dyld
- Перераспределяет все относительные ссылки на адреса внутри файла
- Связывает общие библиотеки (dylibs)
- Разделяет символьные ссылки на функции и переменные
- Передает управление точке входа исполняемого файла
dyld_info:
Последнюю версию исходного кода dyld и XNU можно найти на apple-oss-distributions.
Заголовок
Структурой
mach_header_64 является 64-битный заголовок Mach-O, который появляется в начале файла или в смещении, указанном в заголовке толстого двоичного формата.
Это используется для определения Mach-O файла и предоставления информации о том, каким образом следует загрузить его в памяти. Снизу приведены примеры подлинных заголовков Mach:
Вы можете использовать команду
otool с флагами –hv для того, чтобы посмотреть информацию из командной строки:
Magic
Поле
magic используется для определения формата файла и порядка битов.
Если двоичный файл был скомпилирован для машины с «младшеконечным» форматом следования байтов, и затем переведен на машину со «старшеконечным» форматом следования байтов, то значение magic будет
MH_CIGAM_64. В ином случае, значение magic будет MH_MAGIC_64.
Скомпилировано на машине с «младшеконечным» форматом для машины с «младшеконечным» форматом.
XNU читает значение magic с помощью собственного порядка байтов хоста. В случае, если значение совпадает с
MH_CIGAM_64, он изменяет порядок байтов двоичного файла с помощью функции ядра NXSwapInt которая принимает целое число без знака в качестве аргумента и возвращает значение замены байтов
CPU Type
В поле
cputype указывается целевая архитектура:
Переменная типа
cpu_type_t является псевдонимом целого числа и может быть найдена в machine.h в репозитории. Ниже представлены все типы и их значения:На системах macOS с 64-разрядными процессорами, размер
integer_t составляет 4 байта.
CPU SubtypeЗначение
cpusubtype определяет конкретного участника семейства ЦП и используется в сочетании с cputype для проверки совместимости аппаратного обеспечения.
В machine.h описано множество subtypes (подтипов), но для 64-разрядной архитектуры подходят лишь немногие. Снизу показаны доступные:
К тому-же,
cpusubtype используется для извлечения версии аутентификации указателя с помощью побитового процесса ANDing в cpusubtype с маской 0x0f000000 и последующего сдвига результата на 24 бита вправо:
File Types
filetype определяет структуру файла.
Устаревшие типы файлов были выделены серым цветом
Flags
В 32-битном поле
flags в заголовке файла Mach-O хранятся константы, которые определяют дополнительные параметры для компоновщика (ld) или динамического компоновщика (dyld).
Самые распространенные flags
Все flags с дополнительными комментариями к ним вы можете найти в loader.h.
Команды загрузки
Структура
load_command следует прямо за заголовком Mach. Каждая команда загрузки должна начинаться с двух полей: cmd, которое является типом команды загрузки и cmdsize, которая хранит его размер.
К примеру, размер типа команды
LC_SEGMENT_64 составляет 72 байта:
Все константы для типов команд запуска (
cmd) описаны в loader.h.
Выравнивание на 64-разрядной архитектуреВ 64-разрядной архитектуре все части Mach-O должны быть кратны 8 байтам и дополняться
0x00 байтами. В частности, я имею в виду заголовок, команды загрузки, сегменты, секции и таблицы в части данных.Продолжая эту логику, поскольку команды загрузки отвечают за указание Dynamic Linker-у (dyld), как загрузить весь файл Mach-O в память, поле
cmdsize всегда должно быть кратно 8 байтам.
Пример размера команды загрузки LC_MAIN
Защита памяти на 64-разрядной архитектуре
Файл Mach-O, загружаемый с помощью XNU в виртуальную память, разделен на сегменты, связанные с определенными атрибутами защиты памяти.
Они устанавливаются с помощью
mprotect() во время отображения памяти с помощью mmap():
Функция архитектуры, называемая eXecute Never (NX), не позволяет одновременно устанавливать защиту какой-либо области памяти как для записи, так и для выполнения (
W ^ X).
СегментыПервыми командами запуска в файле Mach-O являются сегменты:
segment_command_64
Различные сегменты. Менее распространенные отмечены серым:
Секции
Сегменты подразделяются на секции (
section_64):
Значения Flags можно найти здесь: loader.h
Каждая секция имеет свой собственный размер и содержимое загружаемого файла, но она наследует защиту памяти от сегмента, к которому принадлежит. Разделы с суффиксами
__object относятся к Objective-C, а __swift5 - к Swift.
Секция __text в сегменте __TEXT
Существует также множество других секций. Вот некоторые из самых распространенных
__PAGEZERO
Указывает ядру зарезервировать начальные 4гб (
0x100000000) виртуальной памяти. Может также занять больше места (slide), в случае, если используется флаг MH_PIE.
Поскольку к данной (
---) области не применяются никакие права защиты, то любая попытка доступа через нулевой указатель будет считана и в последствии приведет к сбою.
Этот сегмент не занимает места на диске.
__TEXT
Поскольку размер файла сегмента
__PAGEZERO равен нулю, то __TEXT является первым «настоящим» сегментом и находится в начале файла.В виртуальной памяти, он начинается с
0x100000000 (+slide, если используется MH_PIE) и отмечен как читабельный и исполняемый. Он хранит данные только для чтения и код исполнения. Также, он состоит из заголовка и команд запуска Mach-O.
__DATA_CONST
Данный сегмент хранит глобальные переменные, которые помечены как постоянные данные. Во время выполнения
mmap, __DATA_CONST доступен для чтения и записи. Однако, после инициализации, dyld управляеятся флагом сегмента SG_READ_ONLY для того, чтобы сделать его только для чтения через mprotect.
Одним из вариантов использования этого является неленивое связывание
__got.__DATA
Содержит данные для чтения и записи. Является хранилищем для глобальных и статичных переменных, а также для неисполняемых структур данных во время выполнения.
__RESTRICT
Пустой сегмент с пустой секцией
__restrict, только для чтения. Указывает dyld подтвердить подпись для всего загружаемого кода.
__LINKEDIT
Не имеет секций и описывается командами запуска, запускаемыми после него, таких как:
LC_SYMTAB, LC_MAIN, LC_LOAD_DYLIB и т.д. Содержит данные только для чтения, используемые dyld во время связки символов.
Сегмент
__LINKEDIT, к примеру, начинается с 0xC000 и заканчивается 0xC000 + 0x4EC0 == 0x10EC0, что является последним байтом в двоичном файле:
__LINKEDIT должен быть последним сегментом. Dynamic Linker (dyld) отклонит любые двоичные файлы с данными сегмента после __LINKEDIT.Цепные исправления
После множества команд загрузки (
LC_SEGMENT_64), ответственных за загрузку сегментов в виртуальную память, пришло время поговорить о привязке и перемещении.- Привязка разрешает символьные ссылки в двоичном файле. Это процесс замены символов, которые относятся к коду или данным, фактическими адресами.
- Перемещение следит за тем, чтобы все указатели указывали на правильное местоположение, путем добавления базового адреса динамической библиотеки, из которой они пришли, к их сдвигу.
К сожалению, MachOView пока не поддерживает цепные исправления. До macOS 12 информация, необходимая для выполнения этой задачи, использовалась
LC_DYLD_INFO.Пример цепных исправлений
Они обрабатываются с помощью
LC_DYLD_CHAINED_FIXUPS, на которую ссылается linkedit_data_command. Комментарии к структуре связаны с приведенным выше примером:
Значение
dataoff является сдвигом dyld_chained_fixups_header, который определяет цепные исправления.
Значения в комментариях относятся к ниже приведенному signed_ad_hoc_example.
Обратите внимание на пример
dyld_chained_fixups_header, помеченного зеленым цветом в данных LC_DYLD_CHAINED_FIXUPS, помеченных красным цветом:
Благодаря выравниванию,
dyld_chained_starts_in_image начинается с 0xC020, после четырех идущих байтов 0x00 в 0xC016–0xC020, как указано в starts_offset.
Структура указывает количество сегментов в бинарном файле и местоположение их
dyld_chained_starts_in_segment. Поле seg_count определяет фактическое количество элементов в этом массиве. Для нашего примера бинарного файла структура будет выглядеть следующим образом:
- dyld_chained_starts_in_image помечено желтым
- dyld_chained_starts_in_segment[2] помечено голубым
- dyld_chained_starts_in_segment[3] помечено серым
Пример dyld_chained_starts_in_image
Каждый
dyld_chained_starts_in_segment описан ниже. Числа в комментариях относятся к segment[2] (выделенного голубым цветом).
Значения, основанные на signed_ad_hoc_example
Изображение ниже показывает эту структуру, разобранную с помощью команды
dyld_info:
В каждом
dyld_chained_starts_in_segment:- Массив
page_start[0]указывает на первое исправление на странице, а следующее исправление находится на текущей позиции плюс значение следующего поля, умноженное на 4. - Эти исправления 64-битные и могут быть либо
повторным базированием, либосвязыванием, при этом тип указывается младшим битом.
pointer_format. Все они описаны в fixup-chains.h. Ниже вы можете увидеть структуру исправления для pointer_format == 0x6:***Достигнуто максимальное количество загружаемых файлов, продолжение ниже***