От исходного кода к двоичному коду
Программирование начинается с умной идеи и написания исходного кода на языке программирования по вашему выбору, например C, и сохранения исходного кода в файле. С помощью подходящего компилятора, например GCC, ваш исходный код сначала транслируется в объектный код. В конце концов, компоновщик переводит объектный код в двоичный файл, который связывает объектный код со ссылочными библиотеками. Этот файл содержит отдельные инструкции в виде машинного кода, которые понимаются ЦП и выполняются сразу после запуска скомпилированной программы.
Упомянутый выше двоичный файл имеет определенную структуру, и один из наиболее распространенных файлов называется ELF, что означает аббревиатуру Executable и Linkable Format. Он широко используется для исполняемых файлов, перемещаемых объектных файлов, общих библиотек и дампов ядра.
Двадцать лет назад - в 1999 году - проект 86open выбрал ELF в качестве стандартного двоичного формата файла для Unix и Unix-подобных систем на процессорах x86. К счастью, формат ELF был ранее задокументирован как в двоичном интерфейсе приложений System V, так и в стандарте интерфейса инструментов [4]. Этот факт чрезвычайно упростил соглашение о стандартизации между различными поставщиками и разработчиками операционных систем на основе Unix.
Причиной этого решения стал дизайн ELF - гибкость, расширяемость и кроссплатформенная поддержка различных форматов байтов и размеров адресов. Дизайн ELF не ограничивается конкретным процессором, набором инструкций или аппаратной архитектурой. Подробное сравнение форматов исполняемых файлов можно найти здесь [3].
С тех пор формат ELF используется несколькими различными операционными системами. Среди прочего, это Linux, Solaris / Illumos, Free-, Net- и OpenBSD, QNX, BeOS / Haiku и Fuchsia OS [2]. Кроме того, вы найдете его на мобильных устройствах под управлением Android, Maemo или Meego OS / Sailfish OS, а также на игровых консолях, таких как PlayStation Portable, Dreamcast и Wii.
В спецификации не уточняется расширение имени файла для файлов ELF. Используется множество комбинаций букв, таких как .axf, .bin, .elf, .o, .prx, .puff, .ko, .so и .mod, или none.
Структура файла ELF
На терминале Linux команда man elf дает вам удобную сводку о структуре файла ELF:
Листинг 1: Справочная страница структуры ELF
$ человек одиннадцатьELF (5) Руководство программиста Linux ELF (5)
ИМЯ
elf - формат исполняемых файлов и файлов Linking Format (ELF)
ОБЗОР
#включают
ОПИСАНИЕ
Заголовочный файл определяет формат исполняемого двоичного файла ELF.
файлы. Среди этих файлов есть обычные исполняемые файлы, которые можно перемещать.
объектные файлы, файлы ядра и разделяемые библиотеки.
Исполняемый файл, использующий формат файла ELF, состоит из заголовка ELF,
за которым следует таблица заголовков программы или таблица заголовков разделов, или и то, и другое.
Заголовок ELF всегда находится по нулевому смещению файла. Программа
таблица заголовков и смещение таблицы заголовков разделов в файле
определяется в заголовке ELF. Две таблицы описывают остальные
особенности файла.
...
Как видно из описания выше, файл ELF состоит из двух разделов - заголовка ELF и данных файла. Раздел данных файла может состоять из таблицы заголовка программы, описывающей ноль или более сегментов, таблицы заголовка раздела, описывающей ноль или более разделов, за которой следуют данные, на которые ссылаются записи из таблицы заголовка программы, и таблицы заголовка раздела. Каждый сегмент содержит информацию, необходимую для выполнения файла во время выполнения, а разделы содержат важные данные для связывания и перемещения. На рисунке 1 это схематично показано.
Заголовок ELF
Заголовок ELF имеет длину 32 байта и определяет формат файла. Он начинается с последовательности из четырех уникальных байтов 0x7F, за которыми следуют 0x45, 0x4c и 0x46, что переводится в три буквы E, L и F. Среди других значений заголовок также указывает, является ли это файл ELF для 32 или 64-битный формат, использует малый или большой порядок байтов, показывает версию ELF, а также для какой операционной системы файл был скомпилирован, чтобы взаимодействовать с правильным двоичным интерфейсом приложения (ABI) и набором команд процессора.
Шестнадцатеричный дамп бинарного файла touch выглядит следующим образом:
Листинг 2: Шестнадцатеричный дамп двоичного файла
$ hd / usr / bin / touch | голова -500000000 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 | .ELF ........... |
00000010 02 00 3e 00 01 00 00 00 e3 25 40 00 00 00 00 00 | ..> ......% @ ..... |
00000020 40 00 00 00 00 00 00 00 28 e4 00 00 00 00 00 00 | @ ....... (....... |
00000030 00 00 00 00 40 00 38 00 09 00 40 00 1b 00 1a 00 | [электронная почта защищена] @ ..... |
00000040 06 00 00 00 05 00 00 00 40 00 00 00 00 00 00 00 | [электронная почта защищена] |
Debian GNU / Linux предлагает команду readelf, которая входит в пакет GNU binutils. Вместе с ключом -h (сокращенная версия –file-header) он красиво отображает заголовок файла ELF. Листинг 3 иллюстрирует это для команды touch.
Листинг 3: Отображение заголовка файла ELF
$ readelf -h / usr / bin / touchЗаголовок ELF:
Магия: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Класс: ELF64
Данные: дополнение до 2, прямой порядок байтов
Версия: 1 (текущая)
ОС / ABI: UNIX - System V
Версия ABI: 0
Тип: EXEC (Исполняемый файл)
Машина: Advanced Micro Devices X86-64
Версия: 0x1
Адрес точки входа: 0x4025e3
Заголовки начала программы: 64 (байтов в файл)
Начало заголовков разделов: 58408 (байтов в файл)
Флаги: 0x0
Размер этого заголовка: 64 (байта)
Размер заголовков программы: 56 (байтов)
Количество заголовков программ: 9
Размер заголовков разделов: 64 (байта)
Количество заголовков секций: 27
Индекс таблицы строки заголовка раздела: 26
Заголовок программы
Заголовок программы показывает сегменты, используемые во время выполнения, и сообщает системе, как создать образ процесса. Заголовок из листинга 2 показывает, что файл ELF состоит из 9 заголовков программ, каждый из которых имеет размер 56 байт, а первый заголовок начинается с байта 64.
Опять же, команда readelf помогает извлечь информацию из файла ELF. Ключ -l (сокращение от –program-headers или –segments) раскрывает более подробную информацию, как показано в листинге 4.
Листинг 4. Отображение информации о заголовках программы.
$ readelf -l / usr / bin / touchТип файла Elf - EXEC (исполняемый файл)
Точка входа 0x4025e3
Имеется 9 заголовков программ, начиная со смещения 64.
Заголовки программы:
Тип Offset VirtAddr PhysAddr
Выравнивание флажков FileSiz MemSiz
PHDR 0x0000000000000040 0x0000000000400040 0x0000000000400040
0x00000000000001f8 0x00000000000001f8 R E 8
ИНТЕРП 0x0000000000000238 0x0000000000400238 0x0000000000400238
0x000000000000001c 0x000000000000001c R 1
[Запрашивающий интерпретатор программы: /lib64/ld-linux-x86-64.so.2]
ЗАГРУЗИТЬ 0x0000000000000000 0x0000000000400000 0x0000000000400000
0x000000000000d494 0x000000000000d494 R E 200000
ЗАГРУЗИТЬ 0x000000000000de10 0x000000000060de10 0x000000000060de10
0x0000000000000524 0x0000000000000748 RW 200000
ДИНАМИЧЕСКИЙ 0x000000000000de28 0x000000000060de28 0x000000000060de28
0x00000000000001d0 0x00000000000001d0 RW 8
ПРИМЕЧАНИЕ 0x0000000000000254 0x0000000000400254 0x0000000000400254
0x0000000000000044 0x0000000000000044 R 4
GNU_EH_FRAME 0x000000000000bc40 0x000000000040bc40 0x000000000040bc40
0x00000000000003a4 0x00000000000003a4 R 4
GNU_STACK 0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 ЧЗ 10
GNU_RELRO 0x000000000000de10 0x000000000060de10 0x000000000060de10
0x00000000000001f0 0x00000000000001f0 R 1
Сопоставление раздела с сегментом:
Сегменты ...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini. родата .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
04. Динамический
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .jcr .dynamic .got
Заголовок раздела
Третья часть структуры ELF - это заголовок раздела. Он предназначен для перечисления отдельных разделов двоичного файла. Ключ -S (сокращение от –section-headers или –sections) перечисляет различные заголовки. Что касается команды touch, то имеется 27 заголовков разделов, и в листинге 5 показаны только первые четыре из них плюс последний. Каждая строка охватывает размер раздела, тип раздела, а также его адрес и смещение в памяти.
Листинг 5: Детали раздела раскрыты readelf
$ readelf -S / usr / bin / touchИмеется 27 заголовков разделов, начиная со смещения 0xe428:
Заголовки разделов:
[Nr] Имя Тип Адрес Смещение
Размер EntSize Флаги Ссылка Информация Выровнять
[0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[1] .interp ПРОГБИТЫ 0000000000400238 00000238
000000000000001c 0000000000000000 А 0 0 1
[2] .note.ABI-tag ПРИМЕЧАНИЕ 0000000000400254 00000254
0000000000000020 0000000000000000 А 0 0 4
[3] .note.gnu.build-i ПРИМЕЧАНИЕ 0000000000400274 00000274
...
...
[26] .shstrtab STRTAB 0000000000000000 0000e334
00000000000000ef 0000000000000000 0 0 1
Ключ к флагам:
W (запись), A (выделение), X (выполнение), M (объединение), S (строки), l (большой размер)
I (информация), L (порядок ссылок), G (группа), T (TLS), E (исключить), x (неизвестно)
O (требуется дополнительная обработка ОС) o (зависит от ОС), p (зависит от процессора)
Инструменты для анализа файла ELF
Как вы могли заметить из приведенных выше примеров, GNU / Linux снабжен рядом полезных инструментов, которые помогут вам анализировать файл ELF. Первый кандидат, который мы рассмотрим, - это файловая утилита.
Файл отображает основную информацию о файлах ELF, включая архитектуру набора инструкций, для которой предназначен код в перемещаемом, исполняемом или совместно используемом объектном файле. В листинге 6 указано, что / bin / touch - это 64-разрядный исполняемый файл, соответствующий стандарту Linux Standard Base (LSB), динамически связанный и созданный для ядра GNU / Linux версии 2.6.32.
Листинг 6: Основная информация с использованием файла
$ file / bin / touch/ bin / touch: исполняемый файл ELF 64-бит LSB, x86-64, версия 1 (SYSV), динамически скомпонованный, интерпретатор / lib64 / l,
для GNU / Linux 2.6.32, BuildID [sha1] = ec08d609e9e8e73d4be6134541a472ad0ea34502, без
$
Второй кандидат - readelf. Он отображает подробную информацию о файле ELF. Список переключателей сравнительно длинный и охватывает все аспекты формата ELF. Использование переключателя -n (сокращение от –notes) В листинге 7 показаны только те разделы примечаний, которые существуют в файле touch - тег версии ABI и битовая строка идентификатора сборки.
Листинг 7. Отображение выбранных разделов файла ELF.
$ readelf -n / usr / bin / touchОтображение заметок, найденных по смещению файла 0x00000254 с длиной 0x00000020:
Владелец Размер данных Описание
GNU 0x00000010 NT_GNU_ABI_TAG (тег версии ABI)
ОС: Linux, ABI: 2.6.32
Отображение заметок, найденных по смещению файла 0x00000274 с длиной 0x00000024:
Владелец Размер данных Описание
GNU 0x00000014 NT_GNU_BUILD_ID (битовая строка уникального идентификатора сборки)
Идентификатор сборки: ec08d609e9e8e73d4be6134541a472ad0ea34502
Обратите внимание, что в Solaris и FreeBSD утилита elfdump [7] соответствует readelf. По состоянию на 2019 год не было новых выпусков или обновлений с 2003 года.
Номер три - это пакет elfutils [6], который доступен исключительно для Linux. Он предоставляет альтернативные инструменты для GNU Binutils, а также позволяет проверять файлы ELF. Обратите внимание, что все имена утилит, представленных в пакете, начинаются с eu для «elf utils».
И последнее, но не менее важное: упомянем objdump. Этот инструмент похож на readelf, но ориентирован на объектные файлы. Он предоставляет аналогичный диапазон информации о файлах ELF и других форматах объектов.
Листинг 8: Информация о файле, извлеченная objdump
$ objdump -f / bin / touch/ bin / touch: формат файла elf64-x86-64
архитектура: i386: x86-64, флаги 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
начальный адрес 0x00000000004025e3
$
Существует также программный пакет elfkickers [9], который содержит инструменты для чтения содержимого файла ELF, а также для управления им. К сожалению, количество выпусков невелико, поэтому мы просто упоминаем об этом, а не приводим дальнейшие примеры.
Как разработчик, вы можете вместо этого взглянуть на «pax-utils» [10,11]. Этот набор утилит содержит ряд инструментов, которые помогают проверять файлы ELF. Например, dumpelf анализирует файл ELF и возвращает файл заголовка C, содержащий подробную информацию - см. Рисунок 2.
Заключение
Благодаря сочетанию продуманного дизайна и отличной документации формат ELF работает очень хорошо и все еще используется спустя 20 лет. Утилиты, показанные выше, позволяют вам получить представление об ELF-файле и понять, что делает программа. Это первые шаги для анализа программного обеспечения - удачного взлома!
Ссылки и ссылки
- [1] Исполняемый и связываемый формат (ELF), Википедия
- [2] Фуксия ОС
- [3] Сравнение форматов исполняемых файлов, Википедия
- [4] Linux Foundation, указанные спецификации
- [5] Чиро Сантилли: Учебное пособие по ELF Hello World
- [6] пакет Debian elfutils
- [7] elfdump
- [8] Майкл Боелен: 101 файл ELF в Linux: понимание и анализ
- [9] эльфийки
- [10] Утилиты Hardened / PaX
- [одиннадцать] pax-utils, пакет Debian
Благодарности
Автор благодарит Axel Beckert за поддержку в подготовке этой статьи.