mirror of
https://github.com/Linux4Yourself/book.git
synced 2025-02-03 07:17:17 +08:00
258 lines
16 KiB
Markdown
258 lines
16 KiB
Markdown
# Строение 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 файлов (см. таблицы в конце статьи):
|
||
* **Перемещаемый файл** - хранит инструкции (и данные), которые могут быть связаны с другими объектными файлами. Результатом может быть объектный или исполняемый файл. Так же к этому типу относятся объектные файлы статических библиотек.
|
||
* **Разделяемый объектный файл** - также как и первый тип, содержит инструкции и данные, может быть связан с другими перемещаемыми и разделяемыми объектными файлами, в результате чего будет создан новый объектный файл, либо же при запуске программы ОС может динамически связывать его с исполняемым файлом программы , в результате чего будет создан исполняемый образ программы (в посл. случае речь идёт о разделяемых библиотеках).
|
||
* **Исполняемый файл** - содержит полное описание, позволяющее ОС создать образ процесса. В т.ч.: *инструкции*, *данные*, *описания необходимых разделяемых объектных файлов* и др.
|
||
|
||
Для того, чтобы вывод всех команд, приведённых ниже, был краток, прост и понятен, напишите какую-нибудь простейшую программу, в которой нет **ничего** лишнего, что затраднит чтение:
|
||
```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`
|
||
Неинициализированные данные с правами на чтение/запись
|
||
|
||
## Ещё немного о секциях
|
||
Также, некоторую информацию о секциях можно просмотреть, написав скрипт на 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
|
||
```
|