Hexter

Усередині двійкових форматів файлів: заголовки PNG, ZIP і Mach-O з поясненнями

Кожен формат файлу, який ви використовуєте щодня, зображення, архів, виконуваний файл, має структуру, визначену аж до байта, і майже нічого з цієї структури не видно через інструменти, якими ви зазвичай відкриваєте ці файли. Preview показує вам картинку. Archive Utility показує вам розпаковані файли. Жоден не показує байтів, які зробили ці результати можливими. Ось як три поширені формати насправді виглядають зсередини і як подивитися на них самостійно.

PNG: послідовність позначених фрагментів

Кожен файл PNG починається з тих самих восьми байтів: 89 50 4E 47 0D 0A 1A 0A. Це не дані зображення, це сигнатура, навмисно обрана так, щоб містити байт, який стає непридатним, якщо файл випадково передано як текст (0x0A і 0x0D це символи нового рядка, тож зіпсована передача в текстовому режимі пошкоджує їх у спосіб, який легко виявити).

Після сигнатури все у PNG це фрагмент, і кожен фрагмент має ту саму чотиричастинну розкладку:

Діаграма структури фрагмента PNG: 4-байтове поле довжини, 4-байтове поле типу на кшталт IHDR чи IDAT, самі дані фрагмента і 4-байтова контрольна сума CRC наприкінці.
Кожен фрагмент PNG, від першого (IHDR) до останнього (IEND), має цю саму розкладку з чотирьох полів.
  • Довжина (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, і вкладка «Аналіз» розпізнає формат автоматично, проходячи справжню структуру фрагментів чи команд завантаження й показуючи її як дерево поруч із сирим шістнадцятковим кодом, тож ви можете клацнути поле й побачити точно, з яких байтів воно походить. За замовчуванням він лише для читання, тож це справді безпечно робити на справжньому файлі, двійковому файлі застосунку, знімку екрана, завантаженому архіві, без жодного ризику його змінити.

Якщо ж ви хочете перевірити наведене вище вручну: відкрийте PNG і переконайтеся, що перші вісім байтів збігаються із сигнатурою, потім подивіться на байти з 8 по 11 (довжина першого фрагмента) і байти з 12 по 15 (його тип, який має читатися IHDR). Це достатньо невелика вправа, щоб зробити за кілька хвилин, і це справді корисний спосіб зрозуміти формат файлу, яким ви користувалися все життя, але ніколи насправді на нього не дивилися.