Внутри двоичных форматов файлов: заголовки PNG, ZIP и Mach-O
У каждого формата файлов, которым вы пользуетесь ежедневно, изображения, архива, исполняемого файла, есть структура, определённая вплоть до байта, и почти ничего из этой структуры не видно через инструменты, которыми вы обычно открываете такие файлы. Preview показывает картинку. Archive Utility показывает извлечённые файлы. Ни то, ни другое не показывает байты, сделавшие эти результаты возможными. Вот как на самом деле выглядят три распространённых формата изнутри и как посмотреть на них самому.
PNG: последовательность помеченных чанков
Каждый файл PNG начинается с одних и тех же восьми байтов: 89 50 4E 47 0D 0A 1A 0A. Это не данные изображения, это сигнатура, намеренно выбранная так, чтобы включать байт, который становится непригодным, если файл случайно передан как текст (0x0A и 0x0D являются символами перевода строки, поэтому сломанная передача в текстовом режиме портит их так, что это легко обнаружить).
После сигнатуры всё в PNG представляет собой чанк, и каждый чанк следует одной и той же четырёхчастной раскладке:
- Длина (4 байта): сколько байтов данных следует далее, не считая типа или CRC.
- Тип (4 байта, всегда ASCII): четырёхбуквенный код, например
IHDR(заголовок изображения, всегда первый),IDAT(сжатые данные пикселей, их может быть несколько) илиIEND(отмечает конец файла). - Данные: то, что задаёт этот тип чанка. Данные
IHDRсодержат ширину, высоту, глубину бита и тип цвета, всегда в одной и той же фиксированной раскладке. - CRC (4 байта): контрольная сумма типа и данных, чтобы декодер мог обнаружить повреждённый чанк, а не тихо прочитать его неверно.
Вот и весь формат. Декодер PNG просто идёт от чанка к чанку, читая длину, чтобы знать, сколько пропустить, пока не наткнётся на IEND.
ZIP: локальные заголовки, затем каталог в конце
ZIP-архив хранит каждый файл с его собственным локальным заголовком файла, помеченным сигнатурой 50 4B 03 04 (PK в начале является отсылкой к Филу Кацу, создателю формата). Этот заголовок включает имя файла, метод сжатия, а также сжатый и несжатый размеры, за которыми сразу следуют сжатые данные файла.
Что сбивает людей с толку: эти размеры и контрольные суммы для каждого файла в локальном заголовке иногда равны нулю, а реальные значения хранятся позже в «дескрипторе данных», потому что ZIP был спроектирован для поддержки потоковой записи, где итоговый размер неизвестен, пока данные уже не записаны. Точный список того, что на самом деле есть в архиве, живёт в центральном каталоге в самом конце файла, и это же причина, по которой некоторые повреждённые или обрезанные ZIP-файлы могут выглядеть так, будто в них есть содержимое, когда вы сканируете их с начала, но не открываются, потому что инструмент, которым вы пользуетесь, сначала читает центральный каталог, а он отсутствует или повреждён.
Mach-O: магическое число, затем команды загрузки
Mach-O представляет собой формат исполняемых файлов, который macOS использует для скомпилированных двоичных файлов. Он открывается магическим числом, которое сообщает загрузчику сразу две вещи: 32-битный он или 64-битный, и какой порядок байтов использует остальная часть файла. 0xFEEDFACE означает 32 бита, 0xFEEDFACF означает 64 бита, а если вы видите байты в обратном порядке (0xCEFAEDFE или 0xCFFAEDFE), это значит, что файл был записан в порядке байтов, противоположном тому, что у читающей его машины.
После магического числа идёт фиксированный заголовок (тип процессора, тип файла и число команд загрузки), за которым следует ровно столько команд загрузки подряд. Через команды загрузки файл Mach-O описывает всё, что нужно загрузчику: какие сегменты отобразить в память, какие динамические библиотеки связать, где точка входа. Идея похожа на чанки PNG: фиксированный заголовок, затем последовательность самоописывающих записей, каждая из которых говорит читателю, сколько пропустить, чтобы дойти до следующей.
Посмотреть на это самому
Ничего из этого не требует писать парсер. Откройте любой файл PNG, ZIP или Mach-O в Hexter, и вкладка «Анализ» распознает формат автоматически, пройдёт реальную структуру чанков или команд загрузки и покажет её деревом рядом с сырым hex, так что вы можете щёлкнуть по полю и увидеть точно, из каких байтов оно взялось. По умолчанию это только для чтения, так что делать это на настоящем файле, двоичном файле приложения, скриншоте, скачанном архиве, действительно безопасно, без всякого риска его изменить.
Если вы хотите проверить сказанное выше вручную: откройте PNG и убедитесь, что первые восемь байтов совпадают с сигнатурой, затем посмотрите на байты с 8 по 11 (длина первого чанка) и байты с 12 по 15 (его тип, который должен читаться как IHDR). Это достаточно небольшое упражнение, чтобы сделать его за пару минут, и это по-настоящему полезный способ понять формат файла, которым вы пользуетесь всю жизнь, но на который никогда толком не смотрели.