Реверс-инжиниринг бинарного формата на примере файлов Korg .SNG



Мы живем в удивительное время. Вокруг нас изобилие техники: телефоны, компьютеры, умные часы и прочие гаджеты. Каждый день производители выпускают на рынок все новые и новые устройства. Большинству их них предначертана короткая и яркая (или не очень) жизнь: мощная маркетинговая компания в момент выпуска, 1-2 года полноценной поддержки производителем, а затем медленное забвение. Простые устройства могут годами работать и после окончания срока официальной поддержки. С «умными» девайсами все сложнее. Хорошо если гаджет хотя бы продолжит работу после отключения серверов/сервисов производителя. И повезет, если очередное обновление ОС, драйверов или другого ПО не прибьет совместимость.

К сожалению все чаще события развиваются по пессимистичному сценарию. И спустя 5-10 лет после покупки, у нас на руках оказываются технически исправные устройства, которыми тем не менее нельзя пользоваться из-за отсутствия софтовой поддержки. Конечно, неработающий гаджет — это неприятно. Но куда неприятнее, если имеются пользовательские данные в несовместимом ни с чем формате. Эти данные можно считать потерянными если устройство перестает функционировать. В моем случае самое страшное еще не случилось, но тревожные звоночки уже вовсю звенят.

Итак, есть небезызвестная фирма Korg, которая выпускает весьма качественное музыкальное оборудование. В 2010м году я купил синтезатор этой фирмы для занятия музыкой в качестве хобби. Korg microstation — модель достаточно продвинутая. Кроме всего прочего, имеет на борту секвенсер для записи своих трэков и может писать данные на карту памяти в проприетарном формате SNG. Есть возможность экспорта в распространенный формат миди, но при этом теряются почти все метаданные: информация о наложенных эффектах и фильтрах, различные настройки виртуальных инструментов и т.д. Основная же проблема лично для меня — в скорости перехода к записи музыкальных идей. Муза — создание капризное, и я чаще всего наталкиваюсь на интересную идею просто импровизируя или наигрывая что-нибудь незамысловатое. Чем быстрее я нажму кнопку записи без блуждания по меню — тем вероятнее смогу повторить и записать интересный фрагмент, который в будущем может стать частью полноценного произведения. Конечно, этот подход неидеален, но мы же говорим про хобби. Так или иначе, за почти десять лет, у меня скопилось около тысячи музыкальных эскизов и зарисовок в формате SNG.

Звоночек же прозвенел в виде череды глюков синтезатора, потребовавших перепрошивки устройства. И я задумался о переводе всех своих скопившихся данных в формат Midi, тем более что это позволит гораздо проще их хранить, систематизировать и редактировать. Поиск конвертера в гугле ничего не дал. Есть множество запросов на всевозможных форумах, история вопроса тянется уже лет 20 если не больше. Все что я нашел — чья-то древняя виндовая утилита, естественно несовместимая с моими файлами.

И тогда я решил попробовать посмотреть что же представляет из себя этот формат SNG? Может где-то внутри там спокойно лежат обычные миди данные, которые можно без особых усилий вытащить и сохранить?

Попытка решить задачу «в лоб»


Итак, из инструкции к синтезатору можно узнать, что формат SNG — это контейнер, в котором хранятся так называемые «песни». Каждая песня содержит 16 трэков секвенсера с музыкальными данными, а также настройки звуков и эффектов. При экспорте в формат Midi через меню синтезатора, каждая «песня» экспортируется в отельный файл .MID, а все настройки звуков и эффектов теряются. Т.к. свои идеи я наигрываю в самом простом виде и без эффектов, то проблемой является именно большое количество SNG файлов и неудобство процесса ручной конвертации. Посмотрим, нельзя ли этот процесс ускорить или автоматизировать.

Для начала вспомним что такое миди-данные. Упрощенно говоря, это поток музыкальных событий: нажатие и отпускание клавиши, нажатие и отпускание педали сустейна, изменение темпа, патча (виртуального инструмента) и других параметров. Каждое событие содержит временную дельту с момента предыдущего события и данные, например, интенсивность и высоту ноты. Формат midi файла очень простой: кроме заголовков и самих данных там практически ничего нет.

Розовый — событие Note On. Бледно-желтый — дельта времени. Голубой — событие Note Off.

