Форматы объектных файлов

Original:http://www.backerstreet.com/decompiler/object_formats.htm

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

Существует 3 возможных типа файлов:

  1. Структурированные форматы (COFF, ELF)
  2. Помеченные форматы потоков (OMF, IEEE)
  3. Необработанные файлы (DOS, rom-изображения, S-записи и т. Д.)

У каждого типа формата есть свои сильные и слабые стороны.

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

Каждый формат файла требует своего собственного File Format Reader.

Шаг 1: укажите тип файла

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

Смещение содержимого файла формата

ELF 0 7F 45 4C 46

COFF 0 2-байтовый тип машины (*)

IEEE

OMF

(*) Одной из характеристик COFF является то, что первые 2 байта идентифицируют как формат COFF, так и целевой процессор. К сожалению, не существует стандарта для этих 2 байтов, а также для процессоров, которые поддерживают как большой endiand, так и little endian, те же 2 байта могут появляться в обоих заказах, что затрудняет идентификацию файла как файла COFF с абсолютной достоверностью.

Мы увидим, что даже файлы COFF для одного и того же целевого процессора могут иметь разные структуры данных, потому что разные компиляторы предпочитают не следовать стандарту (обычно они иногда используют 32-битные поля вместо исходного 16-битного определения поля).

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

Шаг 2: определение типа процессора

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

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

Смещение содержимого файла формата

ELF 0x12 2-байтовый тип машины

COFF 0 см. Предыдущую таблицу

IEEE

OMF

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

Шаг 3: определение областей кода, данных и информации

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

Форматы ELF и COFF основаны на концепции разделов. Раздел – это область в файле, которая имеет однородную информацию, такую ​​как весь код или все данные, или все символы и т. Д.

Декомпилятор читает таблицу разделов и использует ее для преобразования смещений файлов в адреса и наоборот.

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

Обратите внимание, что не обязательно раздел, помеченный как исполняемый, будет содержать только машинные инструкции. Другие типы данных только для чтения можно также поместить в исполняемый раздел, такой как строки только для чтения (“const”) и константы с плавающей запятой. Компилятор или компоновщик также могут добавить дополнительный код, который не был сгенерирован непосредственно скомпилированным исходным кодом. Примером дополнительного кода являются таблицы виртуальных функций, обработка исключений (try / catch / throw) в C ++ и таблица глобального смещения (GOT) и таблица связей процедур (PLT) для поддержки динамической компоновки DLL.

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

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

Неиспользуемые исполняемые файлы предоставляют различный уровень символической информации:

  • Адреса глобальных меток: это могут быть точки входа функций и глобальные переменные данных. Обратите внимание, что в большинстве случаев размер таких объектов не предусмотрен. То есть мы можем знать, где начинается функция, но мы можем не знать, где она заканчивается. Так как точки входа статических функций могут не храниться в файлах, не просто предположить, что функция заканчивается в начале точки входа следующей функции.
  • Имена импортированных (динамически связанных) библиотек и адреса точек входа библиотек или кода батута, сгенерированного для доступа к этим библиотекам. Если целевая программа является самой DLL, таблица экспорта хранится в двоичном файле. Таблица экспорта предоставляет точку входа функций, экспортируемых DLL.
  • Если в файле обнаружена таблица перемещения, декомпилятор может использовать содержащуюся в ней информацию, чтобы определить, какие команды действуют на адреса, а не на числовые константы. Это очень важно, когда вы пытаетесь определить цель инструкции по сборке. Это также означает, что декомпилятор должен фактически привязать целевой файл к некоторому фиктивному адресу памяти, который может полностью отличаться от фактического адреса, который будет использоваться операционной системой, особенно при декомпиляции перемещаемого файла (.o, .obj или. Dll).
  • Если файл был скомпилирован с отладочной информацией (-g в системах Unix), можно найти гораздо больше информации, такой как список исходных файлов и номеров строк, который использовался для построения целевой программы, типы переменных и Глобальные, статические и локальные переменные функций с их именами. Это лучший случай, поэтому декомпилятор должен воспользоваться этим богатством информации.

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

Leave a Reply

Your email address will not be published. Required fields are marked *