Как проверить бинарный файл

Обновлено: 06.07.2024

Т екстовые файлы хранят данные в виде текста (sic!). Это значит, что если, например, мы записываем целое число 12345678 в файл, то записывается 8 символов, а это 8 байт данных, несмотря на то, что число помещается в целый тип. Кроме того, вывод и ввод данных является форматированным, то есть каждый раз, когда мы считываем число из файла или записываем в файл происходит трансформация числа в строку или обратно. Это затратные операции, которых можно избежать.

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

Выполните программу и посмотрите содержимое файла output.bin. Число, которое ввёл пользователь записывается в файл непосредственно в бинарном виде. Можете открыть файл в любом редакторе, поддерживающем представление в шестнадцатеричном виде (Total Commander, Far) и убедиться в этом.

Запись в файл осуществляется с помощью функции

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

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

Функция возвращает число удачно прочитанных элементов, которые помещаются по адресу ptr. Всего считывается count элементов по size байт. Давайте теперь считаем наше число обратно в переменную.

fseek

Одной из важных функций для работы с бинарными файлами является функция fseek

Эта функция устанавливает указатель позиции, ассоциированный с потоком, на новое положение. Индикатор позиции указывает, на каком месте в файле мы остановились. Когда мы открываем файл, позиция равна 0. Каждый раз, записывая байт данных, указатель позиции сдвигается на единицу вперёд.
fseek принимает в качестве аргументов указатель на поток и сдвиг в offset байт относительно origin. origin может принимать три значения

  • SEEK_SET - начало файла
  • SEEK_CUR - текущее положение файла
  • SEEK_END - конец файла. К сожалению, стандартом не определено, что такое конец файла, поэтому полагаться на эту функцию нельзя.

В случае удачной работы функция возвращает 0.

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

Вместо этого можно также использовать функцию rewind, которая перемещает индикатор позиции в начало.

В си определён специальный тип fpos_t, который используется для хранения позиции индикатора позиции в файле.
Функция

используется для того, чтобы назначить переменной pos текущее положение. Функция

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

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

Рассмотрим пример: пользователь вводит числа. Первые 4 байта файла: целое, которое обозначает, сколько чисел было введено. После того, как пользователь прекращает вводить числа, мы перемещаемся в начало файла и записываем туда число введённых элементов.

Вторая программа сначала считывает количество записанных чисел, а потом считывает и выводит числа по порядку.

Примеры

1. Имеется бинарный файл размером 10*sizeof(int) байт. Пользователь вводит номер ячейки, после чего в неё записывает число. После каждой операции выводятся все числа. Сначала пытаемся открыть файл в режиме чтения и записи. Если это не удаётся, то пробуем создать файл, если удаётся создать файл, то повторяем попытку открыть файл для чтения и записи.

2. Пишем слова в бинарный файл. Формат такой - сначало число букв, потом само слово без нулевого символа. Ели длина слова равна нулю, то больше слов нет. Сначала запрашиваем слова у пользователя, потом считываем обратно.

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

4. Функция saveInt32Array позволяет сохранить массив типа int32_t в файл. Обратная ей loadInt32Array считывает массив обратно. Функция loadInt32Array сначала инициализирует переданный ей массив, поэтому мы должны передавать указатель на указатель; кроме того, она записывает считанный размер массива в переданный параметр size, из-за чего он передаётся как указатель.

5. Создание таблицы поиска. Для ускорения работы программы вместо вычисления функции можно произвести сначала вычисление значений функции на интервале с определённой точностью, после чего брать значения уже из таблицы. Программа сначала производит табулирование функции с заданными параметрами и сохраняет его в файл, затем подгружает предвычисленный массив, который уже используется для определения значений. В этой программе все функции возвращают переменную типа Result, которая хранит номер ошибки. Если функция отработала без проблем, то она возвращает Ok (0).

6. У нас имеются две структуры. Первая PersonKey хранит логин, пароль, id пользователя и поле offset. Вторая структура PersonInfo хранит имя и фамилию пользователя и его возраст. Первые структуры записываются в бинарный файл keys.bin, вторые структуры в бинарный файл values.bin. Поле offset определяет положение соответствующей информации о пользователе во втором файле. Таким образом, получив PersonKey из первого файла, по полю offset можно извлечь из второго файла связанную с данным ключом информацию.

Зачем так делать? Это выгодно в том случае, если структура PersonInfo имеет большой размер. Извлекать массив маленьких структур из файла не накладно, а когда нам понадобится большая структура, её можно извлечь по уже известному адресу в файле.

