Lx4U-book/docs/additional/LinuxStructure4.md

258 lines
16 KiB
Markdown
Raw Permalink Normal View History

# Строение ELF-файлов
![INTRODUCTION](pic/LinuxStr4/intro.png)
## Теория
ELF - сокращение от "Executable and Lincable Format" - формат исполняемых и связываемых файлов. ELF определяет их структуру. Данная спецификация позволяет UNIX-подобным(/образным) системам правильно интерпретировать содержащиеся в файле машинные команды. Используется во многих операционных системах: GNU/Linux, FreeBSD, Solaris, etc.
Понимание строения ELF файла может редко пригодиться, но, тем не менее, оно будет полезно для понимания процесса разработки программного обеспечения, поиска дыр в безопасности и обнаружения подозрительных программ или файлов.
## Начальное строение
![ELF файл](pic/LinuxStr4/ELF.png)
Для начала создадим директорию, в которой будут расположены тестовые программы, на которых будем "упражняться":
```bash
mkdir ~/LinuxPrograms
cd ~/LinuxPrograms
```
### Типы
Есть несколько типов ELF файлов (см. таблицы в конце статьи):
* **Перемещаемый файл** - хранит инструкции (и данные), которые могут быть связаны с другими объектными файлами. Результатом может быть объектный или исполняемый файл. Так же к этому типу относятся объектные файлы статических библиотек.
2021-06-30 18:54:37 +08:00
* **Разделяемый объектный файл** - также как и первый тип, содержит инструкции и данные, может быть связан с другими перемещаемыми и разделяемыми объектными файлами, в результате чего будет создан новый объектный файл, либо же при запуске программы ОС может динамически связывать его с исполняемым файлом программы , в результате чего будет создан исполняемый образ программы (в посл. случае речь идёт о разделяемых библиотеках).
* **Исполняемый файл** - содержит полное описание, позволяющее ОС создать образ процесса. В т.ч.: *инструкции*, *данные*, *описания необходимых разделяемых объектных файлов* и др.
Для того, чтобы вывод всех команд, приведённых ниже, был краток, прост и понятен, напишите какую-нибудь простейшую программу, в которой нет **ничего** лишнего, что затраднит чтение:
```c
vim simple.c
int main() {
return(0);
}
```
И скомпилируйте её:
```bash
gcc -o simple simple.c
```
Убедитесь в том, что это ELF файл:
```bash
file simple
```
Структура у каждого файла может различаться. Грубо говоря, ELF файл состоит из:
* Заголовка
* Данных
Подробнее:
* **Таблица заголовков программы**: 0 или более сегментов памяти (только в исполняемом файле). Сообщает, как исполняемый файл должен быть помещён в виртуальную память процесса. Это необходимо для образа процесса, исполняемых файлов и общих объектов. Для перемещаемых объектных файлов это не требуется.
* **Таблица заголовков разделов**: 0 или более разделов. Сообщает, как и куда нужно загрузить раздел. Каждая запись раздела в таблице содержит название и размер раздела. Таблица заголовков раздела должна использоваться для файлов, используемых при редактировании ссылок.
* **Данные:** тпблицы заголовка программы или раздела
* **Заголовок ELF** *(54/64 байта для 32/64 бит)*: определяет использование 32/64 бит (смотреть `struct Elf32_Ehdr`/`struct Elf64_Ehdr` в `/usr/include/elf.h`)
* **Заголовок программы:** как создать образ процесса. Используются во время выполнения. Сообщают ядру или компоновщику время выполнения `ld.so`, что загружать в память и как найти информацию о динамической компоновке.
* **Заголовок разделов:** используются во время компоновки или компиляции. Сообщают редактору ссылок `ld`, как разрашать символы и как группировать похожие потоки байтов из разных двоичных объектов ELF.
--* *--
* *Разделы* - самые мелкие неделимые единицы в ELF файле, которые могут быть обработаны. Разделы содержат основную часть информации об объектных файлах для представления связывания. Эти данные включают инструкции, таблицу символов и информацию о перемещении. (просмотр ссылок)
* *Сегменты* - наименьшие отдельные единицы, которые могут быть отображены в памяти с помощью `exec` или компоновщика. (исполняемые)
> Разделы и сегменты не имеют определённого порядка в ELF. Только заголовок имеет фиксированную позицию.
При помощи утилиты `readelf` можно просмотреть основную информацию о файле.
> Эта утилита входит в состав пакета `binutils`, поэтому ничего доустанавливать не надо.
**Основные возможности `readelf`:**
* Просмотр заголовка файла:
```bash
readelf -h simple
```
* Просмотр информации о сегментах и сейкиях:
```bash
readelf -S -W simple
```
* Чтение информации о символах:
```bash
readelf -s -W simple
```
### Заголовок
Введите:
```bash
readelf -h simple
```
![Заголовок](pic/LinuxStr4/header.png)
> Заголовок является обязательным - он служит для того, чтобы данные корректно интерпретировались при линковке и исполнении.
Из вывода утилиты `readelf` следует, что заголовок начинается с т.н. *магического числа* (magic number). Это число содержит информацию о файле. Первые 4 байта определяют, что это ELF: `45 4c 46`.
После типа файла следует поле класса (архитектура, для которой предназначен бинарник).
**Значения:**
* 0 - некорректный класс
* 1 - 32 бит
* 2 - 64 бит
Ниже находится поле данных. Это зависимый от процессора метод кодирования данных.
**Значения:**
* 0 - некорректный тип
* 1 - Little Endian (LSB)
* 2 - Big Endian (MSB)
Разные типы процессоров по разному обрабатывают структуры данных, а эти значения помогают правильно интерпретировать объекты в файле.
> Эффект LSB становится видимым при использовании утилиты hexdump на бинарном файле. Просмотрите заголовок ELF у нашего файла `simple`:
```bash
hexdump -n 16 simple
```
Получите такой вывод:
```
0000000 457f 464c 0102 0001 0000 0000 0000 0000
0000010
```
Пары значений другие из-за интерпретации порядка данных.
Затем следует поле "Версия". На данный момент, используется только версия 01.
Каждая ОС имеет свой способ вызова функций. Что-то похоже, а что-то различается. В поле OS/ABI описываются специфичные для операционной системы или ABI расширения, используемые в файле. В некоторых других структурах ELF файла имеются флаги и поля, значения которых зависят от ОС или ABI, интерпретация этих полей определяется значением данного байта. В таблице ниже представлена таблица значений:
| Значение | Описание |
|:---------|:---------|
| 0 | UNIX System V |
| 1 | HP-UX |
| 2 | NetBSD |
| 3 | GNU ELF (GNU/Linux) |
| 6 | Solaris |
| 7 | AIX |
| 8 | IRIX |
| 9 | FreeBSD |
| 10 | Tru64 UNIX |
| 11 | Modesto |
| 12 | OpenBSD |
| 13 | OpenVMS |
| 15 | Amiga Research OS |
| 18 | OpenVOS |
При необходимости, так же может быть указана версия ABI.
В поле "Машина" указывается архитектура аппаратной платформы, для которой предназначен файл. В таблице ниже представлены некоторые из них:
| Значение | Описание |
|:---------|:---------|
| 0 | Не определено |
| 3 | Intel 80386 |
| 20 | PowerPC |
| 21 | PowerPC (64 бит) |
| 62 | x86_64 |
В поле "Тип" указывается предназначение файла. В таблице ниже они приведены:
| Значение | Описание | Значение поля |
|:---------|:---------|:--------------|
| 0 | Некорректный тип | --- |
| 1 | Перемещаемый файл (файл до линковки) | REL |
| 2 | Исполняемый файл | EXEC |
| 3 | Разделяемый объектный файл (библиотека) | DYN |
| 4 | Core file | CORE |
(смотрите блок "Типы" этой статьи, чтобы узнать больше информации).
## Продолжение
Помимо заголовка, есть данные. Оно, в свою очередь, подразделяется ещё на несколько:
* программные заголовки (сегменты)
* заголовки секций/секции
* данные
О первых двух пунктах было уже говорено в пункте "Типы".
> **Для начала**. Файл ELF имеет два различных «вида». Один из них предназначен для линкера и разрешает исполнение кода (сегменты). Другой предназначен для команд и данных (секции). В зависимости от цели, используется соответствующий тип заголовка. Начнём с заголовка программы, который находится в исполняемых файлах ELF.
## Продолжение про заголовки программы
ELF файл состоит из нуля и более сегментов. И описывает, как создать процесс, образ памяти для исполнения в рантайме. Когда ядро видит эти сегменты, оно размещает их в виртуальном адресном пространстве, используя системный вызов `mmap`...
> **Смотрите также:**
> `man 2 mmap`
... Т.е., конвертирует заранее подготовленные инструкции в образ памяти. Для разделяемых библиотек (*shared libs*) процесс схож.
И примеры заголовков:
### `GNU_EH_FRAME`
Сортированная очередь, используемая компилятором GCC. В неё хранятся обработчики исключений. Они используются для того, чтобы корректно обработать ситуацию, если что-то пошло не так.
### `GNU_STACK`
Используется для сохранения информации о стеке. Он не должен быть исполняемым, так как это может быть очень небезопасным.
Если сегмент `GNU_STACK` отсутствует, то используется исполняемый стек. Для того, чтобы показать детали устр-ва стека, используйте утилиты `scanelf` и `execstack`.
> **Смотрите также:**
> `man scanelf`
> `man execstack`
## Секции
Секции появляются в файле после преобразования компилятором GNU C кода С в ассемблер (и ассемблер GNU создаёт объекты).
![Sections](pic/LinuxStr4/sections.png)
### `.text`
Содержит исполняемый код, упакованный в сегмент с правами на чтение и исполнение (но не редактирование).
### `.data`
Инициализированные данные с правами на чтение и запись.
### `.bss`
Неинициализированные данные с правами на чтение/запись
## Ещё немного о секциях
2021-06-30 18:54:37 +08:00
Также, некоторую информацию о секциях можно просмотреть, написав скрипт на Python с применением библиотеки `lief`:
```python
#!/bin/python
import lief
binary = lief.parse("simple")
header = binary.header
print("Entry point: %08x" % header.entrypoint)
print("Architecture: ", header.machine_type)
for section in binary.sections:
print("Section %s - size: %s bytes" % (section.name, section.size))
```
Запуск:
```
chmod +x elf.py
./elf.py
```
> Название `elf.py` замените на нужное.
Вывод такой:
![Lief](pic/LinuxStr4/lief.png)
## Смотрите также:
```bash
man readelf
man objdump
man mmap
man hexdump
man file
man gcc
```