ImHex: гайд по реверс-инженерингу формата файлов на примере сохранений FEZ

Автор поста, разработчик ImHex, бесплатного open source hex-редактора для всех операционных систем (есть и браузерная версия, ImHex Web). Ему часто задают один и тот же вопрос: как реверс-инженерить неизвестный формат файла. Раньше единственным ответом было: «Посмотри декомпилированный код программы, которая читает и записывает эти файлы, и разберись, идя в обратном направлении». Пост, развёрнутый ответ на этот вопрос на практическом примере.
В качестве примера берётся файл сохранения игры FEZ, выпущенной в 2012 году. Игра скачивается в Steam (последний полный релиз вышел 2 декабря 2016 года), после недолгой игры создаётся автосохранение, которое находится на диске (в Linux, по пути /home/werwolv/.local/share/FEZ/SaveSlot2). При открытии файла в ImHex видно, что данные не сжаты и не зашифрованы, но не имеют сигнатуры (magic bytes) в начале, формат ImHex не может определить автоматически.
Дальше игра декомпилируется: FEZ написана на C#, который относительно легко восстанавливается в читаемый код такими инструментами, как JetBrains Rider. Среди файлов игры выделяются библиотеки FEZ.exe, FezEngine.dll, Common.dll, ContentSerialization.dll и EasyStorage.dll. Внутри класса PCSaveDevice находится код, который формирует имя файла как «SaveSlot» плюс номер слота, а также функция Save, собирающая байтовый буфер через BinaryWriter и записывающая его на диск.
На основе этого кода в языке шаблонов ImHex, Pattern Language, описывается формат файла. Функция Save() всегда пишет ровно 0xA000 байт: если данных меньше, файл дополняется нулями, если больше, выбрасывается исключение InvalidOperationException; это ограничение задаётся атрибутом [[fixed_size(0xA000)]]. Первое поле файла, 64-битная метка времени в формате Windows FILETIME (число 100-наносекундных интервалов, отсчитываемых с 1601 года); для неё в стандартной библиотеке ImHex уже есть готовый тип. Второе поле, номер версии формата сохранения: игра проверяет, что он равен 6, и выбрасывает исключение, если это не так; такую проверку в Pattern Language можно задать как условие прямо внутри описания типа.
Дальше по шагам разбираются более сложные конструкции: nullable-объекты (сначала пишется bool, существует ли значение, и только затем само значение), строки (сначала длина в формате 7BitEncodedInt, затем сами байты), списки (32-битный счётчик элементов и сами элементы) и перечисления (enum), которые в Pattern Language можно почти один в один скопировать из C#-кода. В конце разбор доходит до вложенной структуры LevelSaveData, а дальнейшую часть формата читателю предлагается доразобрать самостоятельно, с опорой на уже готовый шаблон автора (ссылка «My Pattern» в оригинале).
В заключение формулируется общий алгоритм реверс-инжиниринга бинарного формата, применимый не только к играм: сначала проверить, не является ли формат уже известным (через встроенный в ImHex определитель сигнатур или инструмент binwalk); если нет, найти в программе код, который читает или пишет файл (по исходникам или через декомпиляцию: Rider, для .NET, Ghidra, IDA или Binary Ninja, для нативного кода, Recaf, для JVM); разобрать, из каких базовых блоков состоит формат (целые числа, булевы значения, строки, вложенные структуры); и задокументировать находки в виде Pattern-файла, который заодно проверяет правильность разбора. Отдельно отмечается: не каждую программу так же легко декомпилировать и проанализировать, как FEZ, но общий рабочий процесс остаётся тем же.
Ключевые факты
- Разработчик ImHex на конкретном примере показывает, как реверс-инженерить неизвестный бинарный формат файла, материал построен на разборе сохранений игры FEZ (2012).
- Игра декомпилируется через JetBrains Rider (написана на C#); в коде находится функция Save, которая всегда пишет данные ровно в 0xA000 байт, дополняя нулями более короткие данные.
- Для декодирования пишется шаблон на Pattern Language ImHex: 64-битная метка времени Windows FILETIME (отсчёт с 1601 года), проверка версии формата (должна быть равна 6), nullable-объекты, строки в формате 7BitEncodedInt, списки и перечисления (enum).
- Общий алгоритм: сначала проверить, не является ли формат уже известным (ImHex magic detection, binwalk); затем найти код чтения/записи через декомпиляцию (Rider для .NET, Ghidra/IDA/Binary Ninja для нативного кода, Recaf для JVM); разобрать базовые типы данных; задокументировать в виде Pattern-файла.
- ImHex, бесплатный open source hex-редактор для всех ОС, доступен и в браузере (ImHex Web); пользователям версии v1.38.1 и ниже при проблемах с описанными функциями рекомендуют перейти на Nightly-сборку.
Почему это важно
Материал ценен не идеей (реверс-инжиниринг форматов, старая практика), а тем, что показывает её пошагово и от начала до конца на реальном, воспроизводимом примере: от декомпиляции игры до готового шаблона, который расшифровывает каждый байт файла. Обычно такие разборы дают только общий совет вроде «покопайся в декомпилированном коде», здесь вместо совета дан полный путь с конкретными инструментами и кодом. Материал набрал 151 голос и 31 комментарий на Hacker News, интерес аудитории к теме высокий.
Кому это важно
Тем, кто реверс-инженерит бинарные форматы файлов профессионально или как хобби: специалистам по анализу вредоносного ПО и цифровой криминалистике, разработчикам инструментов для сохранения старых игр (game preservation) и модификаций, а также всем, кто пользуется ImHex и хочет освоить его язык шаблонов Pattern Language.
Как это применить
Алгоритм из поста применим к любому неизвестному бинарному формату: сначала проверить, не опознаётся ли он уже существующими инструментами (встроенный в ImHex определитель сигнатур, утилита binwalk); если нет, найти в программе код чтения/записи файла через исходники или декомпиляцию (JetBrains Rider для .NET-программ, Ghidra, IDA или Binary Ninja для нативного кода, Recaf для программ на JVM); разобрать формат на базовые блоки (целые числа, булевы значения, строки, списки, вложенные структуры); и описать находки в виде Pattern-файла ImHex, который заодно служит проверкой корректности разбора.
Можно ли доверять
Пост написан разработчиком самого ImHex (имя в тексте не указано, только домен werwolv.net) и подробно, с кодом и скриншотами, разбирает реальный процесс на конкретном файле, что делает его технически проверяемым. Часть скриншотов и шагов исходного материала не воспроизводится текстом дословно, а итоговый Pattern-файл дан только ссылкой, без содержимого.
Риски и подводные камни
Автор прямо оговаривает: не каждую программу так же легко декомпилировать и анализировать, как FEZ, написанную на C#, для программ на других языках или с защитой от реверс-инжиниринга (обфускация, антиотладка) путь будет сложнее и потребует других инструментов (Ghidra, IDA, Binary Ninja). Отдельно: пользователям ImHex v1.38.1 и ниже при проблемах с описанными функциями рекомендуют перейти на Nightly-сборку.
«Посмотри декомпилированный код программы, которая читает и записывает эти файлы, и разберись, идя в обратном направлении.»
— автор поста, разработчик ImHex