Попробуем поискать наши миди данные в файле SNG. Для этого запишем на синтезаторе последовательность из несколько нот и экспортируем в оба формата. Т.к. мы не знаем где конкретно в бинарных файлах находятся музыкальные данные, то попробуем повторить процесс с разными последовательностями нот.

Здесь и дальше я пользуюсь Hex-редактором Synalyze It! Его возможности в дальнейшем нам очень пригодятся. Пока же просто воспользуемся функцией сравнения двоичных файлов.

По сути, совпало только название «песни». Сравнив два SNG файла с разными последовательностями нот можно примерно предположить где именно хранятся музыкальные данные, но нам это пока ничем не поможет — формат данных отличается. Сам файл в десятки раз больше Midi файла и судя по всему содержит много дополнительной информации. Можно видеть сигнатуру KORG в первых четырех байтах и некоторые другие строки, в том числе название «песни» и имена патчей (тембров), назначенных на трэки.

Разбор структуры блоков данных


На этом можно было бы закончить, если бы, к счастью, не появились инструменты, позволяющие относительно легко проанализировать и понять структуру данных хранящихся в двоичном виде. В этом нам поможет все та же программа Synalaze It!, которая позволяет создавать и применять «грамматику» для анализа бинарных файлов.

Грамматика — это иерархическая описательная структура, позволяющая представить двоичные данные в понятном человеку виде. Программа позволяет загрузить грамматики для некоторых форматов. Например, для того же midi:

Для формата SNG готовой грамматики ожидаемо не нашлось. Что ж, посмотрим, что мы сможем извлечь из файла своими силами.

Начнем с заголовка. Как правило эта часть содержит сигнатуру файла, информацию о версии, размеры и смещения блоков данных. Сравнив несколько различных SNG файлов, найдем неизменные части и обратим более пристальное внимание на те что меняются

Создадим структуру заголовка в редакторе грамматики. Первые 4 байта — очевидно сигнатура файла. Предположим, что следующие 4 байта относятся к версии. Следующие несколько десятков байт не меняются и не содержат ничего интересного — создадим для них binaryData соответствующего размера. А вот дальше начинается интересное. Можно заметить некоторые закономерности в поведении байтов по смещениям 0x13 и 0x1b. Второй похоже соответсвует количеству «песен» в нашем файле. А первый тоже растет с ростом количества данных в заголовке — похоже это размер, только отсчет идет не от начала файла, а со следующего байта 0x14. На данном этапе мы можем только догадываться о типе числовых данных. Предположим, что размер имеет тип UInt32, т.е. занимает 4 байта. Добавим их к нашей структуре. Теперь мы можем задать размер структуры заголовка (size + 20).

Грамматика с добавленной структурой заголовка файла


Посмотрим что же идет дальше. Если внимательно присмотреться, то можно заметить что по всему файлу разбросаны трехбуквенные аббревиатуры: SNG1, SDK1, SGS1 и так далее. Эти символы встречаются во всех SNG файлах, поэтому можно предположить, что это сигнатуры неких блоков. К тому же наш заголовок очень удачно закончился как раз перед одной из таких сигнатур. Сравним как ведут себя следующие за ней 4 байта в файлах разного размера. Видно, что значения растут с увеличением количества данных.

Еще немного экспериментов, анализа и расчетов и начинает вырисовываться следующая картина:



Альтернативный вид диаграммы


Таким образом наш файл состоит из достаточно простой иерархии блоков. Есть родительские блоки, которые могут содержать несколько дочерних блоков. Есть блоки-листья (в терминологии бинарных деревьев), которые не содержат других блоков.

Дальше начинается магия. С помощью всего нескольких структур грамматики мы можем полностью автоматически распарсить блочную структуру файла

Итак, создадим структуру-шаблон DataChunk со следующими полями (в квадратных скобочках указан размер):

id: String [4]
size: Int [4]
hierarchy: Int [4]
data: structure

Теперь создадим структуру parentChunk, которая наследует DataChunk. В свойстве hierarchy укажем Fixed Value 0x400 — это признак родительского блока. Обязательно отметить чекбокс Must match

Аналогично, создадим childChunk. Hierarchy в данном случае будет иметь два значения: 0x240100 и 0x100

Добавим ссылки на структуры parentChunk и childChunk в структуру data parentChunk — таким образом мы создаем рекурсию.

Наконец, добавляем ссылку на структуру parentChunk в главную ноду.

Порядок элементов в структуре data parentChunk должен быть Variable, также требуется задать минимальное и максимальное количество дочерних элементов этой структуры: 0 и Unlimited соотвественно.

