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

Адаптированный проект для публичного использования, рабочий на Гите, компилируется в простой exe-файл. Можно скачать как exe-файл, если доверяете своему антивирусу.  Надеюсь, кому-то пригодится. Но чтобы начать пользоваться надо научиться писать XАML определения вложенных структур, по которым работает парсер. Ссылка на проект с исходниками в конце статьи.

Не будет никаких модных слов, только то, что нужно для работы.


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

Приложение в работе
Приложение в работе

На приведенном интерфейсе пользователя приложения можно выделить:

Внизу:

  • интерфейс для выбора файла описания структуры данных для парсинга (задание для парсера) по кнопке “LoadXaml”;

  • интерфейс для выбора файла данных, который будет анализироваться-визуализироваться по кнопке “ParseArr”.

Панель управления с

  • кнопками “LoadXaml” и “ParseArr”,

  • количеством загруженных объектов описания полей/структур (DeclCnt=46),

  • количеством считанных объектов значений полей/структур (ValueCnt=307).

Слева направо:

  • Окно логирования процессов загрузки описания/анализа данных;

  • Окно визуализации прочитанных данных-структур в виде дерева записей, выбор записи синхронизирован с выделением области в шестнадцатеричном представлении потока данных;

  • Окно визуализации бинарных данных, в котором выделение синхронизировано с выбором в окне визуализации данных.

На картинке показана предопределенная структура и предопределенный файл данных из состава проекта. Если скомпилировать, запустить проект и нажать на кнопку “ParseArr” должна воспроизвестись такая же картинка после очевидных манипуляций с деревом объектов.

 Задание для парсера

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

Как это не покажется кому-то странным, самым простым способом описания таких структур оказался XML, а точнее XAML. Идея в том, что мы создаем дерево объектов с описаниями структур, их полей и зависимостей от прочитанных данных. Последнее надо пояснить с примером, который вы сможете найти в проекте на Гите.

<local:StreamBitsFld bitLen="2" Name="StrId_CNT"/>
<local:FldDefArray Name="StrIds" SizeLink="StrId_CNT" ZeroBase="1">
    <local:NamedByFirst Name="StrId">
        <local:StreamBitsFld bitLen="10" Name="StrId"/>
        <local:StreamBitsFld bitLen="6" Name="Datid_CNT"/>
        <local:FldDefArray Name="Datids" SizeLink="Datid_CNT">
            <local:NamedByFirst Name="StrId" nameAdditionIndex="2">
                <local:StreamBitsFld bitLen="1" Name="Grub_REQUIRED"/>
                <local:StreamBitsFld bitLen="5" Name="RFU2"/>
                <local:StreamBitsFld bitLen="10" Name="Datid"/>
            </local:NamedByFirst>
        </local:FldDefArray>
    </local:NamedByFirst>
</local:FldDefArray>

Запись задает вполне интуитивный способ описания вложенных структур и даже массива структур размер которого определяется значением из поля внешней структуры. Разберем подробно:

Запись:

<local:StreamBitsFld bitLen="2" Name="StrId_CNT"/>

задает элемент типа local:StreamBitsFld который определяет битовое поле размером bitLen (от 1 до 16 бит), значение которого будет прочитано в переменную типа INT для отображения в виде конечного элемента в дереве считанных из бинарного потока значений. Значение будет подписано строкой, заданной в поле Name.

Элементы, которые содержат вложенные элементы:

<local:FldDefArray Name="StrIds" SizeLink="StrId_CNT" ZeroBase="1">

<local:NamedByFirst Name="StrId">

<local:FldDefArray Name="Datids" SizeLink="Datid_CNT">

Определяют вложенные структуры и даже массивы структур, например запись

<local:FldDefArray Name="StrIds" SizeLink="StrId_CNT" ZeroBase="1">

Определяет массив структур, состоящих из вложенных элементов этого XML узла. Размер массива задается значением прочитанным парсером из первого предыдущего поля с именем, заданным в поле SizeLink если идти от данного элемента вверх по дереву определений, то есть из элемента:

<local:StreamBitsFld bitLen="2" Name="StrId_CNT"/>

Обратите внимание мы не задаем размер массива, мы задаем откуда парсер будет читать размер массива во время работы. Таким образом размер массива определяется данными. Поле ZeroBase задает как интерпретировать значение «ноль» для длины массива. «1» означает что в массиве будет хотя бы один элемент.

При таком способе описания структуры данных для парсера, порядок разбора для парсера задается самым естественным с точки зрения человеческого восприятия, способом. Мы последовательно выписали все структуры, массивы поля, которые парсер соответственно последовательно будет читать из бинарного потока данных. С другой стороны, мы определяем структуру данных в виде дерева, и именно в таком виде это описание и загружается-сохраняется как задание для парсера в памяти. Собственно алгоритм работы парсера и заключается в том, чтобы в заданной последовательности применять объекты описания полей-структур (которые можно определить как сложные поля) к бинарному потоку данных в текущей позиции этого потока. Применяя объект описания поля, парсер создает объект значения поля и смещает текущую позицию в потоке в соответствии с размером прочитанного поля, любая прочитанная структура смещает текущую позицию в потоке на суммарный размер всех своих вложенных (дочерних) элементов. Вряд ли можно придумать более простой алгоритм для реализации парсера.

WPF визуализация в виде дерева

