Давайте заглянем под капот исполняемого формата iOS, Mach-O.
В этой статье мы собираемся изучить исполняемый файл Mach-O (Mach-Object), который является собственным форматом для исполняемых файлов в macOS, iOS и других системах, основанных на ядре Mach.
Прежде всего, давайте создадим файл IPA, чтобы мы могли его изучить.
Пакет iOS App Store (IPA)
Чтобы создать IPA в Xcode, сначала заархивируйте приложение, щелкните распространить в органайзере, а затем выберите экспорт для групп разработчиков.
Когда экспорт будет завершен и ваш IPA будет создан, просто замените расширение .ipa
на .zip
и разархивируйте его. В распакованной папке будет каталог с именем Payload, который будет содержать приложение.
В демонстрационных целях я создал IPA, ориентированный на iOS 10 до последней версии iOS 13 из репозитория this.
Если у вас нет приложения для практики, вы можете скачать скомпилированное приложение DVIA-v2 здесь.
Теперь давайте посмотрим на двоичный файл. Поскольку приложение нацелено на iOS 10, которая поддерживает 32-разрядную архитектуру, создаваемый Xcode двоичный файл должен быть толстым двоичным файлом. Подождите, что такое толстый двоичный файл?
Жир двоичный
Термин «толстый двоичный файл» достаточно известен, но что он означает на самом деле и как его распознать? И действительно ли двоичный файл толстый? Почему? Недостаточно кардио?
Жирный двоичный файл просто означает, что исполняемый файл может работать на нескольких архитектурах ЦП, например, в мире iOS это может быть amrv7
и arm64
. Где armv7
- это 32-битная архитектура (например, iPhone 3GS / 4 / 4S…), а arm64
- 64-битная архитектура, начиная с iPhone 5S.
Для простого анализа исполняемых файлов Mach-O Apple поставляет otool
macOS. Инструмент, который может исследовать двоичный файл Mach-O. Чтобы узнать поддерживаемые архитектуры двоичного файла, необходимо передать -fv
флагов. Где -f
печатает толстые заголовки, а -v
переводит вывод в известные символы.
Результат выполнения следующей команды otool -fv ~/RE/DVIA-v2/Payload/DVIA-v2.app/DVIA-v2
может выглядеть так:
Fat headers fat_magic FAT_MAGIC nfat_arch 2 architecture armv7 cputype CPU_TYPE_ARM cpusubtype CPU_SUBTYPE_ARM_V7 capabilities 0x0 offset 16384 size 2964496 align 2^14 (16384) architecture arm64 cputype CPU_TYPE_ARM64 cpusubtype CPU_SUBTYPE_ARM64_ALL capabilities 0x0 offset 2981888 size 3573088 align 2^14 (16384)
Если выходные данные пустые, это просто означает, что исполняемый файл не является толстым двоичным файлом и нацелена только на одну архитектуру. Чтобы узнать, какова целевая архитектура при пустом выводе, необходимо взглянуть на заголовок Mach-O, описанный ниже.
Мы подробнее рассмотрим, что означает вывод, в следующем разделе, посвященном формату Mach-O.
Формат Mach-O
Как упоминалось ранее, Mach-O - это формат для собственных исполняемых файлов macOS и iOS. Формат состоит из множества команд и типов данных, которые представлены структурами C. Все структуры можно легко изучить через Xcode.
Я пройдусь по определенным структурам в порядке их размещения в скомпилированном двоичном файле.
Дизассемблированная часть двоичного файла Mach-O
Существует множество дизассемблеров, которые можно использовать для изучения файлов Mach-O. Два моих фаворита - Hopper и Radare2. Для этой демонстрации я использовал Hopper, чтобы разобрать двоичный файл и исследовать в нем часть Mach-O. Полный разобранный вывод можно найти здесь.
Краткий обзор, содержащий команды заголовка и сегмента, можно увидеть во фрагменте ниже.
В самом начале двоичного файла находится заголовок Mach-O, поэтому начнем с него.
Заголовок Mach-O
Каждый двоичный файл Mach-O начинается с заголовка. И здесь нам поможет программа otool
. При переходе к otool
параметр h
выводит заголовок Mach-O или заголовки, если это толстый двоичный файл. Команда otool -hv ./DVIA-v2
приведет к следующему результату:
Поскольку у меня толстый двоичный файл, вывод otool
дает два результата: один для ARM (32-разрядный) и один для ARM64 (64-разрядный).
Откуда взялся заголовок?
Ответ находится в /usr/include/mach-o/loader.h
. Здесь объявляется структура mach_header
и mach_header_64
, которая содержит все значения, полученные в результате otool
. Структурный комментарий очень помогает в его понимании.
Давайте посмотрим на некоторые параметры, которые содержит заголовок.
uint32_t magic также объявлен в loader.h
и содержит эти два объявления:
cputpe объявлен в /usr/include/mach/machine.h
, и там находятся CPU_TYPE_ARM
и CPUT_TYPE_ARM64
вместе со всеми другими архитектурами ЦП, поддерживаемыми Mach-O.
Остальные параметры в структуре заголовка я оставлю для вашего собственного исследования. :)
После заголовка в двоичном файле идет сегмент Mach-O.
Сегмент Mach-O
Сегмент представляет собой часть исполняемого файла, загруженного в память. Это само приложение вместе со всеми связанными библиотеками и фреймворками с компоновщиком. К счастью, комментарий структуры сегментов говорит сам за себя.
Раздел Mach-O
Ранее упомянутая команда сегмента состоит из нуля или более разделов. Когда вы находитесь в среде выполнения программы inlldb
, вы можете исследовать все разделы приложения с помощью команды image dump sections
. Это сбросит все сегменты и их разделы. На изображении ниже показаны все разделы в сегменте DVIA-v2, который является основным исполняемым файлом.
Чтобы получить сегмент загруженного стороннего фреймворка, можно выполнить команду с именем включенной библиотеки. Например, image dump sections Realm
выдаст следующий результат:
Структура раздела выглядит следующим образом.
Есть еще много всего, что нужно исследовать, я настоятельно рекомендую прочитать весь файл loader.h, так как он полон интересных вещей, таких как machine.h и mach.h.
…
Далее я перечислю только имена структур / команд, которые используются в их определенном порядке в двоичном файле.
__macho_dyld_info_command __macho_symtab_command __macho_dysymtab_command __macho_dylinker_command __macho_uuid_command __macho_version_min_command __macho_load_command __macho_entry_point_command __macho_load_command __macho_dylib_command __macho_rpath_command __macho__linkedit_data_command
Давайте кратко рассмотрим выделенные команды. Благодаря их структурным комментариям нет необходимости в дополнительных объяснениях. Спасибо, Apple!
__macho_uuid_command
Эта структура объявляет уникальный идентификатор установки приложения, на который разработчик приложения может, а иногда и должен полагаться.
__macho_version_min_command
Это команда, которая определяет версию ОС, в которой может работать исполняемый файл.
__macho_entry_point_command
Команда, указывающая на функцию main. Основная функция - точка входа в программы.
__macho_dylib_command
В dylib_command
определяются динамические фреймворки и библиотеки, а затем динамический компоновщик -dyld
загружается в память приложения.
В заключении
В этой статье дается обзор того, как исполняемый формат Mach-O выглядит с практической точки зрения. Я надеюсь, что он дал достаточно информации, чтобы продолжить исследование самостоятельно.
Для получения более подробной информации по этой теме я настоятельно рекомендую изучить эти статьи:
Майк Эш - Давайте создадим исполняемый файл Mach-O
Справочник по формату файлов OSX ABi Mach-O
Apple - темы Mach-O
Если вам нравится то, что вы читаете, поделитесь и похлопайте пятьдесят. :)
Если вам понравилась эта статья и вы хотите узнать больше, пожалуйста, ознакомьтесь с моей книгой Модульная архитектура на iOS и macOS - Создание больших масштабируемых приложений и фреймворков iOS и macOS с помощью Domain Driven Design, где я храню последние практики разработки, которые я изучил вместе путь. Или просто свяжитесь с LinkedIn.