Применим изменения, и вуаля — наш файл красиво распарсен на основные блоки

Про сами данные нам по-прежнему ничего не известно, зато теперь мы можем гораздо легче ориентироваться в файле и сосредоточиться на поиске нужной нам информации.

Разбор блока, содержащего «оглавление» файла


Для тренировки, попробуем разобрать какой-нибудь простой блок, например, SDK1. Судя по всему, он содержит что-то вроде оглавления — список песен и, вероятно, некие смещения/размеры.

Создадим структуру sdk1Chunk, наследующую childChunk. Отредактируем поле ID, указав сигнатуру нашего блока в поле Fixed Values. Не забываем про чекбокс Must match. В данных блока можно наблюдать достаточно очевидный повторяющийся паттерн: название «песни» и пока неизвестные данные. Заметим, что размер повторяющихся фрагментов 64 байта. Также, сравнив версии файлов с разным количеством «песен», можно определить что в первых четырех байтах хранится их количество. Путем несложных расчетов и сделав несколько допущений, получим следующий вариант структуры в грамматике:

Здесь я создал дочернюю структуру songInfo размером 64 байта и указал возможность повторения numSongs раз. Вот так выглядит результат применения грамматики:

Дальнейший анализ файла остается делом техники. Я менял на синтезаторе общие настройки «песни» и параметры отдельных трэков. Сравнивая версии файлов с различными изменениями, можно совершенствовать и уточнять грамматику. После достаточно большого количества таких итераций, нераспознанных фрагментов данных в файле почти не остается. Я немного увлекся процессом и разобрал почти все секции файла, хотя для исходной задачи это не требовалось.

Небольшая часть грамматики SNG файла после 8 часов разбора


Детали этого процесса я упущу — в дальнейшем мы сосредоточимся на анализе непосредственно музыкальных данных.

Но об этом — в следующей части. Там же мы столкнемся с интересной задачей по конвертации данных (вполне подходит для собеседований), попробуем решить ее с помощью небольшого скрипта и услышим довольно необычный результат тестовой конвертации.

Предварительные итоги


Потребность в реверс-инжиниринге двоичных файлов может возникнуть неожиданно. Например, для анализа прошивки устройств, конвертации из редких форматов данных, анализа цифровых угроз или даже банальной модификации сохранений игр. Современные инструменты позволяют решать эти задачи быстро и эффективно. Примерно 10 лет назад, я занимался исследованием прошивок ноутбуков и этот процесс мог занять несколько недель. Тогда требовалось вручную писать скрипты для анализа блоков данных и размечать структуры. С новым частично автоматизированным подходом, я создал почти полную грамматику файла всего за пару дней.

Начать анализ двоичного файла можно с поиска строк — они могут дать первые зацепки и ускорить процесс анализа. Часто двоичные файлы состоят из блоков данных, которые организованы в иерархическую или линейную структуру. Если разобраться с этой структурой, то дальнейший анализ будет проходить гораздо проще. Заголовок файла может дать подсказки по смещениями/размерам блоков данных. На первых этапах имеет смысл сосредоточиться на описании очевидных структур и блоков. Задачу анализа сильно упрощает возможность создавать новые версии файлов с разными настройками, параметрами, данными. Существует ряд сложностей связанных с неизвестными типами данных и порядком байт в их двоичном представлении (Endianness). Эти вопросы мы затронем в следующей части.

Рекомендуемая литература


Andreas Pehnack. How to Approach
 Binary File Format Analysis
Поддержать автора
Поделиться публикацией