email

Всё ещё не понятно? – пиши вопросы на ящик

image

Автор: Йори Квичко (Yori Kvitchko) (Counter Hack)

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

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

Хотя для некоторых пентестеров реверс-инжиниринг – темное искусство, выходящее за рамки их компетенции и доступное только признанным мастерам своего дела. Да, я согласен, не так просто перейти от анализа веб-приложений к использованию IDA Pro. Однако, даже не будучи мастером в области реверс-инжиниринга, вы сможете использовать некоторые практические методы для получения важной информации и, возможно, даже сможете находить уязвимости в отдельных бинарных файлах или автономных приложениях.

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

Один из самых интересных видов приложений, с которым вы столкнетесь во время пентеста – утилиты для внутреннего использования. Обычно разработчики подобных инструментов придерживаются концепции «главное, чтобы работало» и не сильно заботятся о безопасности, поскольку не предполагают, что кто-то со стороны может получить доступ к таким утилитам. Однако мы, пентестеры, конечно же, знаем, что получить доступ к таким утилитам не так сложно, как кажется на первый взгляд. Вот некоторые ситуации, когда вы можете использовать техники по анализу бинарных файлов:

  • Когда вы получили доступ к скомпрометированной (часто клиентской) машине, где установлено приложение для внутреннего пользования. Обращайте особое внимание на все, что маркируется именем компании.
  • Когда во время пентестов владельцы исследуемой системы заодно попросили вас протестировать конкретное приложение.
  • После получения доступа к внутреннему веб-серверу, на котором установлено Flash/Java или любое другое клиентское приложение, встроенное в размещаемый веб-сайт.
  • Когда вы находите любой бинарный файл или приложение, которое на ваш взгляд может относиться к утилитам для внутреннего пользования. Не забудьте проверить общедоступные диски и ftp-сервера на предмет наличия на них папок с именем «tools» или нечто похожее.

В настоящее время множество автономных приложений являются ни чем иным как фронтендами к старым веб-приложениям. Если это ваш случай (приложение – фронтенд к веб-серверу), ваша задача заметно упрощается. В дополнение к этому вы можете снять дамп памяти у работающего приложения (при помощи утилиты mdd или одной из утилит, которые перечислены в этом списке), а затем использовать утилиту strings. Вы можете найти GET- или POST-запросы с паролями или другой интересной информацией.

Запустите приложение и найдите его PID. Это можно сделать при помощи Task Manager, Process Explorer или старой доброй командной строки, например, так:

Как только мы получили PID (предположим, он равен 6140), мы можем узнать IP-адреса, с которыми взаимодействует приложение. Откройте командную строку и запустите netstat с параметром, отображающим PID, и перезапуском каждую секунду.

Если ничего не происходит, попробуйте заставить приложение создать исходящее соединение или открыть порт на прослушивание. Как только вы получили IP-адрес, простой фильтр в Wireshark доделает за нас всю остальную работу, локализовав интересующий на IP-адрес.

Вы также можете поискать DNS-запросы, которые были использованы для получения IP-адреса, при помощи следующего фильтра:

DNS-имена часто могут помочь в определении типа бекэнда. Также не забывайте использовать сканер Nmap.

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

Используя продукты Контура, специалисты делают внутренний контроль бизнеса систематизированным, снижают объем ручной работы и эффективно предупреждают нарушения.

У меня есть файл размером чуть больше 500 МБ, который вызывает некоторые проблемы.

Я считаю, что проблема в соглашении конца строки (EOL). Я хотел бы посмотреть на файл в его неинтерпретированной необработанной форме (1), чтобы подтвердить соглашение EOL о файле.

Как я могу просмотреть «двоичный» файл, используя что-то встроенное в Windows 7? Я бы предпочел не загружать что-либо дополнительное.

