Если вы столкнулись с файлом в формате Mach-O на macOS — будь то исполняемый файл, динамическая библиотека или объектный файл — и вам нужно понять, что внутри, вы не одиноки. Разработчики, исследователи безопасности и системные администраторы регулярно работают с этим форматом. В отличие от PE-файлов (Portable Executable), которые используются в Windows, Mach-O — это формат, принятый в macOS и iOS. Но принципы анализа во многом схожи: заголовок, секции, символы, импорты/экспорты. Разберёмся, как к этому подступиться.
- Что такое Mach-O и зачем его разбирать
- Инструменты, которые понадобятся
- Шаг 1: Определяем, что за файл перед нами
- Шаг 2: Смотрим заголовок Mach-O
- Шаг 3: Изучаем сегменты и секции
- Шаг 4: Анализируем символы
- Шаг 5: Смотрим зависимости
- Шаг 6: Точка входа и поток выполнения
- Шаг 7: Дизассемблирование и анализ кода
- Сценарии выбора инструмента
- Частые ошибки при работе с Mach-O
- Практический пример: быстрый анализ подозрительного файла
- Как лучше организовать работу
- Итог
Что такое 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 может быть недостаточно. Вот варианты от простого к сложному:
- otool — быстрый просмотр дизассемблера конкретной функции. Подходит для первичного анализа.
- objdump — более детальный вывод, поддержка разных архитектур.
objdump -d -M intel /path/to/binaryдля синтаксиса Intel. - Hopper — графический интерфейс, декомпиляция в псевдокод, удобная навигация по функциям. Если вы регулярно работаете с бинарниками, это лучший выбор по соотношению цена/качество.
- radare2 — бесплатная альтернатива Hopper с мощным скриптовым интерфейсом. Кривая обучения выше, но возможности шире.
- 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 работает бинарник. Это важно при отладке на разных системах.
Практический пример: быстрый анализ подозрительного файла
Допустим, вы скачали файл и хотите понять, не вредоносный ли он. Вот типичная последовательность действий:
file suspicious_binary— определяем тип и архитектуру.codesign -dvv suspicious_binary— проверяем подпись. Если её нет или она невалидная — это красный флаг.otool -L suspicious_binary— смотрим, какие библиотеки подключает. Подозрительно, если тянет нестандартные пути.strings suspicious_binary | grep -i "http"— ищем URL-адреса в бинарнике.nm -m suspicious_binary— смотрим символы. Если нет символов (stripped), но при этом много неопределённых импортов — ещё один красный флаг.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 и посмотрите, как устроена простейшая системная утилита. Это лучший способ закрепить знания на практике.