Комментарии 17

    0
    Планируется ли выложить утилиту/скрипт конвертер?
      +3
      Да, в следующей статье выложу код скрипта. Возможно код консольной утилиты или отдельные его части тоже будут выложены. Проблема в том что это учебный проект и по сути прототип (хоть и решающий мои задачи), а не законченный инструмент — поэтому хвастаться качеством кода и элегантностью решений не получиться.
      +5
      Чукча не всегда кодер. Главное, что чукча — реверсер, а это похвально!

      С качеством кода поможет заливка на Github, где через Pull Requests вам помогут исправить ваш код.
        +2
        Интересный разбор. Всегда почему-то все эти музыкальные форматы казались некоей чёрной магией (несмотря на то, что неплохо разбираюсь, скажем, в исполняемых файлах), а тут прямо разбирается как по нотам.
        А чем обусловлен выбор инструмента? Почему бы не переиспользовать что-то более распространённое для этой цели, скажем тот же kaitai struct, там же сразу и парсер нагенерится.
          0
          Хотелось решить задачу с минимумом усилий и затратив наименьшее количество времени, особенно в сравнении с процессом ручного экспорта. Изначально я планировал вытащить только музыкальные данные и использовать встроенную скриптовую функциональность Synalaze It для конвертации их в midi. К сожалению этот подход сработал только частично и масштабировать его мне не удалось. Но тот код, который я успел написать, умещается на полтора экрана и выполняет 99% задуманного
            0
            Кстати, имеется конвертер из формата Synalyze It в формат kaitai struct
              0
              Похоже придется из любопытства им воспользоваться и посмотреть насколько эффективно данная задача будет решаться в kaitai struct. Многие его советуют
            0
            Реверс, это всегда интересно, спасибо!
              0
              есть такие специальные программы, общее название — DAW. Много их… Есть и бесплатные, и удобные…
              Поверьте, разбираться, хранить миди, аудио и прочие файлы в них гораздо проще.
              И, кстати, на чём вы хранили тысячи заготовок?
              Подход в целом немного странный. Если прокололи колесо — неужели нужно изучать сопромат резины?..
                0
                Причины я описал. Они в основном организационные. Использовать DAW — означает включить компьютер, запустить DAW, создать проект, добавить трек, выбрать нужный плагин, включить запись с клавиатуры. Для записи следующей идеи нужно опять добавить дорожку, выбрать плагин, нажать запись. На это уходит время и к тому моменту я уже обычно не могу повторить то что хотел записать. Если нет выделенной машины и отдельного органзиованного рабочего места, то времени уходит еще больше. Плюс я очень чувствителен к звучанию пианино и найти плагин, который меня устраивает я долгое время не мог (да и сейчас с этим есть нюансы)
                Я признаю что это спорный подход. Но я не профессиональный музыкант, думаю это простительно. На данный момент, я хочу перейти к более осознанному и организованному процессу, поэтому и занялся тем что описано в статье
                Заготовки хранились на карте памяти с периодическим бэкапом на ПК. По размеру они побольше чем миди, но все равно занимают совсем немного места. Опять же, подчеркну — что это только наброски. Когда я дозревал до полноценной записи, конечно я использовал DAW.
                0
                Спасибо. Было интересно почитать. Сам, для подобного использую Kaitai Struct (вот тут статья на Хабре: habr.com/ru/post/281595)
                  0
                  Спасибо. Как-то пропустил эту статью
                    0

                    А существуют для него редакторы+визуализаторы в виде интегрированной среды? Не в виде веб.

                      0
                      Не знаю. Для моих скромных потребностей достаточно web версии.
                    0

                    Хорошо всё-таки, что файл никак не упакован и не зашифрован, думаю, даже простейший xor сильно испортил жизнь

                      0

                      Интересный подход сразу шариться в готовых файлах и пытаться что-то оттуда извлечь.
                      Я бы декомпозировал. Записал бы файл из единственной ноты. Потом из другой ноты. Потом из ноты другой длительности — и т.д., и смотрел бы, что меняется в выходных байтиках.
                      Когда-то давно я примерно так написал PPD для печати из-под линукса на Ricoh Aficio 450 — перехватывал задания для печати из-под windows с разными параметрами-настройками входных лотков, дуплекса, степлера и т.д. — и делал аналогично под линуксом.
                      Не так важно, что именно записано в файле; важно, в чём внутреннее отличие от другого файла с заданным визуальным отличием.


                      Ну а редакторов с поддержкой темплейтов сейчас пруд пруди. Уже 10 лет назад попадался что-то вроде 010 editor, где тоже на встроенном с-подобном языке можно было внятно описать структуру файла

                        0
                        Я бы декомпозировал. Записал бы файл из единственной ноты. Потом из другой ноты. Потом из ноты другой длительности — и т.д., и смотрел бы, что меняется в выходных байтиках.


                        В продолжении я опишу этот подход. Проблема в том, что данный формат может содержать несколько фрагментов данных, т.е. несколько «песен». Каждая песня состоит из треков. Треки содержат такты. Во всей этой структуре также пришлось разобраться. Кроме непосредственно информации о нажатии нот в блоке данных хранятся метаданные, поэтому прямое сравнение ничего не давало. Даже два пустых трека без нот, но разной длительности будут значительно отличаться.

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое