Как открыть и изучить Mach-O файл на macOS: практическое руководство

Если вы столкнулись с файлом в формате Mach-O на macOS — будь то исполняемый файл, динамическая библиотека или объектный файл — и вам нужно понять, что внутри, вы не одиноки. Разработчики, исследователи безопасности и системные администраторы регулярно работают с этим форматом. В отличие от PE-файлов (Portable Executable), которые используются в Windows, Mach-O — это формат, принятый в macOS и iOS. Но принципы анализа во многом схожи: заголовок, секции, символы, импорты/экспорты. Разберёмся, как к этому подступиться.

Что такое Mach-O и зачем его разбирать

Mach-O (Mach Object) — формат исполняемых файлов, объектных файлов и динамических библиотек, используемый в системах на базе ядра Mach. Он пришёл из NeXTSTEP и стал стандартом для macOS после перехода компании Apple на эту платформу.

По структуре Mach-O напоминает PE: есть заголовок с метаданными, описание сегментов и секций, таблица символов, информация о перемещениях (relocations) и т.д. Если вы уже работали с PE-файлами, то поймёте аналогию: вместо DOS Header и PE Header здесь будет mach_header или mach_header_64, а вместо секций .text, .data — сегменты и секции внутри них.

Зачем это нужно на практике:

  • Анализ вредоносного ПО для macOS.
  • Реверс-инжиниринг проприетарных приложений.
  • Отладка и поиск ошибок в собственных программах.
  • Проверка зависимостей и экспортируемых символов библиотек.
  • Понимание, как собрано и слинковано приложение.

Инструменты, которые понадобятся

На macOS набор инструментов для работы с Mach-O достаточно богат. Вот основные:

Инструмент Назначение Уровень сложности
otool Просмотр заголовков, секций, символов, зависимостей. Встроен в macOS. Начальный
nm Просмотр таблицы символов (имена, типы, адреса). Начальный
file Определение типа файла, архитектуры (x86_64, arm64). Начальный
dyld_info (из dyld) Детальная информация о связке (binding, rebasing, exports). Средний
objdump Дизассемблирование, просмотр секций. Средний
Hopper Disassembler Графический дизассемблер с декомпиляцией. Платный. Средний-продвинутый
radare2 Мощный open-source фреймворк для бинарного анализа. Продвинутый
IDA Pro Профессиональный дизассемблер. Платный, дорогой. Продвинутый

Шаг 1: Определяем, что за файл перед нами

Прежде чем лезть в структуру, нужно понять базовые характеристики файла. Это аналог того, как при работе с PE вы сначала смотрите на сигнатуру «MZ» и определяете, это EXE, DLL или SYS.

Откройте терминал и выполните:

file /path/to/binary

Вывод может выглядеть примерно так:

  • Mach-O 64-bit executable x86_64 — обычный исполняемый файл для Intel Mac.
  • Mach-O 64-bit executable arm64 — нативный для Apple Silicon (M1/M2/M3).
  • Mach-O universal binary with 2 architectures — fat binary (универсальный), содержит код для нескольких архитектур.
  • Mach-O dynamically linked shared library — динамическая библиотека (.dylib).
  • Mach-O object file — объектный файл (.o), ещё не слинкованный.

Если файл — fat binary, можно извлечь нужную архитектуру с помощью lipo:

lipo /path/to/binary -thin arm64 -output /path/to/output_binary

Шаг 2: Смотрим заголовок Mach-O

Заголовок — это точка входа в структуру файла. Аналог PE Header. Для просмотра используйте otool -h:

otool -h /path/to/binary

Вывод покажет ключевые поля:

  • magic — сигнатура. 0xfeedface для 32-bit, 0xfeedfacf для 64-bit, 0xcafebabe для fat binary.
  • cputype и cpusubtype — тип и подтип процессора.
  • filetype — тип файла: MH_EXECUTE (исполняемый), MH_DYLIB (динамическая библиотека), MH_BUNDLE (bundle), MH_OBJECT (объектный файл).
  • ncmds — количество load commands.
  • sizeofcmds — суммарный размер всех load commands.

Для более подробного вывода со всеми load commands:

otool -l /path/to/binary

Это аналог просмотра Data Directories и Section Headers в PE. Вы увидите сегменты (LC_SEGMENT или LC_SEGMENT_64), пути к библиотекам (LC_LOAD_DYLIB), точку входа (LC_MAIN или LC_UNIXTHREAD) и многое другое.

