Формат ELF-файла
В результате компиляции и компоновки ассемблером GAS создается файл формата ELF (Executable and Linkable Format), который содержит всю информацию, необходимую операционной системе для загрузки и запуска программы. Условно файл ELF состоит из трех частей:
- Заголовок файла ELF
- Заголовки программы
- Заголовки разделов
Инструменты Arm GNU Toolchain предоставляют специальную утилиту для исследования ELF-файла — readelf . Мы ее можем найти в той же папке, где и располгааются исполняемые файлы компилятора gcc для arm64:

Например, в пакете Arm GNU Toolchain aarch64-none-elf эта утилита называется aarch64-none-elf-readelf . Передавая этой утилите различные параметры, можно получить представление различных частей файла в формате ELF. Для получения полной справки по данной утиилите ей надо передать опцию -H . Отмечу лишь некоторые основные опции, которые мы можем использовать для исследования файла в формате ELF:
- -a или —all : вывод всей информации, аналогично применению опций -h -l -S -s -r -d -V -A -I
- -h или —file-header : вывод заголовка файла
- -l или —program-headers : вывод заголовков программы
- -S или —section-headers (либо —sections ): вывод заголовков разделов
- -g или —section-groups : вывод групп разделов
- -t или —section-details : вывод информации о разделах
- -e или —headers : вывод всех заголовков, эквивалентно набору опций -h -l -S
- -s или —syms : вывод таблицы символов
Рассмотрим на примере простейшей программы, которая выводит приветственное соощение на консоль. Допустим, у нас есть файл hello.s со следующим кодом:
.global _start // устанавливаем стартовый адрес программы _start: mov X0, #1 // 1 = StdOut - поток вывода ldr X1, =hello // строка для вывода на экран mov X2, #19 // длина строки mov X8, #64 // устанавливаем функцию Linux svc 0 // вызываем функцию Linux для вывода строки mov X0, #0 // Устанавливаем 0 как код возврата mov X8, #93 // код 93 представляет завершение программы svc 0 // вызываем функцию Linux для выхода из программы .data hello: .ascii "Hello METANIT.COM!\n" // данные для вывода
Скомпилируем из этого файла объектный файл:
aarch64-none-elf-as hello.s -o hello.o
Затем из скомпилированного объектного файла hello.o создадим бинарный файл в формате ELF:
aarch64-none-elf-ld hello.o -o hello.so
Итак, мы получили бинарный исполныемый файл hello.so в формате ELF. Теперь проанализируем его.
Заголовок файла ELF
Для получения заголовка выполним команду
aarch64-none-elf-readelf -h hello.so
В результате мы должны получить вывод наподобие следующего:
c:\arm>aarch64-none-elf-readelf -h hello.so ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: AArch64 Version: 0x1 Entry point address: 0x400000 Start of program headers: 64 (bytes into file) Start of section headers: 66152 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 2 Size of section headers: 64 (bytes) Number of section headers: 6 Section header string table index: 5 c:\arm>
Это и есть заголовок программы. Разберем его.
Вначале идет поле Magic . Оно всегда имеет размер 16 байт и призван указать, что файл является корректным файлом ELF. Это поле всегда начинается с байта 0x7f , за которым следуют 3 байта, соответствующие символам «ELF» из таблицы ASCII .
Поле Class указывает на разрядность. Значение ELF64 говорит, что файл использует 64-разрядный формат. В 32-разрядных программах это поле имело бы значение ELF32 .
Поле Data сообщает операционной системе и загрузчику, в каком порядке должны считываться поля файла ELF — big-endian (обратный порядок) или little-endian (прямой порядок). Файлы ELF на Arm обычно используют кодировку little-endian для самого формата файла ELF.
Поле Version указывает на версию файла.
Поле Type указывает на тип файла
Поле Machine сообщает загрузчику, для какого типа процессоров предназначена программа. Наша 64-битная программа устанавливает в этом поле значение AArch64, указывая, что файл ELF будет работать только на 64-битных процессорах Arm. В случае с 32-разрядной программой это поле имело бы значение ARM , что означало бы, что она работает только на 32-разрядных процессорах Arm.
Поле Entry point address сообщает загрузчику, где находится точка входа программы. Когда программа была подготовлена в памяти операционной системой или загрузчиком и готова начать выполнение, это поле указывает, откуда начинать выполнение программы. В данном случае это адрес 0x400000 — стандартный адрес для ARM64.
Поле Flags указывает дополнительную информацию, которая может понадобиться загрузчику. Это поле зависит от архитектуры. В 64-битной программе, например, не определены флаги, зависящие от архитектуры, и это поле всегда будет содержать нулевое значение 0x0 . Для 32-битной программы это поле может принимать ряд значений, которые информируют, что программа ожидает аппаратную поддержку операций с плавающей запятой:
- EF_ARM_ABIMASK (0xff000000) : старшие 8 бит значения содержат версию ABI, которая применяется файлом ELF. В настоящее время этот старший байт должен содержать значение 5 (т. е. 0x05000000), что означает, что файл ELF использует версию EABI 5.
- EF_ARM_BE8 (0x00800000) : файл ELF содержит код BE-8 для выполнения на процессоре Arm v6. Этот флаг должен быть установлен только для исполняемого файла.
- EF_ARM_ABI_FLOAT_HARD (0x00000400) : указывает, что файл соответствует стандарту вызова аппаратных процедур с плавающей точкой и что процессор будет Armv7 или выше и будет поддерживать аппаратное расширение VFP3-D16 с плавающей точкой.
- EF_ARM_ABI_FLOAT_SOFT (0x00000200) : указывает, что файл соответствует стандарту программного вызова процедур с плавающей точкой. Операции с плавающей точкой обрабатываются через вызовы библиотечных функций.
Поле Size of this header описывает размер заголовка файла.
Остальные поля описывают расположение и количество заголовков программы и разделов в файле:
- Start of program headers : начало заголовков программы
- Start of section headers : начало заголовков разделов
- Size of program headers : размер заголовков программы
- Number of program headers : количество заголовков программы
- Size of section headers : размер заголовков разделов
- Number of section headers : количество заголовков разделов
Загрузчик использует эти поля для подготовки ELF-файла в памяти к выполнению.
Вопросы с меткой [elf]
Используйте данную метку в вопросах, связанных с форматом файлов ELF.
45 вопросов
Конкурсные
Неотвеченные
- Конкурсные 0
- Неотвеченные
- Цитируемые
- Рейтинг
- Неотвеченные (мои метки)
86 показов
Место хранения статических локальных переменных зависит от уровня оптимизации?
Иногда в Compiler Explorer одни и те же статические локальные переменные функций показываются до функций, их использующих (-O0), а иногда — после, в конце ассемблера (-O2). Означает ли это, что они .
задан 15 окт в 9:17
28 показов
Как создать elf с большим количеством сегментов?
По работе понадобилось пропатчить elf файл так, чтоб в нем было большое количество сегментов. Любой файл,хоть hello world. Какими способами это можно сделать?
задан 20 дек 2022 в 12:11
365 показов
Как «запустить» приложение в ELF формате в Ubuntu?
Разработчик выложил старую версию своего приложения, скачав этот файл без расширения, я решил узнать, в каком он формате. Просмотрев его я обнаружил, что он в ELF формате. Как запустить, либо .
задан 15 янв 2022 в 21:03
49 показов
Как превратить ELF файл в питоновский код?
У меня есть исполняемый файл формата elf32-i386. Как мне восстановить исходный код?
задан 5 янв 2022 в 23:35
Как компилируется динамическая библиотека .so?
Правильно ли я понимаю, что в секции .plt находится адрес вызываемой функции из динамической библиотеки и в момент выполнения происходит, переход по тому адресу, где скомпонована библиотека, и .
задан 20 мая 2021 в 21:18
Кодирование названия секции .text
Почему в elf-файлах названия секций кодируются по-разному? Я видел (так считает парсер), что секция .text может кодироваться как 146 (0x92) и 155 (0x9B). И есть ли где-нибудь описание того, как должны .
задан 16 мая 2021 в 19:55
несколько файлов в один исполняемый linux
У меня есть исполняемый файл, который далее обращается в папки и там запускает программы и тд Как мне сделать так, чтобы все эти файлы превратились в один исполняемый файл? Насколько я знаю, в винде .
задан 24 окт 2020 в 20:00
147 показов
Почему появилась ошибка сегментирования NASM
Не могу понять, что не так с кодом Ошибка на строке «cmp [rax+rbx],byte 0» (проверял в gdb), но ведь для 64 архитектуры такая команда дозволена(?) Заранее спасибо segment .text global _start .
задан 19 сен 2020 в 14:02
95 показов
Как скомпилить NASM
Недавно задавал вопрос про ошибку сегментирования. Нашлись проблемы в коде, но их решения не дало результата. Полез в инет и понял, что любой онлайн IDE для 64 битного NASM-а компилит код без проблем (.
задан 15 сен 2020 в 14:56
45 показов
Ошибка сегментирования NASM
Делал подпрограмму для NASM, которая должна была считать длину строки ну и собственно выводить её. Код взял из видео, но у меня ошибка. Код такой: _strlen: push rdx xor rdx,rdx _strlen_loop_start: .
задан 14 сен 2020 в 17:58
48 показов
Почему неверный результат умножения NASM
Начал недавно учить NASM, сижу под Arch x86_64 , NASM version 2.15.04. Нашел недавно следующий код: section .text global _start _start: mov al,’3′ sub al, ‘0’ mov .
задан 14 сен 2020 в 15:22
148 показов
Как собрать elf и запустить пример из репозитория?
Я нашёл в репозитории гитхаба пример elf-loader’а, который я хочу запустить,чтобы создать blob. Но я не могу понять, как собрать пример. В ELF creation, где указаны папки moduleexample and .
задан 14 апр 2020 в 16:33
124 показа
Генераця ELF файлов
K примеру у меня есть байтовый массив готовых опкодов. Как мне сгенерировать ELF файл вместе с этими опкодами? Есть ли какая-то готовая библиотека для этого на С?
задан 1 фев 2020 в 15:41
65 показов
Компоновка с бинарным файлом ELF
Есть ли возможность скомпоновать программу с бинарным файлом и как это сделать? В моем случае есть исполняемый бинарный ELF файл /bin/btrfs (интерфейс для администрирования файловой системы). Из него &.
задан 31 янв 2020 в 18:17
97 показов
имена переменных в elf файле
Я разобрался с elf заголовком, но не полностью. В elf заголовке есть указатели на секции и т.д. Но я не пойму где хранятся указатели на переменные. Я пишу дизассемблер и мне бы хотелось ещё иметь .
user302477
задан 26 ноя 2019 в 11:52
15 30 50 на странице
-
Важное на Мете
Связанные метки
Подписаться на ленту
Лента новых вопросов с меткой [elf]
Для подписки на ленту скопируйте и вставьте эту ссылку в вашу программу для чтения RSS.
Дизайн сайта / логотип © 2023 Stack Exchange Inc; пользовательские материалы лицензированы в соответствии с CC BY-SA . rev 2023.10.27.43697
Нажимая «Принять все файлы cookie» вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.
Анатомия эльфов. Разбираемся с внутренним устройством ELF-файлов

Если в мире Windows исполняемые файлы представлены в формате Portable Executable (PE), то в Linux эта роль отведена файлам в формате Executable and Linkable Format (ELF). Сегодня мы заглянем внутрь таких файлов, немного поисследуем их структуру и узнаем, как они устроены.
Сразу отмечу, что в отличие от Windows в Linux от расширения файла не зависит практически ничего (за совсем небольшим исключением). Тип и формат файла определяется его внутренним содержимым и наличием тех или иных атрибутов, поэтому файлы в формате Executable and Linkable Format могут иметь любое расширение.
Что нам понадобится
Вообще, можно позавидовать людям, обитающим в мире Linux, — как правило, в системе «из коробки» идет большое число утилит и программ, которые в Windows необходимо где‑то искать и устанавливать дополнительно, да еще и не всегда бесплатно. В нашем случае для анализа ELF-файлов в Linux присутствует вполне состоятельный арсенал встроенных средств и утилит:
- readelf — с помощью этой утилиты можно практически полностью просматривать все потаенные места ELF-файлов в удобочитаемом виде;
- hexdump — простой просмотрщик файлов в шестнадцатеричном представлении (конечно, до hiew из мира Windows ему далеко, но, во‑первых, он присутствует в системе по умолчанию, а во‑вторых, делает это совершенно бесплатно);
- strings — с помощью этой известной утилиты можно увидеть имена всех импортируемых (или экспортируемых) функций, а также библиотек, из которых эти функции импортированы, названия секций и еще много чего интересного;
- ldd — позволяет выводить имена разделяемых библиотек, из которых импортируются те или иные функции, используемые исследуемой программой;
- nm — может показывать таблицу имен из состава отладочной информации, которая добавляется в ELF-файлы при их компиляции (эта отладочная информация с помощью команды strip может быть удалена из файла, и в этом случае утилита nm ничем не поможет);
- objdump — способна вывести информацию и содержимое всех элементов исследуемого файла, в том числе и в дизассемблированном виде.
Часть перечисленного (кроме hexdump и ldd) входит в состав пакета GNU Binutils. Если этого пакета в твоей системе нет, его легко установить. К примеру, в Ubuntu это выглядит следующим образом:
sudo apt install binutils
В принципе, имея все перечисленное, можно уже приступать к анализу и исследованию ELF-файлов без привлечения дополнительных средств. Для большего удобства и наглядности можно добавить к нашему инструментарию известный в кругах реверс‑инженеров дизассемблер IDA в версии Freeware (этой версии для наших целей будет более чем достаточно, хотя никто не запрещает воспользоваться версиями Home или Pro, если есть возможность за них заплатить).

Также неплохо было бы использовать вместо hexdump что‑то поудобнее, например 010 Editor или wxHex Editor. Первый hex-редактор — достойная альтернатива Hiew для Linux (в том числе и благодаря возможности использовать в нем большое количество шаблонов для различных типов файлов, среди них и шаблон для парсинга ELF-файлов). Однако он небесплатный (стоимость лицензии начинается с 49,95 доллара, при этом есть 30-дневный триальный период).

Говоря о дополнительных инструментах, которые облегчают анализ ELF-файлов, нельзя не упомянуть Python-пакет lief. Используя этот пакет, можно писать Python-скрипты для анализа и модификации не только ELF-файлов, но и файлов PE и MachO. Скачать и установить этот пакет получится традиционным для Python-пакетов способом:
pip install lief
Подопытные экземпляры
В Linux (да и во многих других современных UNIX-подобных операционных системах) формат ELF используется в нескольких типах файлов.
- Исполняемый файл — содержит все необходимое для создания системой образа процесса и запуска этого процесса. В общем случае это инструкции и данные. Также в файле может присутствовать описание необходимых разделяемых объектных файлов, а также символьная и отладочная информация. Исполняемый файл может быть позиционно зависимым (в этом случае он грузится всегда по одному и тому же адресу, для 32-разрядных программ обычно это 0x8048000 , для 64-разрядных — 0x400000 ) и позиционно независимым исполняемым файлом (PIE — Position Independent Execution или PIC — Position Independent Code). В этом случае адрес загрузки файла может меняться при каждой загрузке. При построении позиционно независимого исполняемого файла используются такие же принципы, как и при построении разделяемых объектных файлов.
- Перемещаемый файл — содержит инструкции и данные, при этом они могут быть статически связаны с другими объектными файлами, в результате чего получается разделяемый объектный или исполняемый файл. К этому типу относятся объектные файлы статических библиотек (как правило, для статических библиотек имя начинается с lib и применяется расширение *. a ), однако, как мы уже говорили, расширение в Linux практически ничего не определяет. В случае статических библиотек это просто дань традиции, а работоспособность библиотеки будет обеспечена с любым именем и любым расширением.
- Разделяемый объектный файл — содержит инструкции и данные, может быть связан с другими перемещаемыми файлами или разделяемыми объектными файлами, в результате чего будет создан новый объектный файл. Такие файлы могут выполнять функции разделяемых библиотек (по аналогии с DLL-библиотеками Windows). При этом в момент запуска программы на выполнение операционная система динамически связывает эту разделяемую библиотеку с исполняемым файлом программы, и создается исполняемый образ приложения. Опять же традиционно разделяемые библиотеки имеют расширение *. so (от английского Shared Object).
- Файл дампа памяти — файл, который содержит образ памяти того или иного процесса на момент его завершения. В определенных ситуациях ядро может создавать файл с образом памяти аварийно завершившегося процесса. Этот файл также создается в формате ELF, однако мы о такого рода файлах говорить не будем, поскольку задача исследования дампов и содержимого памяти достаточно объемна и требует отдельной статьи.
Для наших изысканий нам желательно иметь все возможные варианты исполняемых файлов из перечисленных выше, чем мы сейчас и займемся.
Делаем исполняемые файлы
Не будем выдумывать что‑то сверхоригинальное, а остановимся на классическом хелловорлде на С:
int main ( int argc , char * argv [] ) < printf ( "Hello world" ) ;
Компилировать это дело мы будем с помощью GCC. Современные версии Linux, как правило, 64-разрядные, и входящие в их состав по умолчанию средства разработки (в том числе и компилятор GCC) генерируют 64-разрядные приложения. Мы в своих исследованиях не будем отдельно вникать в 32-разрядные ELF-файлы (по большому счету отличий от 64-разрядных ELF-файлов в них не очень много) и основные усилия сосредоточим именно на 64-разрядных версиях программ. Если у тебя возникнет желание поэкспериментировать с 32-разрядными файлами, то при компиляции в GCC нужно добавить опцию -m32 , при этом, возможно, потребуется установить библиотеку gcc-multilib. Сделать это можно примерно вот так:
sudo apt- get install gcc- multilib
Итак, назовем наш хелловорлд example. c (кстати, здесь как раз один из немногих случаев, когда в Linux расширение имеет значение) и начнем с исполняемого позиционно зависимого кода:
gcc -no-pie example. c -o example_ no_ pie
Как ты уже догадался, опция -no-pie как раз и говорит компилятору собрать не позиционно независимый код.
Вообще, если говорить правильно, то GCC — это не совсем компилятор. Это комплексная утилита, которая в зависимости от расширения входного файла и опций вызывает нужный компилятор или компоновщик с соответствующими входными данными. Причем из С или другого высокоуровневого языка сначала исходник транслируется в ассемблерный код, а уже затем все это окончательно преобразуется в объектный код и собирается в нужный нам ELF-файл.
В целом можно выделить четыре этапа работы GCC:
- препроцессирование;
- трансляция в ассемблерный код;
- преобразование ассемблерного кода в объектный;
- компоновка объектного кода.
Чтобы посмотреть на промежуточный результат, к примеру в виде ассемблерного кода, используй в GCC опцию -S :
gcc -S -masm = intel example. c
Обрати внимание на два момента. Первый — мы в данном случае не задаем имя выходного файла с помощью опции -o (GCC сам определит его из исходного, добавив расширение *. s , что и означает присутствие в файле ассемблерного кода). Второй момент — опция -masm=intel , которая говорит о том, что ассемблерный код в выходном файле необходимо генерировать с использованием синтаксиса Intel (по умолчанию будет синтаксис AT&T, мне же, как и, наверное, большинству, синтаксис Intel ближе). Также в этом случае опция -no-pie не имеет смысла, поскольку ассемблерный код в любом случае будет одинаковый, а переносимость обеспечивается на этапе получения объектного файла и сборки программы.
На выходе получим файл example. s с таким вот содержимым (полностью весь файл показывать не будем, чтобы не занимать много места):
Строение ELF-файлов⚓︎
ELF — сокращение от «Executable and Lincable Format» — формат исполняемых и связываемых файлов. ELF определяет их структуру. Данная спецификация позволяет UNIX-подобным(/образным) системам правильно интерпретировать содержащиеся в файле машинные команды. Используется во многих операционных системах: GNU/Linux, FreeBSD, Solaris, etc.
Понимание строения ELF файла может редко пригодиться, но, тем не менее, оно будет полезно для понимания процесса разработки программного обеспечения, поиска дыр в безопасности и обнаружения подозрительных программ или файлов.
Начальное строение⚓︎

Для начала создадим директорию, в которой будут расположены тестовые программы, на которых будем «упражняться»:
mkdir ~/LinuxPrograms cd ~/LinuxPrograms
Типы⚓︎
Есть несколько типов ELF файлов (см. таблицы в конце статьи): * Перемещаемый файл — хранит инструкции (и данные), которые могут быть связаны с другими объектными файлами. Результатом может быть объектный или исполняемый файл. Так же к этому типу относятся объектные файлы статических библиотек. * Разделяемый объектный файл — также как и первый тип, содержит инструкции и данные, может быть связан с другими перемещаемыми и разделяемыми объектными файлами, в результате чего будет создан новый объектный файл, либо же при запуске программы ОС может динамически связывать его с исполняемым файлом программы , в результате чего будет создан исполняемый образ программы (в посл. случае речь идёт о разделяемых библиотеках). * Исполняемый файл — содержит полное описание, позволяющее ОС создать образ процесса. В т.ч.: инструкции, данные, описания необходимых разделяемых объектных файлов и др.
Для того, чтобы вывод всех команд, приведённых ниже, был краток, прост и понятен, напишите какую-нибудь простейшую программу, в которой нет ничего лишнего, что затраднит чтение:
vim simple.c int main() return(0); >
И скомпилируйте её:
gcc -o simple simple.c
Убедитесь в том, что это ELF файл:
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 :
-
Просмотр заголовка файла:
readelf -h simple
readelf -S -W simple
readelf -s -W simple