Я думаю, не надо объяснять, что результатом работы парсера является дерево объектов значений в памяти. Поскольку все классы используемых объектов наследуются от одного базового класса BaseBinaryFied они легко строятся в дерево вложенных объектов используя интерфейс, определенный в этом базовом классе.

Чтобы отобразить полученное дерево объектов в памяти в виде визуального дерева визуальных объектов на форме, достаточно передать корневой объект этого дерева в визуальный объект окна fieldsTree

            List<object> lst = new List<object>(1);
            lst.Add(rootElmnt);
            wnd.fieldsTree.ItemsSource = lst;

Хотя это конечно не совсем все, еще надо определить способ представления объектов для ветвей и листьев дерева через HierarchicalDataTemplate:

<HierarchicalDataTemplate DataType="{x:Type local:parentBinValue}"
                        ItemsSource="{Binding Path=Children}">
    <StackPanel Orientation="Horizontal" Background="LightSkyBlue" Margin="2" >
        <TextBlock Text="{Binding Path=Descript}" Padding="3,1, 1,1"/>
        <TextBlock Text="{Binding Path=startPos}" Padding="1,1, 10,1"/>
    </StackPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:BaseBinValue}"      >
    <StackPanel Orientation="Horizontal" Background="LightGreen"  Margin="1" >
        <TextBlock Text="{Binding Path=Descript}" Padding="10,1, 1,1"/>
        <TextBlock Text=": " Padding="1,1, 0,1"/>
        <TextBlock Text="{Binding Path=fldVal}" Padding="1,1, 10,1"/>
        <TextBlock Text="{Binding Path=BitPos }" Padding="1,1, 10,1"/>
    </StackPanel>
</HierarchicalDataTemplate>

Если, конечно, на Хабре еще остались те кому интересны такие практические задачи которые приходится решать в повседневной работе.

Ссылки

Проект парсера на Гите

ZIP-архивы с исходниками и с файлами примеров, скомпилированный исполняемый файл.

Комментарии (6)


  1. greg0r0
    30.11.2023 13:27
    +5

    А зачем писать свой костыль? Есть же тот же 010Editor с C-подобным описанием структур для парсинга. Ну или фиг с ним - он платный, тогда можно использовать ImHex и тоже для него написать DataProcessor.

    Если хочется все же свой фронт/свой пайплан - то можно натянуть kaitai как нужно.


    1. egr0proxy
      30.11.2023 13:27

      Почему бы и нет? Человек поделился конкретной реализацией - может быть она вписывалась в общую инфраструктуру какого-то проекта(C#, WPF). Мне лично интересна реализация на WPF. Если кто-то уже сделал нечто подобное - это не повод для того чтобы не попытаться сделать аналог на других технологиях и своими руками.

      Меня больше задевает некоторая категоричность высказываний автора типа "Если, конечно, на Хабре еще остались те кому интересны такие практические задачи которые приходится решать в повседневной работе". Автор статьи думает что он на хабре единственный программист-практик? К чему это?!


      1. rukhi7 Автор
        30.11.2023 13:27

        Автор статьи думает что он на хабре единственный программист-практик? К чему это?!

        Ну может быть автору где-то, прежде, очень не повезло с аудиторией, и у него сложилось такое впечатление. Может такое быть?

        И теперь автор очень хочет чтобы его переубедили!

        По поводу того что:

        А зачем писать свой костыль?

        я чесно говоря и не знал о таком разнообразии подобных инструментов, спасибо @greg0r0 за ссылки. Но я вот их смотрю и вижу что мне, все равно, сделать и работать со своей тулзой проще, чем разбираться с чужой, которая, не факт, что точно подойдет под мои задачи. Свою то тулзу я (и, теперь, любой разработчик) всегда допилить могу туда куда мне надо. Я уже не говорю о возможностях интеграции с проектом который использует результаты парсера, она же не для картинки делалась изначально, хотя... для картинки тоже :) , хотя картинка пока так себе, конечно.


        1. greg0r0
          30.11.2023 13:27

          Вопрос в возможностях - у указанных инструментов под капотом маленькие компиляторы таких правил с поддержкой разных фич (эти правила не "наголо" применяются на бинарные данные). Можно условные 120 рабочих часов допиливать свою тулзу, а можно за 10 изучить документацию и 110 уже работать непосредственно над проектом.

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

          > возможностях интеграции с проектом

          Ну так я и добавил упоминание kaitai для такого :) Зачем писать свой "транслятор" описания структур, если уже есть готовый и с широкими возможностями синтаксиса.

          Бтв, если нужно будет что-то именно бинарное детектировать в трафике - можно использовать yara, она для таких задач и предназначена.


          1. rukhi7 Автор
            30.11.2023 13:27
            +1

            Да я в общем со всем согласен, просто уточню по поводу статьи:

            Ну так я и добавил упоминание kaitai для такого :) Зачем писать свой "транслятор" описания структур, если уже есть готовый и с широкими возможностями синтаксиса.

            1. Например чтобы кто-то, как вы, показал что есть интересного по этому поводу

            2. Например, поупражняться с WPF визуализацией, возможно, кому то будет интересно на практическом примере

            3. чтобы лучше понять как это изнутри работает

            наверно еще что-то можно придумать, но вроде как со всех сторон полезно!

            Но наверно главное "чтобы" именно по поводу статьи: чтобы появилась соответствующая тема на Хабре, где можно про это почитать на родном языке, найти ссылки, подискутировать...


  1. KyHTEP
    30.11.2023 13:27

    Мне для реверса формата подошел https://hexinator.com/