Шаг 3: Изучаем сегменты и секции

В Mach-O, как и в PE, код и данные организованы в сегменты, внутри которых находятся секции. Но есть важное отличие: в Mach-O сегменты определяются на уровне load commands, а секции — внутри структур сегментов.

Посмотреть сегменты и секции можно так:

otool -l /path/to/binary | grep -A 20 "LC_SEGMENT_64"

Типичные сегменты:

  • __TEXT — исполняемый код и константы. Содержит секции __text (машинный код), __stubs (заглушки для динамической линковки), __cstring (строковые литералы), __const (константы).
  • __DATA — изменяемые данные. Содержит __data (инициализированные данные), __bss (неинициализированные данные), __la_symbol_ptr (lazy symbol pointers для динамической линковки).
  • __DATA_CONST — константные данные, которые могут быть защищены от записи (аналог .rdata в PE).
  • __LINKEDIT — метаданные линковщика: таблица символов, строки, информация о перемещениях.

Для извлечения конкретной секции (например, машинного кода из __text):

otool -tvV /path/to/binary

Флаг -t выводит содержимое секции __text, -v — с дизассемблированием, -V — с символическим дизассемблированием (раскрывает вызовы функций).

Шаг 4: Анализируем символы

Таблица символов — один из самых ценных источников информации. Она показывает имена функций, глобальных переменных, импортируемых и экспортируемых символов.

Быстрый просмотр символов:

nm /path/to/binary

Для более информативного вывода с типами символов:

nm -m /path/to/binary

Типы символов в выводе nm:

  • (text) с префиксом _ — определённая функция (например, _main).
  • U — неопределённый символ (undefined), импортируется из внешней библиотеки.
  • T — символ в секции кода (аналог экспортируемой функции).
  • D — символ в секции данных.
  • S — символ в секции неинициализированных данных.

Если нужно посмотреть только неопределённые символы (то есть понять, откуда бинарник тянет внешние функции):

nm -u /path/to/binary

Это аналог просмотра Import Table в PE. Вы увидите все функции, которые бинарник импортирует из системных библиотек и других .dylib.

Для просмотра динамически экспортируемых символов (если это библиотека):

nm -g /path/to/library.dylib

Шаг 5: Смотрим зависимости

Каждый Mach-O файл, который собран с динамическими библиотеками, содержит load commands типа LC_LOAD_DYLIB — список всех .dylib, от которых он зависит.

Просмотр зависимостей:

otool -L /path/to/binary

Вывод покажет пути к библиотекам, их версии и совместимость. Это критически важно при отладке: если библиотека отсутствует или версия не совпадает, приложение не запустится.

Обратите внимание на особые пути:

  • /usr/lib/libSystem.B.dylib — системная библиотека (libc, libpthread и т.д.). Всегда присутствует.
  • @rpath — относительный путь, разрешается во время загрузки. Часто используется в приложениях с внутренними фреймворками.
  • @executable_path — путь относительно исполняемого файла.
  • @loader_path — путь относительно загружающего модуля.

Шаг 6: Точка входа и поток выполнения

В PE точка входа указывается в заголовке (AddressOfEntryPoint). В Mach-O это делается через load command LC_MAIN (для современных исполняемых файлов) или LC_UNIXTHREAD (для старых).

Найдите в выводе otool -l:

LC_MAIN
  entryoff 1234

Это смещение от начала файла до точки входа в _main. Для дизассемблирования с учётом точки входа:

otool -tvV -p _main /path/to/binary

Флаг -p указывает имя функции, и otool дизассемблирует только её.

Шаг 7: Дизассемблирование и анализ кода

Когда нужно понять логику работы функции, одного otool может быть недостаточно. Вот варианты от простого к сложному:

  1. otool — быстрый просмотр дизассемблера конкретной функции. Подходит для первичного анализа.
  2. objdump — более детальный вывод, поддержка разных архитектур. objdump -d -M intel /path/to/binary для синтаксиса Intel.
  3. Hopper — графический интерфейс, декомпиляция в псевдокод, удобная навигация по функциям. Если вы регулярно работаете с бинарниками, это лучший выбор по соотношению цена/качество.
  4. radare2 — бесплатная альтернатива Hopper с мощным скриптовым интерфейсом. Кривая обучения выше, но возможности шире.
  5. IDA Pro — индустриальный стандарт. Если бюджет позволяет и задачи серьёзные, это топ.

Сценарии выбора инструмента

В зависимости от вашей задачи:

  • Нужно быстро понять, что за файл и откуда импорты: file + otool -L + nm -u. Пяти минут достаточно.
  • Ищете конкретную строку или константу в бинарнике: strings /path/to/binary | grep "keyword".
  • Анализируете вредоносное ПО: начните с otool -l для понимания структуры, затем strings, затем дизассемблирование ключевых функций в Hopper или radare2.
  • Отлаживаете краш в сторонней библиотеке: otool -tvV для дизассемблирования, nm -m для поиска символов, dtruss или dtrace для трассировки системных вызовов.
  • Проверяете безопасность бинарника: смотрите на наличие PIE (Position Independent Executable), stack canaries, ARC и т.д. через otool -l и специализированные скрипты.

Частые ошибки при работе с Mach-O

Путаница между 32-bit и 64-bit. Если вы используете otool на 64-bit файле и видите неожиданные результаты, проверьте, не смотрите ли вы на 32-bit часть fat binary. Используйте lipo -info для проверки.

  • Забывают про fat binary. Универсальный бинарник содержит несколько архитектур. otool по умолчанию покажет первую. Используйте otool -arch arm64 для выбора конкретной.
  • Не учитывают ASLR и PIE. Адреса в дизассемблере могут отличаться от реальных адресов в памяти во время выполнения. Это нормально — работайте со смещениями, а не с абсолютными адресами.
  • Пытаются редактировать бинарник напрямую без понимания load commands. Это почти гарантированно сломает файл. Если нужно модифицировать бинарник, используйте install_name_tool для изменения путей к библиотекам или специализированные инструменты для патчинга.
  • Игнорируют code signing. macOS проверяет подпись бинарника. Если вы измените файл, его нужно переподписать (codesign -s - /path/to/binary для ad-hoc подписи), иначе он не запустится.
  • Не проверяют минимальную версию macOS. Load command LC_BUILD_VERSION или LC_VERSION_MIN_MACOSX указывает, с какой версии macOS работает бинарник. Это важно при отладке на разных системах.

Практический пример: быстрый анализ подозрительного файла

Допустим, вы скачали файл и хотите понять, не вредоносный ли он. Вот типичная последовательность действий:

  1. file suspicious_binary — определяем тип и архитектуру.
  2. codesign -dvv suspicious_binary — проверяем подпись. Если её нет или она невалидная — это красный флаг.
  3. otool -L suspicious_binary — смотрим, какие библиотеки подключает. Подозрительно, если тянет нестандартные пути.
  4. strings suspicious_binary | grep -i "http" — ищем URL-адреса в бинарнике.
  5. nm -m suspicious_binary — смотрим символы. Если нет символов (stripped), но при этом много неопределённых импортов — ещё один красный флаг.
  6. otool -tvV -p _main suspicious_binary — дизассемблируем функцию _main для понимания логики.

Как лучше организовать работу

Если вы регулярно работаете с Mach-O файлами, вот несколько советов из практики:

  • Создайте псевдонимы в терминале. Например: alias mh="otool -h", alias ml="otool -l", alias ms="nm -m". Это сэкономит массу времени.
  • Используйте otool ... | less вместо вывода в терминал. Так удобнее искать и навигироваться.
  • Сохраняйте вывод в файл для последующего анализа: otool -l binary > /tmp/analysis.txt.
  • Для сложного анализа используйте radare2 в режиме визуального графа. Команда r2 -q -c 'aaa; agf @ main' binary покажет граф функции _main.
  • Документируйте находки. При реверс-инжиниринге легко потерять нить. Записывайте смещения, имена функций, гипотезы.

Итог

Анализ Mach-O файлов на macOS — это навык, который нарабатывается практикой. Базовый набор инструментов (file, otool, nm, strings) уже встроен в систему и покрывает 80% задач. Для глубокого анализа стоит освоить Hopper или radare2.

Главное — начинайте с простого: определите тип файла, посмотрите зависимости и символы, затем углубляйтесь в дизассемблер по мере необходимости. Если вы уже работали с PE-файлами, то переход на Mach-O будет достаточно плавным — принципы структуры очень похожи, различаются только имена полей и детали формата.

Начните прямо сейчас: откройте терминал, выполните otool -L /bin/ls и посмотрите, как устроена простейшая системная утилита. Это лучший способ закрепить знания на практике.

Оцените статью
PEFile — Безопасность и технологии простым языком