(1) Мой коллега и я открыли файл в текстовых редакторах, и они показывают строки, как и следовало ожидать. Но оба текстовых редактора будут открывать файлы с различными соглашениями EOL и интерпретировать их автоматически. (TextEdit и Emacs 24.2. Для Emacs я создал второй файл с использованием только первых 4K-байтов head -c4096 в окне Linux и открыл его из окна Windows.

Я попытался использовать hexl-режим в Emacs, но когда я перешел в hexl-режим и вернулся в текстовый режим, содержимое буфера изменилось, добавив видимый ^ M в конец каждой строки, так что я не доверяя этому на данный момент.

Я полагаю, что проблема может быть в конце строки символов. Редакторы, которые мы с коллегой пытались (1), просто автоматически определили в конце строки и показали нам строки. И на основании других доказательств я считаю, что конвенция EOL касается только возврата каретки. (2) вернуть только.

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

ОБНОВИТЬ

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

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

struct scan_info
char model[25];// наименование модели
int price;// цена
double x_size;// горизонтальный размер области сканирования
double y_size;// вертикальный размер области сканирования
int optr;// оптическое разрешение
int grey;// число градаций серого
>;
const int kolScaners = 1;
scan_info spisok[10];
void input();
void sort( scan_info spisok[10] );
void record( scan_info spisok[10] );
int main()
input();
record(spisok);
system( "PAUSE" );

>
// Ввод структур
void input()
for ( int i = 0; i < kolScaners; i++ )
fflush( stdin );
system( "CLS" );
printf( "Введите данные № %d \n", i+1 );
printf( "Модель: \n" );
gets( spisok.model );
printf( "Цена: \n" );
scanf( "%d", &spisok.price );
printf( "Горизонтальный размер области сканирования: \n" );
scanf( "%f", &spisok.x_size );
printf( "Вертикальный размер области сканирования: \n" );
scanf( "%f", &spisok.y_size );
printf( "Оптическое разрешение: \n" );
scanf( "%d", &spisok.optr);
printf( "Число градаций серого: \n" );
scanf( "%d", &spisok.grey);
>
sort(spisok);

>
// Сортировка
void sort( scan_info spisok[10] )
for ( int i = 0; i < kolScaners; i++ )
int imin = i;
int j;
for ( j = i + 1 ; j < kolScaners; j++ )
if ( strcmp( spisok[j].model,spisok[imin].model ) < 0 ) imin = j;
>
scan_info a = spisok;
spisok = spisok[imin];
spisok[imin] = a;
>
>
// Запись структуры в бинарный файл
void record( scan_info spisok[10] )
// Открываем файл
FILE * f;
if (( f = fopen( "file", "wb" )) == NULL )
printf("\nОшибка открытия файла !");
exit(1);
>
// Запись в файл
int k = 0;
for ( int i = 0; i < kolScaners; i++ )

image

Если вас не пугает картинка выше, если вы знаете чем отличается big-endian от little-endian, если вам всегда было интересно как "устроены" бинарные файлы, значит эта статья для ВАС!

На Хабре уже было несколько статей про реверс инжинеринг бинарных форматов и про исследование структуры байткода .class файла:
Пул констант,
Java Bytecode Fundamentals,
Java байткод «Hello world»,
Hello World из байт-кода для JVM и т.д.
У исследователя возникает задача либо разобраться с неизвестным бинарным протоколом либо поковырять бинарную структуру на которую есть спецификация.

Мой интерес к бинарным форматам возник еще когда я был студентом и писал курсовую работу по разработке драйвера файловой системы Linux. Несколько лет спустя я читал лекции по основам Linux для экспертов-криминалистов — в давние времена Linux был в новинку и молодой специалист после ВУЗа мог поведать взрослым экспертам много нового. Рассказывая, как снять дамп с диска с помощью dd, а после подключить образ на другом компьютере для изучения, я понимал, что в образе диска лежит много интересной информации. Эту информацию можно было бы извлечь и без монтирования образа (ага, mount -o loop . ), если знать спецификацию на формат файловой системы и иметь соответствующие инструменты. К сожалению, у меня не было таких инструментов.

Мне был нужен универсальный механизм для описания бинарных структур и универсальный загрузчик. Загрузчик, используя описание, будет читать бинарные данные в память. Обычно приходиться иметь дело с числами, строками, массивами данных и составными структурами. С числами все просто — они имеют фиксированную длину — 1, 2, 4 или 8 байт и могут быть сразу отображены в типы данных, имеющиеся в языке. Например: byte, short, int, long для Java. Для числовых типов длиной более одного байта нужно предусмотреть маркер порядка байт (так называемое BigEndian/LittleEndiang представление).

Со строками сложнее — они могут быть в различных кодировках (ASCII, UNICODE), иметь фиксированную или переменную длину. Строку фиксированной длинны, можно считать как массив байт. Для строк с переменной длиной можно использовать два варианта записи — указывать в начале строки ее длину (Pascal или Length-prefixed strings) либо в конце строки ставить специальный знак, обозначающий конец строки. В качестве такого знака используют байт со значением ноль (так называемые null-terminated srings). Оба варианта имеют преимущества и недостатки, обсуждение которых выходит за рамки этой статьи. Если размер задается в начале, то при разработке формата нужно определиться с максимальной длиной строки: от этого зависит сколько байт мы должны выделить на маркер длины: 2 8 — 1 для одного байта, 2 16 — 1 для двух байт и т.д.

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

Нам необходимо каким-то образом описать структуру Java .class файла. В качестве результата хотелось бы иметь набор Java классов, где каждый класс содержит только поля, соответсвующие исселдуемой структуре данных и, возможно, вспомогательные методы для отображения объекта в человеко-читаемом виде при вызове toString() метода. Категорически не хотелось бы иметь внутри логику, отвечающую за чтение или запись файла.

Берем спецификациею виртуальной машины Java,
JVM Specification, Java SE 12 Edition.
Нас будет интересовать секция 4 "The class File Format".

Для того, чтобы определить какие поля в каком порядке загружать, введем аннотацию @FieldOrder(index=. ). Нам необходимо явно указывать порядок полей для загрузчика, поскольку спецификация не даем нам гарантии на то, в каком порядке они будут сохранены в бинарном файле.

Java .class файл начинается с 4 байт magic number, двух байт минорной версии Java и двух байт мажорной версии. Упакуем magic number в переменную int, а номер минорной и мажорной версии — в short:

Дальше в .class файле идет размер пула констант (двухбайтовая переменная) и сам пул констант. Введем аннотацию @ContainerSize для объявления размера массивов и списочных структур. Размер может быть фиксированный (будем задавать его через аттрибут value) либо иметь переменную длинну, определяемую прочитанной ранее переменной. В этом случае будем использовать "fieldName" аттрибут, который указывает из какой переменной будем считывать размер контейнера. В соответствии со спецификацией (секция 4.1,
"The ClassFile Structure"), реальный размер пула констант отличается на 1 от того значения,
которое записано в constant_pool_count:

Чтобы учесть такие коррекции, введем дополнительный аттрибут corrector в @ContainerSize аннотации.
Теперь мы можем добавить описание пула констант:

В случае более сложных вычислений, можно просто добавить get-метод, который вернет необходимое значение:

Каждый элемент в пуле констант представляет из себя либо описание соответствующей константы типа int, long, float, double, String, либо описание одной из составных частей Java класса — поля класса (fields), методы, сигнатуры методов и т.д. Под термином "контстанта" здесь подразумевается неименованое значение, используемое в коде:

Значение 100500 будет представленно в пуле констант как экземпляр CONSTANT_Integer. JVM спецификация для Java 12 определяет 17 типов, которые могут быть в пуле констант.

Constant type Tag
CONSTANT_Class 7
CONSTANT_Fieldref 9
CONSTANT_Methodref 10
CONSTANT_InterfaceMethodref 11
CONSTANT_String 8
CONSTANT_Integer 3
CONSTANT_Float 4
CONSTANT_Long 5
CONSTANT_Double 6
CONSTANT_NameAndType 12
CONSTANT_Utf8 1
CONSTANT_MethodHandle 15
CONSTANT_MethodType 16
CONSTANT_Dynamic 17
CONSTANT_InvokeDynamic 18
CONSTANT_Module 19
CONSTANT_Package 20

В нашей реализации создадим класс ConstantPoolItem в котором будет однобайтовое поле tag, определяющее какую именно структуру мы читаем в данный момент. На каждый элемент в таблице выше создадим Java класс, наследник ConstantPoolItem. Универсальный загрузчик бинарных файлов должен уметь определять какой именно класс-наследник должен быть использован на основании уже прочитанного тега
(в общем случае тег может быть переменной любого типа). Для этой цели определим интерфейс HasInheritor и реализуем этот интерфейс в классе ConstantPoolItem:

Универсальный загрузчик сам инстанцирует необходимый класс и продложит считывание. Единственное условие: индексы в классах-наследниках должны иметь сквозную нумерацию с родительским классом. Это означает что во всех классах-наследниках ConstantPoolItem, FieldOrder аннатация должна иметь индекс больше единицы, поскольку в родительском классе мы уже прочитали поле tag с номером "1".

После списка элементов пула констант в .class файле идет двухбайтовый идентификатор, определяющий детали данного класса — является ли класс аннотацией, интерфейсом, абстрактным классом, имеет ли флаг final и т.п. Далее следует двухбайтовый идентификатор (ссылка на элемент в пуле констант), определяющий данный класс. Этот идентификатор должен указывать на элемент с типом ClassInfo. Аналогичным образом определяется суперкласс для данного класса (то что указано после слова "extends" в определении класса). Для классов, не имеющих явно определенных суперклассов, в данном поле присутствует ссылка на класс Object.

В языке Java у любого класса может быть только один суперкласс, но количество
интерфейсов, которые реализует данный класс может быть несколько:

Каждый элемент в interfaceIndexList представляет ссылку на элемент в пуле констант (по указанному
инедксу должен находится элемент с типом ClassInfo).
Переменные класса (properties, fields) и методы представленны соответсвующими списками:

Последним элементом в описании Java .class файла является список аттрибутов класса. Здесь могут быть перечислены аттрибуты описывающие исходный файл, относящийся к классу, вложенные классы и т.д.

Java bytecode оперирует числовыми данными в big-endian представлении, будем это представление использовать по умолчанию. Для двоичных форматов с little-endian числами будем использовать LittleEndian аннотацию. Для строк, которые не имеют предопределенной длины, а
считываются до терминального символа (как C-like null-terminated строки) будем использовать
аннотацию @StringTerminator:

Иногда в нижележащие классы нужно пробросить информацию с более высокого уровня. Объект Method в methodList не имеет информации об имени класса, в котором он находится, более того объект-метод не содержит своего названия и списка параметров. Вся эта информация представленна в виде индексов на элементы в пуле констант. Для виртуальной машины этого достаточно, но нам хотелось бы реализовать методы toString(), чтобы они отображали информацию о методе в удобном для человека виде, а не в виде индексов на элементы в пуле констант. Для этого класс Method должен получить ссылку на ConstantPoolList и на переменную со значением thisClassIndex. Чтобы иметь возможность передавать ссылки на нижележащие уровни вложенности, будем использовать аннотацию Inject:

В текущем классе (ClassFile) будут вызываться getter методы для constantPoolList и thisClassIndex переменных, а в принимающем классе (в данном случае Method), будут вызваны setter методы (если они присутствуют).

Итак, у нас есть один интерфейс HasInheritor и пять аннотаций @FieldOrder, @ContainerSize, LittleEndian, Inject и @StringTerminator, которые позволяют описывать бинарные структуры на высоком уровне абстракции. Имея формальное описание, мы можем передать его универсальному загрузчику, который сможет инстанцировать описанную структуру, осуществить разбор бинарного файла и зачитать его в память.

В результате мы должны иметь возможность использовать такой код:

К сожалению, разработчики Java платформы немного перемудрили и для восьмибайтных значений в пуле
констант предусмотрели две ячейки, причем первая ячейка должна содержать значение, а вторая остается
пустой. Это касается long и double констант.


По всей видимости, разработчики Java хотели применить какую-то низкоуровневую оптимизацию, но позже
было признано, что это дизайнерское решение оказалось

Чтобы обработать эти специфичные случаи, добавим аннотацию @EntrySize, которую будем использовать,
чтобы пометить восьмибайтные константы:

Аттрибут value указывает на количество ячеек, которые будет занимать элемент, index — индекс элемета,
который содержит значение. классы LongInfo и DoubleInfo будут расширять класс EightByteNumberInfo.
Универсальный загрузчик нужно будет расширить фукционалом, поддерживающим аннотацию @EntrySize.

После загрузки класса ClassFileLoader'ом можно остановить отладчик и исследовать загруженный класс в инспекторе переменных в IDE.

image

Class file будет выглядеть вот так:

image

А Constant Pool так:

Для загрузки скомпилированного class файла воспользуйтесь загрузчиком annotate4j.classfile.loader.ClassFileLoader.

Большая часть кода была написана для Java 6, к современным версиям я адоптировал только constant pool. Сил и желания полностью реализовать загрузчик Java opcode'ов у меня не хватило, поэтому там только небольшие наработки в этой части.

Используя эту библиотеку (core часть) мне удалось зареверсить бинарный файл с данными Холтер мониторинга (ЭКГ исследование суточной активности сердца). С другой стороны, я не смог расшифровать бинарный протокол одной учетной системы, написанной на Delphi. Я не разобрался как передаются даты и иногда возникала ситуация, когда фактичиские данные не соответствовали структуре, построенной по предыдущим значениям.

Я пытался построить аналогично Java class файлу модель для ELF формата (запускаемый формат в Unix/Linux), но я не смог полностью понять спецификацию — она оказалась для меня слишком расплывчатой. Та же участь постигла JPEG и BMP форматы — все время натыкался на какие-то сложности с пониманием спецификации.

Читайте также: