Elf файл что это
В современных POSIX-системах основным форматом исполняемых файлов, объектных файлов, динамических библиотек является формат ELF. Этот формат используется и на 32-битных (Elf32), и на 64-битных (Elf64) системах и для машин с порядком байт Little-endian, и для машин с порядком байт Big-endian. Далее приведено краткое описание формата Elf32. Формат Elf64 отличается размерами полей, содержащих виртуальные адреса, размеры и смещения в файле.
В описании формата будут использоваться типы данных [u]intN_t, где u является признаком беззнаковости, а N определяет размер типа, например, uint16_t. Эти типы определены в стандартном заголовочном файле stdint.h.
Все типы данных и константы описаны в заголовочном файле elf.h.
Ресурсы для дальнейшего изучения
Если вы хотите больше знать про ELF и обратную разработку, вы можете посмотреть работу, которую мы выполняем в Linux Security Expert. Как часть учебной программы, мы имеем модуль обратной разработки с практическими лабораторными работами.
Для тех из вас, кто любит читать, хороший и глубокий документ: ELF Format и документ за авторством Брайана Рейтера (Brian Raiter), также известного как ELFkickers. Для тех, кто любит разбираться в исходниках, посмотрите на документированный заголовок ELF от Apple.
Совет:
если вы хотите стать лучше в анализе файлов, начните использовать популярные инструменты анализа, которые доступны в настоящее время.
Есть в мире вещи, которые мы принимаем как нечто само собой разумеющееся, хотя они являются истинными шедеврами. Одними из таких вещей являются утилиты Linux, такие, как ls и ps. Хотя они обычно воспринимаются как простые, это оказывается далеко не так, если мы заглянем внутрь. И таким же оказывается ELF, Executable and Linkable Format. Формат файлов, который используется повсеместно, но мало кто его понимает. Это краткое руководство поможет вам достичь понимания.
Прочтя это руководство, вы изучите:
- Зачем нужен формат ELF и для каких типов файлов он используется
- Структуру файла ELF и детали его формата
- Как читать и анализировать бинарное содержимое файла ELF
- Какие инструменты используются для анализа бинарных файлов
Что такое ABI?
ABI — это Бинарный Интерфейс Приложения (Application Binary Interface) и определяет, низкоуровневый интерфейс между операционной системой и исполняемым кодом.
Секции ELF
Заголовки секции
Заголовки секции определяют все секции файла. Как уже было сказано, эта информация используется для линковки и релокации.
Секции появляются в ELF-файле после того, как компилятор GNU C преобразует код С в ассемблер, и ассемблер GNU создаёт объекты.
Как показано на рисунке вверху, сегмент может иметь 0 или более секций. Для исполняемых файлов существует четыре главных секций: .text, .data, .rodata, и .bss. Каждая из этих секций загружается с различными правами доступа, которые можно посмотреть с помощью readelf -S.
Содержит исполняемый код. Он будет упакован в сегмент с правами на чтение и на исполнение. Он загружается один раз, и его содержание не изменяется. Это можно увидеть с помощью утилиты objdump.
Инициализированные данные, с правами на чтение и запись.
.rodata
Инициализированные данные, с правами только на чтение. (=A).
Неинициализированные данные, с правами на чтение/запись. (=WA)
Команды для просмотра секций и заголовков.
- dumpelf
- elfls -p /bin/ps
- eu-readelf –section-headers /bin/ps
- readelf -S /bin/ps
- objdump -h /bin/ps
Группы секций
Некоторые секции могут быть сгруппированы, как если бы они формировали единое целое. Новые линкеры поддерживают такую функциональность. Но пока такое встречается не часто.
Хотя это может показаться не слишком интересным, большие преимущества даёт знание инструментов анализа ELF-файлов. По этой причине, обзор этих инструментов и их назначения приведён в конце статьи.
Radare2
Спасибо dukebarman за дополнение по использованию Radare2 для вывода информации аналогично checksec:
Я вычислю тебя по… RPATH
Эльфы могут сохранять пути для поиска динамически подключаемых библиотек. Чтобы не задавать системную переменную LD_LIBRARY_PATH перед запуском приложения, можно просто «вшить» этот путь в ELF-файл.
Для этого используется запись в секции .dynamic с типом DT_RPATH или DT_RUNPATH (см. главу Directories Searched by the Runtime Linker в документации).
И будь осторожен, юный разработчик, не «спали» свою директорию проекта!
Таблица заголовков секций
Информация, хранящаяся в ELF-файле, организована в секции. Каждая секция имеет свое уникальное имя. Некоторые секции хранят служебную информацию ELF-файла (например, таблицы строк), другие секции хранят отладочную информацию, третьи секции хранят код или данные программы.
Таблица заголовков секций представляет собой массив структур Elf32_Shdr. Количество элементов массива определяется полем e_shnum заголовка ELF-файла. Массив находится по смещению, хранящемуся в поле e_shoff. Элемент массива 0 зарезервирован и не используется для описания секций. Таким образом, описания секций находятся в элементах массива с индексами от 1 и до e_shnum - 1.
Структура Elf32_Shdr определена следующим образом:
Поле sh_name хранит индекс имени секции. Индекс имени - это смещение в данных секции, индекс которой задается в поле e_shstrndx заголовка ELF-файла. По этому смещению размещается строка, завершающаяся нулевым байтом, являющаяся именем секции.
Таким образом, чтобы получить имя секции необходимо выполнить следующие действия:
- Загрузить заголовок секции, индекс которой хранится в поле e_shstrndx заголовка ELF-файла.
- Загрузить тело соответствующей секции.
- По смещению, заданному в поле sh_name относительно начала области памяти, в которую загружена секция, находится требуемая строка имени секции.
Поле sh_type хранит тип секции. Возможные значения поля перечислены ниже.
Значение | Симв. имя | Описание |
---|---|---|
0 | SHT_NULL | Пустой заголовок секции. Значения всех прочих полей заголовка секции неопределены. |
1 | SHT_PROGBITS | Секции программы (код или данные или что-либо еще). |
2 | SHT_SYMTAB | Таблица символов (для объектных файлов или динамических библиотек). |
3 | SHT_STRTAB | Таблица строк. |
4 | SHT_RELA | Записи о перемещаемых адресах (relocations). |
5 | SHT_HASH | Хеш-таблица имен для динамического связывания. |
6 | SHT_DYNAMIC | Информация для динамического связывания. |
7 | SHT_NOTE | Произвольная дополнительная информация. |
8 | SHT_NOBITS | Секция не занимает место в файле, но занимает место в адресном пространстве процесса. |
9 | SHT_REL | Записи о перемещаемых адресах. |
Поле sh_flags хранит битовые флаги, описывающие дополнительные атрибуты.
Значение | Симв. константа | Описание |
---|---|---|
1 | SHF_WRITE | Содержимое секции должно быть доступно на запись в адресном пространстве процесса. |
2 | SHF_ALLOC | Для содержимого секции выделяется память в адресном пространстве процесса. |
4 | SHF_EXECINSTR | Секция содержит инструкции процессора. |
Флаги могут комбинироваться с помощью операции побитового или.
Поле sh_addr хранит адрес в виртуальном адресном пространстве процесса в случае, если секция загружается в виртуальное адресное пространство процесса.
Поле sh_offset хранит смещение от начала файла, по которому размещаются данные секции.
Поле sh_size хранит размер секции в байтах.
Поле sh_link хранит индекс другой секции (в некоторых специальных случаях).
Поле sh_info хранит дополнительную информацию о секции.
Поле sh_addralign хранит требование по выравниванию адреса начала секции в памяти. Значения 0 или 1 означают отсутствие требования по выравниванию. В противном случае значением поля должна быть степень 2. Например, секции, загружаемые в виртуальное адресное пространство процесса, как правило, выровнены по размеру страницы процессора (4096).
Поле sh_entsize хранит размер одной записи, если секция хранит таблицу из записей фиксированного размера.
Как появляется RPATH?
Основная причина появления RPATH-записи в эльфе — опция -rpath линковщика для поиска динамической библиотеки. Примерно так:
Такая команда создаст в секции .dynamic RPATH-запись со значением /run/media/pablo/disk1/projects/cheat_sheets/ELF/lib/ .
Эльф без метаинформации
После того как приложение отлажено и выпускается в дикий мир, имеет смысл удалить метаинформацию:
- отладочные секции — бесполезны в большинстве случаев;
- имена переменных и функций — совершенно ни на что не влияют для конечного пользователя (чуть усложняет реверс);
- таблица секций — совершенно не нужна для запуска приложения (её отсутсвие чуть усложнит реверс).
Класс
После объявления типа ELF, следует поле класса. Это значение означает архитектуру, для которой предназначен файл. Оно может равняться 01 (32-битная архитектура) или 02 (64-битная). Здесь мы видим 02, что переводится командой readelf как файл ELF64, то есть, другими словами, этот файл использует 64-битную архитектуру. Это неудивительно, в моей машине установлен современный процессор.
Версия
Затем следует ещё одно магической значение «01», представляющее собой номер версии. В настоящее время имеется только версия 01, поэтому это число не означает ничего интересного.
Сегмент PT_NOTE
В сегменте ELF-файла с типом PT_NOTE хранится дополнительная информация о состоянии выполнения программы. Сегмент сам содержит произвольное количество записей произвольного размера. Сегмент всегда имеет размер, кратный 4 байтам (для Elf32), и каждая запись в сегменте начинается со смещения, кратного 4 байтам. В начале каждой записи находится заголовок записи, описываемый следующей структурой:
Поле n_namesz содержит длину названия записи. Название должно быть непустой строкой, завершающейся байтом 0. Сама строка названия записи начинается сразу же после структуры Elf32_Nhdr.
Поле n_descsz содержит длину информационной части записи. Длина должна быть кратна 4 байтам. Информационная часть записи начинается сразу после названия записи с учетом выравнивания по границе 4 байт.
Поле n_type содержит тип записи. Возможные типы записи зависят от типа файла (объектный, core) и рассматриваются в соответствующих разделах.
Ниже приведен пример сегмента PT_NOTE.
См. от начала | Значение | Комментарий | |||
---|---|---|---|---|---|
0x0000 | 0x07 | 0x00 | 0x00 | 0x00 | n_namesz == 7 |
0x0004 | 0x08 | 0x00 | 0x00 | 0x00 | n_descsz == 8 |
0x0008 | 0x01 | 0x00 | 0x00 | 0x00 | n_type == 1 |
0x000c | 'G' | 'N' | 'U' | 'D' | имя записи: "GNUDBG" |
0x0010 | 'B' | 'G' | '\0' | 0x00 | |
0x0014 | 0x01 | 0x02 | 0x03 | 0x04 | информация: 0x04030201 0x08070605 |
0x0018 | 0x05 | 0x06 | 0x07 | 0x08 |
readelf
Секции ELF
Заголовки секции
Заголовки секции определяют все секции файла. Как уже было сказано, эта информация используется для линковки и релокации.
Секции появляются в ELF-файле после того, как компилятор GNU C преобразует код С в ассемблер, и ассемблер GNU создаёт объекты.
Как показано на рисунке вверху, сегмент может иметь 0 или более секций. Для исполняемых файлов существует четыре главных секций: .text, .data, .rodata, и .bss. Каждая из этих секций загружается с различными правами доступа, которые можно посмотреть с помощью readelf -S.
Содержит исполняемый код. Он будет упакован в сегмент с правами на чтение и на исполнение. Он загружается один раз, и его содержание не изменяется. Это можно увидеть с помощью утилиты objdump.
Инициализированные данные, с правами на чтение и запись.
.rodata
Инициализированные данные, с правами только на чтение. (=A).
Неинициализированные данные, с правами на чтение/запись. (=WA)
Команды для просмотра секций и заголовков.
- dumpelf
- elfls -p /bin/ps
- eu-readelf –section-headers /bin/ps
- readelf -S /bin/ps
- objdump -h /bin/ps
Группы секций
Некоторые секции могут быть сгруппированы, как если бы они формировали единое целое. Новые линкеры поддерживают такую функциональность. Но пока такое встречается не часто.
Хотя это может показаться не слишком интересным, большие преимущества даёт знание инструментов анализа ELF-файлов. По этой причине, обзор этих инструментов и их назначения приведён в конце статьи.
Изменение и удаление RPATH
Машина
Также в заголовке указывается ожидаемый тип машины (AMD64).
Поле типа указывает, для чего предназначен файл. Вот несколько часто встречающихся типов файлов.
CORE (значение 4)
DYN (Shared object file), библиотека (значение 3)
EXEC (Executable file), исполняемый файл (значение 2)
REL (Relocatable file), файл до линковки (значение 1)
Заключение
Файлы ELF предназначены для исполнения и линковки. В зависимости от назначения, они содержат необходимые сегменты и секции. Ядро ОС просматривает сегменты и отображает их в память (используя mmap). Секции просматриваются линкером, который создаёт исполняемый файл или разделяемый объект.
Файлы ELF очень гибкие и поддерживаются различные типы CPU, машинные архитектуры, и операционные системы. Также он расширяемый, каждый файл сконструирован по-разному, в зависимости от требуемых частей. Путём использования правильных инструментов, вы сможете разобраться с назначением файла, и изучать содержимое бинарных файлов. Можно просмотреть функции и строки, содержащиеся в файле. Хорошее начало для тех, кто исследует вредоносные программы, или понять, почему процесс ведёт себя (или не ведёт) определённым образом.
sstrip
Для тщательного удаления символьной информации (в том числе ненужных нулевых байтов в конце файла) можно воспользоваться утилитой sstrip из набора ELFkickers. Для удаления всей символьной информации достаточно выполнить команду:
C использованием библиотеки LIEF также можно сделать быстрый strip (удаляется таблица символов — секция .symtab ):
Данные
Далее идёт поле «данные», имеющее два варианта: 01 — LSB (Least Significant Bit), также известное как little-endian, либо 02 — MSB (Most Significant Bit, big-endian). Эти значения помогают интерпретировать остальные объекты в файле. Это важно, так как разные типы процессоров по разному обрабатывают структуры данных. В нашем случае используется LSB, так как процессор имеет архитектуру AMD64.
Эффект LSB становится видимым при использовании утилиты hexdump на бинарном файле. Давайте посмотрим заголовок ELF для /bin/ps.
Мы видим, что пары значений другие, из-за интерпретации порядка данных.
GNU_STACK
Этот заголовок используется для сохранения информации о стеке. Интересная особенность состоит в том, что стек не должен быть исполняемым, так как это может повлечь за собой уязвимости безопасности.
Если сегмент GNU_STACK отсутствует, используется исполняемый стек. Утилиты scanelf и execstack показывают детали устройства стека.
Команды для просмотра программного заголовка:
- dumpelf (pax-utils)
- elfls -S /bin/ps
- eu-readelf –program-headers /bin/ps
Таблица заголовков программы
Таблица заголовков программы содержит информацию, необходимую для загрузки программы на выполнение.
Таблица заголовков программы представляет собой массив структур Elf32_Phdr. Массив размещается по смещению от начала файла, которое хранится в поле e_phoff заголовка ELF-файла, а количество элементов массива хранится в поле e_phnum заголовка ELF-файла.
Структура Elf32_Phdr определена следующим образом.
Поле p_type хранит тип заголовка. Некоторые возможные значения типа заголовка приведены в таблице ниже.
Значение | Симв. константа | Описание |
---|---|---|
0 | PT_NULL | Обозначает не используемую запись |
1 | PT_LOAD | Сегмент программы, загружаемый в память |
2 | PT_DYNAMIC | Информация для динамического связывания |
3 | PT_INTERP | Загрузчик программ |
4 | PT_NOTE | Дополнительная информация |
6 | PT_PHDR | Информация о самой таблице заголовков программы |
7 | PT_TLS | Thread-local storage |
Поле p_offset хранит смещение от начала файла, по которому располагается данный сегмент.
Поле p_vaddr хранит виртуальный адрес начала сегмента в памяти.
Значение поля p_paddr должно быть равно 0.
Поле p_filesz хранит размер сегмента в файле (может быть 0).
Поле p_memsz хранит размер сегмента в памяти (может быть 0).
Поле p_flags хранит флаги доступа к сегменту в памяти (могут объединяться с помощью побитового "или").
Значение | Симв. константа | Описание |
---|---|---|
1 | PT_X | Сегмент доступен на выполнение |
2 | PT_W | Сегмент доступен на запись |
4 | PT_R | Сегмент доступен на чтение |
readelf
Утилиту readelf можно считать стандартом де-факто для получения сведений об ELF-файле.
Для удобства чтения адреса приведены к 32-битному формату:
Для удобства чтения адреса приведены к 32-битному формату:
Вывод сокращён для удобства чтения:
Опция -W нужна для увеличения ширины консольного вывода (по умолчанию, 80 символов).
Прочитать заголовок и информацию о секциях можно с использованием кода на Python и библиотеки LIEF (предоставляет API не только для Python):
Прежде, чем начать
Этот пост содержит множество команд. Лучше запускать их на тестовой машине. Скопируйте существующие двоичные файлы, перед тем, как запускать на них эти команды. Также мы напишем маленькую программу на С, которую вы можете скомпилировать. В конечном итоге, практика — лучший способ чему-либо научиться.
Анатомия ELF-файла
Распространённым заблуждением является то, что файлы ELF предназначены только для бинарных или исполняемых файлов. Мы уже сказали, что они могут быть использованы для частей исполняемых файлов (объектного кода). Другим примером являются файлы библиотек и дампы ядра (core-файлы и a.out файлы). Спецификация ELF также используется в Linux для ядра и модулей ядра.
Что такое ABI?
ABI — это Бинарный Интерфейс Приложения (Application Binary Interface) и определяет, низкоуровневый интерфейс между операционной системой и исполняемым кодом.
Инструменты анализа двоичных файлов
Если вы хотите анализировать ELF-файлы, определённо будет полезно сначала посмотреть на существующие инструменты. Существуют тулкиты для обратной разработки бинарников и исполняемого кода. Если вы новичок в анализе ELF-файлов, начните со статического анализа. Статический анализ подразумевает, что мы исследуем файлы без их запуска. Когда вы начнёте лучше понимать их работу, переходите к динамическому анализу. Запускайте примеры и смотрите на их реальное поведение.
Заголовки программы
Файл ELF состоит из нуля или более сегментов, и описывает, как создать процесс, образ памяти для исполнения в рантайме. Когда ядро видит эти сегменты, оно размещает их в виртуальном адресном пространстве, используя системный вызов mmap(2). Другими словами, конвертирует заранее подготовленные инструкции в образ в памяти. Если ELF-файл является обычным бинарником, он требует эти программные заголовки, иначе он просто не будет работать. Эти заголовки используются, вместе с соответствующими структурами данных, для формирования процесса. Для разделяемых библиотек (shared libraries) процесс похож.
Программный заголовок в бинарном ELF-файле
Мы видим в этом примере 9 программных заголовков. Сначала трудно понять, что они означают. Давайте погрузимся в подробности.
«Сырой код» из эльфа (binary from ELF)
Бывают ситуации, когда «эльфийские одёжи» в виде ELF-структуры не нужны, а нужен только «голый» исполняемый код приложения.
Статические и динамические бинарные файлы
Когда мы имеем дело с бинарными файлами ELF, полезно будет знать, как линкуются эти два типа файлов. Они могут быть статическими и динамическими, и это относится к библиотекам, которые они используют. Если бинарник «динамический», это означает, что он использует внешние библиотеки, содержащие какие-либо общие функции, типа открытия файла или создания сетевого сокета. Статические бинарники, напротив, включают в себя все необходимые библиотеки.
Если вы хотите проверить, является ли файл статическим или динамическим, используйте команду file. Она покажет что-то вроде этого:
Чтобы определить, какие внешние библиотеки использованы, просто используйте ldd на том же бинарнике:
Совет: Чтобы посмотреть дальнейшие зависимости, лучше использовать утилиту lddtree.
Популярные инструменты
Radare2
Тулкит Radare2 создан Серджи Альваресом (Sergi Alvarez). Число 2 подразумевает, что код был полностью переписан по сравнению с первой версией. Сейчас он используется многими исследователями, для изучения работы кода.
Программные пакеты
Большинство Linux-систем имеют установленный пакет binutils. Другие пакеты могут помочь вам увидеть больше информации. Правильный тулкит упростит вашу работу, особенно если вы занимаетесь анализом ELF-файлов. Я собрал здесь список пакетов и утилит для анализа ELF-файлов.
elfutils
/usr/bin/eu-addr2line
/usr/bin/eu-ar – альтернатива ar, для создания и обработки архивных файлов
/usr/bin/eu-elfcmp
/usr/bin/eu-elflint – проверка на соответствие спецификациям gABI и psABI
/usr/bin/eu-findtextrel – поиск релокаций текста
/usr/bin/eu-ld – комбинирует объектный и архивные файлы
/usr/bin/eu-make-debug-archive
/usr/bin/eu-nm – показывает символы объектного и исполняемого файлов
/usr/bin/eu-objdump – показывает информацию из объектного файла
/usr/bin/eu-ranlib – создаёт индекс архивных файлов
/usr/bin/eu-readelf – показывает ELF-файл в читаемой форме
/usr/bin/eu-size – показывает размер каждой секции (text, data, bss, etc)
/usr/bin/eu-stack – показывает стек текущего процесса или дампа ядра
/usr/bin/eu-strings – показывает текстовые строки (как утилита strings)
/usr/bin/eu-strip – удаляет таблицу символов из файла ELF
/usr/bin/eu-unstrip – добавляет символы и отладочную информацию в бинарник
Примечание: пакет elfutils будет хорошим началом, он содержит большинство утилит для анализа
elfkickers
/usr/bin/ebfc – компилятор языка Brainfuck
/usr/bin/elfls – показывает программные заголовки и заголовки секций с флагами
/usr/bin/elftoc – преобразует бинарник в программу на С
/usr/bin/infect – утилита, инжектирующая дроппер, создаёт файл setuid в /tmp
/usr/bin/objres – создаёт объект из обычных или бинарных данных
/usr/bin/rebind – изменяет связывание и видимость символов в ELF-файлах
/usr/bin/sstrip – удаляет ненужные компоненты из ELF-файла
Примечание: автор пакета ELFKickers сфокусирован на манипулировании ELF-файлами, что позволяет вам получить больше информации при работе с «неправильными» ELF-бинарниками
pax-utils
/usr/bin/dumpelf – дамп внутренней структуры ELF
/usr/bin/lddtree – как ldd, с установкой уровня показываемых зависимостей
/usr/bin/pspax – выводит ELF/PaX информацию о запущенных процессах
/usr/bin/scanelf – широкий диапазон информации, включая подробности PaX
/usr/bin/scanmacho – показывает подробности бинарников Mach-O (Mac OS X)
/usr/bin/symtree – показывает символы в виде дерева
Примечание: некоторые утилиты в этом пакете могут рекурсивно сканировать директории, и идеальны для анализа всего содержимого директории. Фокус сделан на инструментах для исследования подробностей PaX. Помимо поддержки ELF, можно извлекать информацию из Mach-O-бинарников.
prelink
/usr/bin/execstack – можно посмотреть или изменить информацию о том, является ли стек исполняемым
/usr/bin/prelink – релоцирует вызовы в ELF файлах, для ускорения процесса
Часто задаваемые вопросы
Зачем изучать ELF в подробностях?
Перед тем, как погрузиться в технические детали, будет не лишним объяснить, почему понимание формата ELF полезно. Во-первых, это позволяет изучить внутреннюю работу операционной системы. Когда что-то пошло не так, эти знания помогут лучше понять, что именно случилось, и по какой причине. Также возможность изучения ELF-файлов может быть ценна для поиска дыр в безопасности и обнаружения подозрительных файлов. И наконец, для лучшего понимания процесса разработки. Даже если вы программируете на высокоуровневом языке типа Go, вы всё равно будет лучше знать, что происходит за сценой.
Итак, зачем изучать ELF?
- Для общего понимания работы операционной системы
- Для разработки ПО
- Цифровая криминалистика и реагирование на инциденты (DFIR)
- Исследование вредоносных программ (анализ бинарных файлов)
Прежде, чем начать
Этот пост содержит множество команд. Лучше запускать их на тестовой машине. Скопируйте существующие двоичные файлы, перед тем, как запускать на них эти команды. Также мы напишем маленькую программу на С, которую вы можете скомпилировать. В конечном итоге, практика — лучший способ чему-либо научиться.
Удаление символьной информации
Символьная информация — это имена объектов и функций. Без неё реверс приложения немного усложняется.
Структура
В силу расширяемости ELF-файлов, структура может различаться для разных файлов. ELF-файл состоит из:
Структура
В силу расширяемости ELF-файлов, структура может различаться для разных файлов. ELF-файл состоит из:
GNU_STACK
Этот заголовок используется для сохранения информации о стеке. Интересная особенность состоит в том, что стек не должен быть исполняемым, так как это может повлечь за собой уязвимости безопасности.
Если сегмент GNU_STACK отсутствует, используется исполняемый стек. Утилиты scanelf и execstack показывают детали устройства стека.
Команды для просмотра программного заголовка:
- dumpelf (pax-utils)
- elfls -S /bin/ps
- eu-readelf –program-headers /bin/ps
core-файлы
core-файл - это ELF-файл, у которого значение поля e_type заголовка равно ET_CORE (4). В core-файле таблица заголовков секций пуста, а таблица заголовков программ состоит из записей типа PT_LOAD, хранящих содержимое адресного пространства процесса на момент завершения работы процесса, и записи типа PT_NOTE, хранящей состояние процесса на момент завершения работы процесса.
Для тестового файла sample.core содержимое таблицы заголовков программ имеет следующий вид.
p_type | p_flags | p_offset | p_vaddr | p_filesz | p_memsz | p_align |
---|---|---|---|---|---|---|
PT_NOTE | --- | 0x00000234 | 0x00000000 | 1216 | 0 | 0 |
PT_LOAD | r-x | 0x00001000 | 0x08048000 | 4096 | 8192 | 4096 |
PT_LOAD | rw- | 0x00002000 | 0x0804a000 | 4096 | 4096 | 4096 |
PT_LOAD | rw- | 0x00003000 | 0x08542000 | 135168 | 135168 | 4096 |
PT_LOAD | r-x | 0x00024000 | 0x4f2d0000 | 4096 | 126976 | 4096 |
PT_LOAD | r-- | 0x00025000 | 0x4f2ef000 | 4096 | 4096 | 4096 |
PT_LOAD | rw- | 0x00026000 | 0x4f2f0000 | 4096 | 4096 | 4096 |
PT_LOAD | r-x | 0x00027000 | 0x4f2f7000 | 4096 | 1748992 | 4096 |
PT_LOAD | --- | 0x00028000 | 0x4f4a2000 | 0 | 4096 | 4096 |
PT_LOAD | r-- | 0x00028000 | 0x4f4a3000 | 8192 | 8192 | 4096 |
PT_LOAD | rw- | 0x0002a000 | 0x4f4a5000 | 4096 | 4096 | 4096 |
PT_LOAD | rw- | 0x0002b000 | 0x4f4a6000 | 12288 | 12288 | 4096 |
PT_LOAD | rw- | 0x0002e000 | 0xb778a000 | 4096 | 4096 | 4096 |
PT_LOAD | rw- | 0x0002f000 | 0xb77a1000 | 8192 | 8192 | 4096 |
PT_LOAD | r-x | 0x00031000 | 0xb77a3000 | 4096 | 4096 | 4096 |
PT_LOAD | rw- | 0x00032000 | 0xbfe6d000 | 139264 | 139264 | 4096 |
Первый сегмент (сегмент PT_NOTE) содержит информацию о состоянии процесса на момент создания core-файла.
Для core-файлов возможны следующие значения поля n_type (в таблице ниже перечислены не все возможные значения).
Значение | Симв. константа | Описание |
---|---|---|
1 | NT_PRSTATUS | Информационная часть записи имеет тип prstatus_t |
2 | NT_FPREGSET | Информационная часть записи имеет тип prfpregset_t |
3 | NT_PRPSINFO | Информационная часть записи имеет тип prpsinfo_t |
Типы структур, используемые в информационных частях записей, определены в заголовочном файле
Структура prstatus_t определена следующим образом:
Тип elf_gregset_t определен следующим образом:
то есть представляет собой массив, в котором каждый регистр общего назначения находится по определенному индексу.
индекс | регистр |
---|---|
0 | ebx |
1 | ecx |
2 | edx |
3 | esi |
4 | edi |
5 | ebp |
6 | eax |
7 | ds |
8 | es |
9 | fs |
10 | gs |
11 | orig_eax |
12 | eip |
13 | cs |
14 | eflags |
15 | sp |
16 | ss |
Данные файла
Помимо заголовка, файлы ELF состоят из трёх частей.
- Программные заголовки или сегменты
- Заголовки секций или секции
- Данные
От исходника к процессу
Какую бы операционную систему мы не использовали, необходимо каким-то образом транслировать функции исходного кода на язык CPU — машинный код. Функции могут быть самыми базовыми, например, открыть файл на диске или вывести что-то на экран. Вместо того, чтобы напрямую использовать язык CPU, мы используем язык программирования, имеющий стандартные функции. Компилятор затем транслирует эти функции в объектный код. Этот объектный код затем линкуется в полную программу, путём использования линкера. Результатом является двоичный файл, который может быть выполнен на конкретной платформе и конкретном типе CPU.
Смотрим полный заголовок
Хотя некоторые поля могут быть просмотрены через readelf, их на самом деле больше. Например, можно узнать, для какого процессора предназначен файл. Используем hexdump, чтобы увидеть полный заголовок ELF и все значения.
(вывод hexdump -C -n 64 /bin/ps)
Выделенное поле определяет тип машины. Значение 3e — это десятичное 62, что соответствует AMD64. Чтобы получить представление обо всех типах файлов, посмотрите этот заголовочный файл.
Хотя вы можете делать всё это в шестнадцатиричном дампе, имеет смысл использовать инструмент, который сделает работу за вас. Утилита dumpelf может быть полезна. Она показывает форматированный вывод, соответствующий заголовку ELF. Хорошо будет изучить, какие поля используются, и каковы их типичные значения.
Теперь, кгда мы объяснили значения этих полей, время посмотреть на то, какая реальная магия за ними стоит, и перейти к следующим заголовкам!
Формат отладочной информации stabs
При компиляции в исполняемый файл может добавляться отладочная информация, которую отладчик использует для отображения хода исполнения программы в терминах языка высокого уровня. Существует несколько форматов отладочной информации (STABS, DWARF), здесь описывается формат STABS как самый простой.
Для компиляции программы с добавлением отладочной информации в формате STABS используется опция gcc -gstabs, например
В формате STABS отладочная информация хранится в секциях .stab и .stabstr ELF-файла.
Секция .stab содержит массив структур:
Секция .stabstr хранит символьные строки, завершающиеся байтом 0, которые используются в записях в секции .stab.
Поле n_type хранит тип записи. Возможные типы записей можно найти в stab.h, нас будут интересовать только некоторые из них.
Каждой единице компиляции соответствуют две записи N_SO. Первая запись находится в начале описания единицы компиляции и содержит ее имя в поле n_strx и адрес первой инструкции в поле n_value. Вторая запись находится в конце описания единицы компиляции и содержит нулевое значение (пустая строка) в поле n_strx и адрес непосредственно следующий за концом кода данной единицы компиляции в поле n_value.
Записи N_SLINE упорядочены по смещениям внутри функции.
Первая запись в таблице .stab является служебной и имеет тип N_UNDF. Индекс этой записи полагается равным 0.
Формат STABS не позволяет однозначно установить адрес, на котором заканчивается тело функции. Можно использовать следующую эвристику: функция заканчивается либо с началом следующей функции, тогда адрес конца функции - это адрес начала следующей функции, либо с концом единицы компиляции, тогда адрес конца функции - это адрес, хранящийся в поле n_value записи N_SO в конце единицы компиляции.
Как обычно, предполагается, что все диапазоны адресов и значений включают в себя нижнее значение, но не включают в себя верхнее значение, то есть имеют вид [low;high).
Имя файла, в котором располагается функция, может находиться в записи N_SOL после записи N_FUN самой функции, но до первой записи N_SLINE. Следует полагать, что имя файла, в котором определена функция совпадает с именем файла, установленному до первой записи N_SLINE в данной функции.
Есть в мире вещи, которые мы принимаем как нечто само собой разумеющееся, хотя они являются истинными шедеврами. Одними из таких вещей являются утилиты Linux, такие, как ls и ps. Хотя они обычно воспринимаются как простые, это оказывается далеко не так, если мы заглянем внутрь. И таким же оказывается ELF, Executable and Linkable Format. Формат файлов, который используется повсеместно, но мало кто его понимает. Это краткое руководство поможет вам достичь понимания.
Прочтя это руководство, вы изучите:
- Зачем нужен формат ELF и для каких типов файлов он используется
- Структуру файла ELF и детали его формата
- Как читать и анализировать бинарное содержимое файла ELF
- Какие инструменты используются для анализа бинарных файлов
chrpath, PatchELF
Для изменения RPATH под Linux можно воспользоваться утилитами chrpath (доступна в большинстве дистрибутивов) или PatchELF.
Данные файла
Помимо заголовка, файлы ELF состоят из трёх частей.
- Программные заголовки или сегменты
- Заголовки секций или секции
- Данные
Как я могу увидеть тип файла?
Используйте команду file для первой стадии анализа. Эта команда способна показать подробности, извлечённые из «магических» чисел и заголовков.
GNU_EH_FRAME
Это сортированная очередь, используемая компилятором GCC. В ней хранятся обработчики исключений. Если что-то пошло не так, они используются для того, чтобы корректно обработать ситуацию.
objcopy
Использование objcopy вероятно знакомо тем, кто пишет прошивки:
- -S — для удаления символьной информации;
- -g — для удаления отладочной информации.
Никакой магии. Просто взять содержимое загружаемых секций и слепить из них бинарь:
Тип файла, заголовок, секции
В зависимости от задачи интерес могут представлять:
- тип файла (DYN — библиотека, EXEC — исполняемый, RELOC — линкуемый);
- целевая архитектура (E_MACHINE — x86_64, x86, ARM и т.д.);
- точка входа в приложение (Entry Point);
- информация о секциях.
заголовок ELF
Как видно на скриншоте, заголовок ELF начинается с «магического числа». Это «магическое число» даёт информацию о файле. Первые 4 байта определяют, что это ELF-файл (45=E,4c=L,46=F, перед ними стоит значение 7f).
Заголовок ELF является обязательным. Он нужен для того, чтобы данные корректно интерпретировались при линковке и исполнении. Для лучшего понимания внутренней работы ELF-файла, полезно знать, для чего используется эта информация.
Заголовок файла
В начале файла (со смещения 0 от начала) идет заголовок ELF-файла, описываемый следующей структурой:
Структура определена таким образом, что поля структуры выровнены по естественным для данной архитектуры правилам выравнивания (то есть 16-битные поля располагаются по четным адресам, а 32-битные - по адресам кратным 4), а полный размер структуры кратен 4 байтам. 16- и 32-битные значения представлены в порядке байт, естественном для соответствующей архитектуры.
Поле e_ident содержит идентификационную информацию о файле. Поле представляет собой массив байт для того, чтобы иметь одинаковое представление на архитектурах с разным размером слова и разным порядком байт в слове. Элементы массива имеют следующее назначение:
Элемент | Значение | Описание |
---|---|---|
e_ident[0] | '\x7f' | "Магическое" значение |
e_ident[1] | 'E' | "Магическое" значение |
e_ident[2] | 'L' | "Магическое" значение |
e_ident[3] | 'F' | "Магическое" значение |
e_ident[4] | 1 | Размер слова: 0 - неизвестно, 1 - 32, 2 - 64 |
e_ident[5] | 1 | Порядок байт: 0 - неизвестно, 1 - little-endian, 2 - big-endian |
e_ident[6] | 1 | Версия формата ELF: 0 - неизвестно, 1 - текущая версия |
e_ident[7] | 0 | ОС и бинарный интерфейс, для Linux - 0 |
e_ident[8] | 0 | Версия бинарного интерфейса, для Linux - 0 |
e_ident[9] - e_ident[15] | 0 | Зарезервировано |
В дальнейшем будут приводиться значения констант для ОС Linux на архитектуре i386. За значениями констант для других операционных систем или архитектур обращайтесь к документации.
Поле e_type идентифицирует тип файла: 0 (неизвестно), 1 (объектный файл), 2 (исполняемый файл), 3 (разделяемая библиотека), 4 (core-файл).
Поле e_machine идентифицирует тип процессора: 0 (неизвестно), 3 (Intel 80386 и совместимые).
Поле e_version идентифицирует версию файла: 0 (недопустимая версия), 1 (текущая версия).
Поле e_entry определяет виртуальный адрес точки входа в программу. После загрузки программы в память управление передается на этот адрес.
Поле e_phoff задает смещение от начала файла до начала таблицы заголовков программы (program header table). Информация о таблице заголовков программы будет дана ниже.
Поле e_shoff задает смещение от начала файла до начала таблицы заголовков секций (program section table). Информация о таблице заголовков секций будет дана ниже.
Поле e_flags задает дополнительные процессорно-специфичные флаги. В настоящее время значение данного поля должно всегда быть 0.
Поле e_ehsize хранит размер заголовка ELF-файла. Его значение должно быть равно 52 (sizeof(Elf32_Ehdr)).
Поле e_phentsize хранит размер одной записи в таблице заголовков программы. Его значение должно быть 32 (sizeof(Elf32_Phdr)) или 0, если таблица заголовков программы пуста.
Поле e_phnum хранит количество записей в таблице заголовков программы.
Поле e_shentsize хранит размер одной записи в таблице заголовков секций. Его значение должно быть равно 40 (sizeof(Elf32_Shdr)) или 0, если таблица заголовков секций пуста.
Поле e_shnum хранит количество записей в таблице заголовков секций.
Поле e_shstrndx хранит индекс заголовка секции, которая хранит имена всех секций (см. ниже).
Статические и динамические бинарные файлы
Когда мы имеем дело с бинарными файлами ELF, полезно будет знать, как линкуются эти два типа файлов. Они могут быть статическими и динамическими, и это относится к библиотекам, которые они используют. Если бинарник «динамический», это означает, что он использует внешние библиотеки, содержащие какие-либо общие функции, типа открытия файла или создания сетевого сокета. Статические бинарники, напротив, включают в себя все необходимые библиотеки.
Если вы хотите проверить, является ли файл статическим или динамическим, используйте команду file. Она покажет что-то вроде этого:
Чтобы определить, какие внешние библиотеки использованы, просто используйте ldd на том же бинарнике:
Совет: Чтобы посмотреть дальнейшие зависимости, лучше использовать утилиту lddtree.
Как я могу увидеть тип файла?
Используйте команду file для первой стадии анализа. Эта команда способна показать подробности, извлечённые из «магических» чисел и заголовков.
GNU_EH_FRAME
Это сортированная очередь, используемая компилятором GCC. В ней хранятся обработчики исключений. Если что-то пошло не так, они используются для того, чтобы корректно обработать ситуацию.
Проверка эльфа на безопасность
Скрипт проверки безопасности checksec.sh от исследователя Tobias Klein (автора книги A Bug Hunter's Diary) не обновлялся с 2011 года. Данный скрипт для ELF-файлов выполняет проверку наличия опций RelRO (Read Only Relocations), NX (Non-Executable Stack), Stack Canaries, PIE (Position Independent Executables) и для своей работы использует утилиту readelf.
Можно сделать свой аналог на коленке Python и LIEF (чуть короче прародителя и с дополнительной проверкой опции separate-code):
Популярные инструменты
Radare2
Тулкит Radare2 создан Серджи Альваресом (Sergi Alvarez). Число 2 подразумевает, что код был полностью переписан по сравнению с первой версией. Сейчас он используется многими исследователями, для изучения работы кода.
Программные пакеты
Большинство Linux-систем имеют установленный пакет binutils. Другие пакеты могут помочь вам увидеть больше информации. Правильный тулкит упростит вашу работу, особенно если вы занимаетесь анализом ELF-файлов. Я собрал здесь список пакетов и утилит для анализа ELF-файлов.
elfutils
/usr/bin/eu-addr2line
/usr/bin/eu-ar – альтернатива ar, для создания и обработки архивных файлов
/usr/bin/eu-elfcmp
/usr/bin/eu-elflint – проверка на соответствие спецификациям gABI и psABI
/usr/bin/eu-findtextrel – поиск релокаций текста
/usr/bin/eu-ld – комбинирует объектный и архивные файлы
/usr/bin/eu-make-debug-archive
/usr/bin/eu-nm – показывает символы объектного и исполняемого файлов
/usr/bin/eu-objdump – показывает информацию из объектного файла
/usr/bin/eu-ranlib – создаёт индекс архивных файлов
/usr/bin/eu-readelf – показывает ELF-файл в читаемой форме
/usr/bin/eu-size – показывает размер каждой секции (text, data, bss, etc)
/usr/bin/eu-stack – показывает стек текущего процесса или дампа ядра
/usr/bin/eu-strings – показывает текстовые строки (как утилита strings)
/usr/bin/eu-strip – удаляет таблицу символов из файла ELF
/usr/bin/eu-unstrip – добавляет символы и отладочную информацию в бинарник
Примечание: пакет elfutils будет хорошим началом, он содержит большинство утилит для анализа
elfkickers
/usr/bin/ebfc – компилятор языка Brainfuck
/usr/bin/elfls – показывает программные заголовки и заголовки секций с флагами
/usr/bin/elftoc – преобразует бинарник в программу на С
/usr/bin/infect – утилита, инжектирующая дроппер, создаёт файл setuid в /tmp
/usr/bin/objres – создаёт объект из обычных или бинарных данных
/usr/bin/rebind – изменяет связывание и видимость символов в ELF-файлах
/usr/bin/sstrip – удаляет ненужные компоненты из ELF-файла
Примечание: автор пакета ELFKickers сфокусирован на манипулировании ELF-файлами, что позволяет вам получить больше информации при работе с «неправильными» ELF-бинарниками
pax-utils
/usr/bin/dumpelf – дамп внутренней структуры ELF
/usr/bin/lddtree – как ldd, с установкой уровня показываемых зависимостей
/usr/bin/pspax – выводит ELF/PaX информацию о запущенных процессах
/usr/bin/scanelf – широкий диапазон информации, включая подробности PaX
/usr/bin/scanmacho – показывает подробности бинарников Mach-O (Mac OS X)
/usr/bin/symtree – показывает символы в виде дерева
Примечание: некоторые утилиты в этом пакете могут рекурсивно сканировать директории, и идеальны для анализа всего содержимого директории. Фокус сделан на инструментах для исследования подробностей PaX. Помимо поддержки ELF, можно извлекать информацию из Mach-O-бинарников.
prelink
/usr/bin/execstack – можно посмотреть или изменить информацию о том, является ли стек исполняемым
/usr/bin/prelink – релоцирует вызовы в ELF файлах, для ускорения процесса
Что представляет собой файл ELF?
ELF — это сокращение от Executable and Linkable Format (формат исполняемых и связываемых файлов) и определяет структуру бинарных файлов, библиотек, и файлов ядра (core files). Спецификация формата позволяет операционной системе корректно интерпретировать содержащиеся в файле машинные команды. Файл ELF, как правило, является выходным файлом компилятора или линкера и имеет двоичный формат. С помощью подходящих инструментов он может быть проанализирован и изучен.
strip
В самом простом случае можно воспользоваться утилитой strip из набора binutils. Для удаления всей символьной информации достаточно выполнить команду:
- для исполняемого файла:
- для динамической библиотеки:
Класс
После объявления типа ELF, следует поле класса. Это значение означает архитектуру, для которой предназначен файл. Оно может равняться 01 (32-битная архитектура) или 02 (64-битная). Здесь мы видим 02, что переводится командой readelf как файл ELF64, то есть, другими словами, этот файл использует 64-битную архитектуру. Это неудивительно, в моей машине установлен современный процессор.
Заключение
Файлы ELF предназначены для исполнения и линковки. В зависимости от назначения, они содержат необходимые сегменты и секции. Ядро ОС просматривает сегменты и отображает их в память (используя mmap). Секции просматриваются линкером, который создаёт исполняемый файл или разделяемый объект.
Файлы ELF очень гибкие и поддерживаются различные типы CPU, машинные архитектуры, и операционные системы. Также он расширяемый, каждый файл сконструирован по-разному, в зависимости от требуемых частей. Путём использования правильных инструментов, вы сможете разобраться с назначением файла, и изучать содержимое бинарных файлов. Можно просмотреть функции и строки, содержащиеся в файле. Хорошее начало для тех, кто исследует вредоносные программы, или понять, почему процесс ведёт себя (или не ведёт) определённым образом.
Версия ABI
При необходимости, может быть указана версия ABI.
Mangled — demangled имена функций
В ELF-ах, созданных из С++ кода, имена функций декорированы (манглированы) для упрощения поиска соответствующей функции класса. Однако читать такие имена при анализе не очень удобно.
Для представления имён в удобочитаемом виде можно использовать утилиту nm из набора binutils:
Вывод имён символов в деманглированном виде с использованием библиотеки LIEF:
Версия ABI
При необходимости, может быть указана версия ABI.
Ресурсы для дальнейшего изучения
Если вы хотите больше знать про ELF и обратную разработку, вы можете посмотреть работу, которую мы выполняем в Linux Security Expert. Как часть учебной программы, мы имеем модуль обратной разработки с практическими лабораторными работами.
Для тех из вас, кто любит читать, хороший и глубокий документ: ELF Format и документ за авторством Брайана Рейтера (Brian Raiter), также известного как ELFkickers. Для тех, кто любит разбираться в исходниках, посмотрите на документированный заголовок ELF от Apple.
Совет:
если вы хотите стать лучше в анализе файлов, начните использовать популярные инструменты анализа, которые доступны в настоящее время.
На русском языке довольно мало информации про то, как работать с ELF-файлами (Executable and Linkable Format — основной формат исполняемых файлов Linux и многих Unix-систем). Не претендуем на полное покрытие всех возможных сценариев работы с эльфами, но надеемся, что информация будет полезна в виде справочника и сборника рецептов для программистов и реверс-инженеров.
Подразумевается, что читатель на базовом уровне знаком с форматом ELF (в противном случае рекомендуем цикл статей Executable and Linkable Format 101).
Под катом будут перечислены инструменты для работы, описаны приемы для чтения метаинформации, модификации, проверки и размножения создания эльфов, а также приведены ссылки на полезные материалы.
— Я тоже эльф… Синий в красный… Эльфы очень терпеливы… Синий в красный… А мы эльфы. Синий в красный… От магии одни беды…
(с) Маленькое королевство Бена и Холли
В большинстве случаев примеры можно выполнить как на Linux, так и на Windows.
В рецептах мы будем использовать следующие инструменты:
- утилиты из набора binutils (objcopy, objdump, readelf, strip);
- фреймворк radare2;
- hex-редактор с поддержкой шаблонов файлов (в примерах показан 010Editor, но можно использовать, например, свободный Veles);
- Python и библиотеку LIEF;
- другие утилиты (ссылки указаны в рецепте).
В качестве «подопытного» будем использовать ELF-файл simple из таска nutcake's PieIsMyFav на crackmes.one, но подойдёт любой представитель «эльфийского» семейства. Если готовый файл с требуемыми характеристиками не был найден в свободном доступе, то будет приведён способ создания такого эльфа.
«Свободных» эльфов можно также найти по ссылкам:
-
; ; — подборка небольших эльфов с разными настройками; — но стоит учитывать, что тут могут попадаться хитрые образцы.
Зачем изучать ELF в подробностях?
Перед тем, как погрузиться в технические детали, будет не лишним объяснить, почему понимание формата ELF полезно. Во-первых, это позволяет изучить внутреннюю работу операционной системы. Когда что-то пошло не так, эти знания помогут лучше понять, что именно случилось, и по какой причине. Также возможность изучения ELF-файлов может быть ценна для поиска дыр в безопасности и обнаружения подозрительных файлов. И наконец, для лучшего понимания процесса разработки. Даже если вы программируете на высокоуровневом языке типа Go, вы всё равно будет лучше знать, что происходит за сценой.
Итак, зачем изучать ELF?
- Для общего понимания работы операционной системы
- Для разработки ПО
- Цифровая криминалистика и реагирование на инциденты (DFIR)
- Исследование вредоносных программ (анализ бинарных файлов)
заголовок ELF
Как видно на скриншоте, заголовок ELF начинается с «магического числа». Это «магическое число» даёт информацию о файле. Первые 4 байта определяют, что это ELF-файл (45=E,4c=L,46=F, перед ними стоит значение 7f).
Заголовок ELF является обязательным. Он нужен для того, чтобы данные корректно интерпретировались при линковке и исполнении. Для лучшего понимания внутренней работы ELF-файла, полезно знать, для чего используется эта информация.
Версия
Затем следует ещё одно магической значение «01», представляющее собой номер версии. В настоящее время имеется только версия 01, поэтому это число не означает ничего интересного.
Инструменты анализа двоичных файлов
Если вы хотите анализировать ELF-файлы, определённо будет полезно сначала посмотреть на существующие инструменты. Существуют тулкиты для обратной разработки бинарников и исполняемого кода. Если вы новичок в анализе ELF-файлов, начните со статического анализа. Статический анализ подразумевает, что мы исследуем файлы без их запуска. Когда вы начнёте лучше понимать их работу, переходите к динамическому анализу. Запускайте примеры и смотрите на их реальное поведение.
Что такое ELF?
ELF — это Исполняемый и Связываемый Формат (Executable and Linkable Format). Это спецификация формата, определяющая, как инструкции записаны в исполняемом коде.
Данные
Далее идёт поле «данные», имеющее два варианта: 01 — LSB (Least Significant Bit), также известное как little-endian, либо 02 — MSB (Most Significant Bit, big-endian). Эти значения помогают интерпретировать остальные объекты в файле. Это важно, так как разные типы процессоров по разному обрабатывают структуры данных. В нашем случае используется LSB, так как процессор имеет архитектуру AMD64.
Эффект LSB становится видимым при использовании утилиты hexdump на бинарном файле. Давайте посмотрим заголовок ELF для /bin/ps.
Мы видим, что пары значений другие, из-за интерпретации порядка данных.
Что представляет собой файл ELF?
ELF — это сокращение от Executable and Linkable Format (формат исполняемых и связываемых файлов) и определяет структуру бинарных файлов, библиотек, и файлов ядра (core files). Спецификация формата позволяет операционной системе корректно интерпретировать содержащиеся в файле машинные команды. Файл ELF, как правило, является выходным файлом компилятора или линкера и имеет двоичный формат. С помощью подходящих инструментов он может быть проанализирован и изучен.
Заголовки программы
Файл ELF состоит из нуля или более сегментов, и описывает, как создать процесс, образ памяти для исполнения в рантайме. Когда ядро видит эти сегменты, оно размещает их в виртуальном адресном пространстве, используя системный вызов mmap(2). Другими словами, конвертирует заранее подготовленные инструкции в образ в памяти. Если ELF-файл является обычным бинарником, он требует эти программные заголовки, иначе он просто не будет работать. Эти заголовки используются, вместе с соответствующими структурами данных, для формирования процесса. Для разделяемых библиотек (shared libraries) процесс похож.
Программный заголовок в бинарном ELF-файле
Мы видим в этом примере 9 программных заголовков. Сначала трудно понять, что они означают. Давайте погрузимся в подробности.
Анатомия ELF-файла
Распространённым заблуждением является то, что файлы ELF предназначены только для бинарных или исполняемых файлов. Мы уже сказали, что они могут быть использованы для частей исполняемых файлов (объектного кода). Другим примером являются файлы библиотек и дампы ядра (core-файлы и a.out файлы). Спецификация ELF также используется в Linux для ядра и модулей ядра.
OS/ABI
Каждая операционная система имеет свой способ вызова функций, они имеют много общего, но, вдобавок, каждая система, имеет небольшие различия. Порядок вызова функции определяется «двоичным интерфейсом приложения» Application Binary Interface (ABI). Поля OS/ABI описывают, какой ABI используется, и его версию. В нашем случае, значение равно 00, это означает, что специфические расширения не используются. В выходных данных это показано как System V.
Машина
Также в заголовке указывается ожидаемый тип машины (AMD64).
Поле типа указывает, для чего предназначен файл. Вот несколько часто встречающихся типов файлов.
CORE (значение 4)
DYN (Shared object file), библиотека (значение 3)
EXEC (Executable file), исполняемый файл (значение 2)
REL (Relocatable file), файл до линковки (значение 1)
Удаление таблицы секций
Как упоминалось выше, наличие/отсутствие таблицы секций не оказывает влияния на работу приложения. Но при этом без таблицы секций реверс приложения становится чуть сложнее.
Воспользуемся библиотекой LIEF под Python и примером удаления таблицы секций:
readelf
Посмотреть элементы из секции .dynamic (среди которых есть и RPATH) можно так:
Для удобства чтения результат команды сокращён:
С помощью библиотеки LIEF также можно прочитать RPATH-запись в эльфе:
OS/ABI
Каждая операционная система имеет свой способ вызова функций, они имеют много общего, но, вдобавок, каждая система, имеет небольшие различия. Порядок вызова функции определяется «двоичным интерфейсом приложения» Application Binary Interface (ABI). Поля OS/ABI описывают, какой ABI используется, и его версию. В нашем случае, значение равно 00, это означает, что специфические расширения не используются. В выходных данных это показано как System V.
Часто задаваемые вопросы
От исходника к процессу
Какую бы операционную систему мы не использовали, необходимо каким-то образом транслировать функции исходного кода на язык CPU — машинный код. Функции могут быть самыми базовыми, например, открыть файл на диске или вывести что-то на экран. Вместо того, чтобы напрямую использовать язык CPU, мы используем язык программирования, имеющий стандартные функции. Компилятор затем транслирует эти функции в объектный код. Этот объектный код затем линкуется в полную программу, путём использования линкера. Результатом является двоичный файл, который может быть выполнен на конкретной платформе и конкретном типе CPU.
010Editor
HEX-редактор 010Editor предоставляет систему шаблонов. Для ELF-файлов шаблон называется, как ни странно, ELF.bt и находится в категории Executable (меню Templates — Executable).
Интерес может представлять, например, точка входа в исполняемый файл (entry point) (записана в заголовке файла).
objdump
Информация о компиляторе
Что такое ELF?
ELF — это Исполняемый и Связываемый Формат (Executable and Linkable Format). Это спецификация формата, определяющая, как инструкции записаны в исполняемом коде.
Смотрим полный заголовок
Хотя некоторые поля могут быть просмотрены через readelf, их на самом деле больше. Например, можно узнать, для какого процессора предназначен файл. Используем hexdump, чтобы увидеть полный заголовок ELF и все значения.
(вывод hexdump -C -n 64 /bin/ps)
Выделенное поле определяет тип машины. Значение 3e — это десятичное 62, что соответствует AMD64. Чтобы получить представление обо всех типах файлов, посмотрите этот заголовочный файл.
Хотя вы можете делать всё это в шестнадцатиричном дампе, имеет смысл использовать инструмент, который сделает работу за вас. Утилита dumpelf может быть полезна. Она показывает форматированный вывод, соответствующий заголовку ELF. Хорошо будет изучить, какие поля используются, и каковы их типичные значения.
Теперь, кгда мы объяснили значения этих полей, время посмотреть на то, какая реальная магия за ними стоит, и перейти к следующим заголовкам!
Читайте также: