Двоичный формат всегда необходим в каждой универсальной/стандартной операционной системе. Сегодня мы углубимся в Linux и его знаменитый формат ELF.
Хотя Linux не требует расширения для файлов ELF (это могут быть *.bin, *.so и другие (может быть без каких-либо расширение также)) Файлы Executable & Linkable Format обычно используются для исполняемых файлов, моделей ядра, разделяемых библиотек, дампов ядра и объектных файлов. архитектура.

Вообще говоря, файлы ELF состоят из трех основных компонентов (заголовок ELF, разделы и сегменты). Каждый из этих элементов играет свою роль в процессе связывания/загрузки исполняемых файлов ELF. Давайте ознакомимся со структурой каждого компонента:

Заголовок ELF

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

  • e_ident: массив из 16 байтов, содержащий идентификационные флаги файла, которые служат для декодирования и интерпретации содержимого файла. Примеры этих идентификационных флагов включают:
    -EI_MAG0–3: магия ELF
    - EI_CLASS: класс файла.
    - EI_DATA: кодировка данных файла.
    - EI_VERSION: версия файла.
    - EI_OSABI: идентификация OS/ABI.
    - EI_ABIVERSION: версия ABI.
    - EI_PAD: начало байтов заполнения.
    - EI_NIDENT: размер ei_ident.
  • e_type: тип исполняемого файла.
  • e_machine: архитектура файла.
  • e_version: версия объектного файла.
  • e_entry: точка входа приложения.
  • e_phoff: смещение файла таблицы заголовков программы.
  • e_shoff: смещение файла таблицы заголовков разделов.
  • e_flags: специфические для процессора флаги, связанные с файлом.
  • e_ehsize: размер заголовка ELF.
  • e_phentsize: размер записи заголовка программы в таблице заголовков программ.
  • e_phnum: количество заголовков программы.
  • e_shentsize: размер записи заголовка раздела в таблице заголовков разделов.
  • e_shnum: количество заголовков разделов.
  • e_shstrndx: индекс в таблице заголовков разделов, обозначающий раздел, предназначенный для хранения имен разделов.

Чтобы просмотреть эти поля для данного двоичного файла ELF, мы можем использовать любой парсер ELF по выбору. Распространенным инструментом для быстрого анализа файлов ELF является утилита readelf из GNU binutils.

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

readelf -h ‹исполняемый›

Разделы

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

  • sh_name: индекс имени раздела в таблице строк заголовка раздела.
  • sh_type: тип раздела.
  • sh_flags: атрибуты раздела.
  • sh_addr: виртуальный адрес раздела.
  • sh_offset: смещение раздела на диске.
  • sh_size: размер раздела.
  • sh_link: индекс ссылки на раздел.
  • sh_Info: раздел дополнительной информации.
  • sh_addralign: выравнивание разделов.
  • sh_entsize: размер записей, содержащихся в разделе.

Вот некоторые общие разделы:

  • .текст: код.
  • .данные: инициализированные данные.
  • .rodata: инициализированные данные только для чтения.
  • .bss: неинициализированные данные.
  • .plt: PLT (таблица связи процедур) (эквивалент IAT).
  • .got: записи GOT, предназначенные для динамически связанных глобальных переменных.
  • .got.plt: записи GOT, посвященные динамически связанным функциям.
  • .symtab: глобальная таблица символов.
  • .динамический: содержит всю необходимую информацию для динамического связывания.
  • .dynsym: таблицы символов, предназначенные для динамически связанных символов.
  • .strtab: таблица строк раздела .symtab.
  • .dynstr: таблица строк раздела .dynsym.
  • .interp: встроенная строка RTLD.
  • .rel.dyn: глобальная таблица перемещения переменных.
  • .rel.plt: таблица перемещений функций.

Чтобы отобразить разделы с помощью readelf, мы можем использовать следующую команду:

readelf -S ‹исполняемый›

Сегменты

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

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

  • p_type: тип сегмента.
  • p_flags: атрибуты сегмента.
  • p_offset: смещение сегмента в файле.
  • p_vaddr: виртуальный адрес сегмента.
  • p_paddr: физический адрес сегмента.
  • p_filesz: размер сегмента на диске.
  • p_memsz: размер сегмента в памяти.
  • P_align: выравнивание сегментов в памяти.

Существует множество типов сегментов. Некоторые из распространенных типов следующие

  • PT_NULL: неназначенный сегмент (обычно первая запись в таблице заголовков программ).
  • PT_LOAD: загружаемый сегмент.
  • PT_INTERP: сегмент, содержащий раздел .interp.
  • PT_TLS: сегмент локального хранилища потока (общий для статически связанных двоичных файлов).
  • PT_DYNAMIC: удержание раздела .dynamic.

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

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

Чтобы больше поиграть с файлами ELF, Linux предоставляет нам три команды, как показано ниже:

readelf: displays information about one or more ELF format object
        files.  The options control what particular information to
        display.

objdump: displays information about one or more object files.
        The options control what particular information to display.
        This information is mostly useful to programmers who are
        working on the compilation tools, as opposed to programmers who
        just want their program to compile and work.

 nm:     lists the symbols from object files objfile.... If no object files
        are listed as arguments, nm assumes the file a.out.

Note : something important to highlight about readelf it performs a similar
       function to objdump but it goes into more detail and it exists
       independently of the BFD library, so if there is a bug in BFD then
       readelf will not be affected.

Ссылки и ресурсы:

https://www.intezer.com/blog/research/executable-linkable-format-101-part1-sections-segments/
https://medium.com/ax1al/a-brief-introduction -to-executable-linkable-format-1ed9a3fdcc91
https://man7.org/linux/man-pages/man1/readelf.1.html
https://linux.die .net/man/1/objdump
https://linux.die.net/man/1/nm

Спасибо за чтение.

Если у вас есть какие-либо вопросы, не стесняйтесь комментировать и, если вы хотите видеть больше подобных статей, пожалуйста, хлопайте в ладоши 👏👏👏