Допустим, вам на руки попал исполняемый файл, и нужно понять — что он из себя представляет, собрал ли кто‑то его руками или получил естественным путём, можно ли его отладить. Или вы кросс‑компилируете под Linux и хотите убедиться, что результат принципиально отличается от Windows‑сборки. В обоих случаях первый шаг — заглянуть внутрь формата. ELF и PE устроены по‑разному, и разница не только в имени и расширении файла.
- С чего начать анализ ELF
- ELF‑заголовок: базовая информация
- Секции и сегменты: в чём разница
- Таблицы символов и имена
- Как устроен PE‑файл
- DOS‑заголовок и переход
- PE‑заголовок и опциональный заголовок
- Секции в PE
- Где смотреть структуру на практике
- Сравнение структур: что принципиально важно
- Когда какой формат анализировать
- Частые ошибки при анализе
- Как лучше сделать анализ
- Итог
С чего начать анализ ELF
Сам ELF (Executable and Linkable Format) — это стандартный бинарный формат для Unix‑подобных систем. Он задаёт структуру исполняемых файлов, объектных файлов, разделяемых библиотек и дампов ядра. ELF‑файл состоит из нескольких ключевых частей, и анализ всегда начинается с заголовка.
ELF‑заголовок: базовая информация
Структура ELF определяется стандартом и включает:
- идентификаторы — магическое число 0x7f и символы «E», «L», «F»;
- указание архитектуры (32‑бит или 64‑бит);
- тип файла (исполняемый, перемещаемый, динамическая библиотека);
- точка входа (entry point);
- смещение таблицы заголовков секций;
- смещение таблицы программных заголовков.
Это минимальный объём данных, без которого невозможно понять, как интерпретировать остальную часть файла. Типичная ситуация: вы открываете файл в 16‑ричном редакторе или вызываете readelf -h, и первое, что вы увидите — именно эти поля.
Секции и сегменты: в чём разница
В ELF есть два похожих, но принципиально разных понятия: секции (sections) и сегменты (program headers). Секции — это минимальные единицы компиковки и линковки. В них лежит код, данные, отладочная информация, таблицы символов. Сегменты — это способы загрузить файл в память: один сегмент может включать несколько секций, если у них одинаковые флаги доступа.
Когда вы анализируете ELF‑файл, смотрите на оба списка. readelf -S покажет все секции, readelf -l — программные заголовки. Например, секция .text содержит исполняемый код, .data — инициализированные данные, .bss — обнуляемые данные. Секции могут быть пустыми, если код оптимизирован или отладка отключена.
Таблицы символов и имена
ELF хранит информацию о функциях и глобальных переменных в таблицах символов. Две основные секции:
.symtab— полная таблица символов, включая локальные;.dynsym— таблица символов, нужных для динамической компоновки.
Можно посмотреть их через readelf -s или objdump -t. Если символы вырезаны (stripped), вы увидите только динамические имена. Это нормально для большинства бинарников, которые вы получаете в готовом виде.
Как устроен PE‑файл
PE (Portable Executable) — формат исполняемых файлов Windows. Он произошёл от olderформата MZ (DOS‑заголовок), который до сих пор часто можно найти в начальных байтах PE‑файла как дань обратной совместимости. Несмотря на это, основная структура PE радикально отличается от ELF.
DOS‑заголовок и переход
PE‑файл начинается с DOS‑заголовка, который содержит два поля:
- магическое число
0x5A4D(«MZ»); - смещение на PE‑заголовок (обычно по адресу
e_lfanew).
Дальше идёт DOS‑код, который при запуске обычно выводит сообщение «This program cannot be run in DOS mode». Затем располагается PE‑заголовок с сигнатурой «PE\0\0». Уже здесь видно одно из первых отличий: в PE исторически заложена ступенька совместимости, которой нет в ELF.
PE‑заголовок и опциональный заголовок
После сигнатуры идёт стандартный (COFF) заголовок и опциональный заголовок. В стандартном заголовке указываются:
- архитектура (Machine);
- количество секций;
- временная метка сборки;
- смещение таблицы символов (если она есть);
- размер опционального заголовка.
В опциональном заголовке — точка входа, предпочтительный базовый адрес загрузки, размер стека, кучи, подсистема (GUI, консоль, драйвер) и много других вещей. В Windows без опционального заголовка нормальный exe‑файл собрать нельзя.
Секции в PE
Каждая секция в PE описывается собственным заголовком с именем, размером, смещением, флагами доступа. Названия часто начинаются с точки: .text, .data, .rdata, .reloc. В отличие от ELF, секции в PE обычно не группируются в сегменты явно — загрузчик сам по флагам определяет, как отобразить секцию в память.
Где смотреть структуру на практике
| Что смотреть | ELF (Linux/Unix) | PE (Windows) |
|---|---|---|
| Заголовок файла | readelf -h |
CFF Explorer, PEview |
| Список секций | readelf -S |
objdump -h (mingw) |
| Программные заголовки | readelf -l |
видны в опциональном заголовке |
| Таблица символов | readelf -s |
objdump -tили отладочные символы |
| Точка входа | в ELF‑заголовке | в опциональном заголовке |
| Отладочная информация | секции .debug_*, DWARF |
PDB, CV‑записи, CodeView |
Если вы работаете на Linux, но хотите посмотреть PE‑файл, удобно использовать objdump из пакета binutils‑mingw. Для серьёзного анализа Windows‑бинарей пригодятся инструменты вроде CFF Explorer, PEview и Ghidra.
Сравнение структур: что принципиально важно
ELF и PE — оба формата, описывающие, как загрузить код в память и отладить его. Но подходы к организации информации у них разные. Вот ключевые различия, которые сразу бросаются в глаза при анализе:
- Метаданные и расширяемость. ELF легко добавляет новые секции и типы заголовков. Секции могут содержать пользовательские данные, релокации, информацию о версиях, зависимости от конкретных символов. PE более жёстко структурирован и меньше поддаётся расширению без изменения формата.
- Типы содержимого. ELF описывает не только исполняемые файлы, но и объектные файлы, разделяемые библиотеки, core‑дампы. Это сделало формат универсальным для всей экосистемы сборки и отладки. PE в основном ориентирован на исполняемые файлы и DLL.
- Модель загрузки. В Linux загрузчик (ld.so) и ядро операются сегментами ELF и динамической компоновкой, описанной в
.dynamic. В Windows загрузчик PE сам ищет импортируемые библиотеки, обрабатывает таблицу импорта (IAT), и все адреса в PE обычно привязаны к предпочтительному базовому адресу, если нет ASLR. - Отладочная информация. В Linux отладочные данные часто страницы ELF‑файла (DWARF), в Windows — внешний PDB‑файл, хотя раньше использовались встроенные CodeView‑записи.
- Флаги и выравнивание. Оба формата учитывают требования выравнивания секций, но у PE жёстче с минимальной гранулярностью отображения в памяти, что влияет на размер файла при большом количестве мелких секций.
Когда какой формат анализировать
Сценарий 1. Вам пришёл бинарник под Linux, и надо понять, собрал ли его GCC или Clang, есть ли отладочные данные, какие библиотеки нужны для запуска. Вы смотрите ELF‑заголовок, список секций, .dynamic, таблицу символов. Если формат странный — возможно, файл обфусцирован или получен кросс‑компиляцией.
Сценарий 2. Вы получили Windows‑exe, и нужно проверить, не повреждён ли заголовок, не зашифрованы ли секции, какие DLL импортируются. Смотрите DOS‑заголовок, PE‑сигнатуру, опциональный заголовок, таблицу импорта. Если в начале файла не «MZ», а что‑то другое — возможно, это не PE вообще.
Сценарий 3. Вы сравниваете один и тот же исходный код, скомпилированный под две платформы. Тогда полезно посмотреть, как распределён код и данные по секциям, как работают релокации, как организована динамическая компоновка. В ELF вы увидите .got, .plt, в PE — IAT и секцию .reloc.
Частые ошибки при анализе
- Путать секции и сегменты в ELF. Секция
.textможет входить в один программный заголовок вместе с.rodata. Если вы смотрите только секции, можно не понять, как именно загрузчик отобразит файл в память. - Игнорировать DOS‑заголовок в PE. Если вы сразу ищете «PE» с начала файла, потратите время впустую. Надо сначала прочитать
e_lfanewи перейти по смещению. - Считать, что отсутствие символов — признак вредоносности. В реальных продуктах символы часто вырезают на этапе сборки. Это не обязательно вирус, просто стандартная практика.
- Не учитывать выравнивание. Размер секции на диске может отличаться от размера в памяти. Если вы вручную парсите файл, путаница с выравниванием приводит к неверным смещениям.
- Полагаться только на один инструмент.
readelfудобен, но иногда данные в секциях могут быть неполными или повреждёнными. Полезно перепроверятьobjdumpили Ghidra.
Как лучше сделать анализ
- Определите формат. Первые 4 байта ELF —
7f 45 4c 46, PE —4d 5a. Если вы видите что‑то другое, возможно, это скрипт, архив или файл другой ОС. - Прочитайте основной заголовок. Убедитесь, что архитектура совпадает с ожидаемой (x86, x86_64, ARM). Проверьте точку входа — она должна быть в пределах адресного пространства секций.
- Изучите секции. Обратите внимание на названия, размеры, флаги. Если секция
.textпустая, а файл позиционируется как исполняемый — это подозрительно. - Посмотрите динамические зависимости. В ELF —
.dynamicиreadelf -d. В PE — таблица импорта. Так вы поймёте, какие библиотеки нужны для запуска. - Проверьте отладочную информацию. Если она есть, анализ становится намного проще. Если её нет, придётся ориентироваться по именам динамических символов и строкам.
- Используйте дизассемблер. Ghidra, IDA, radare2 позволяют увидеть реальную структуру кода, перекрёстные ссылки, вызовы функций. Это следующий уровень после чтения заголовков.
Итог
Анализ ELF и PE — это не разовое чтение магического числа, а последовательное восстановление картины: как файл собран, какие данные содержит, как будет загружаться в память. ELF отличается гибкостью и универсальностью, PE — более жёсткой структурой с историческими наслоениями. Если вы знаете, где в каждом формате расположены заголовки, секции, таблицы символов и динамические зависимости, вы можете быстро оценить любой бинарник и понять, что с ним делать дальше.
Начните с простого: откройте несколько реальных файлов в readelf и objdump, посмотрите, как меняется структура при отключении отладки, при статической и динамической компоновке. После пары десятков примеров различия между ELF и PE станут не теоретическими, а видны «на глаз».
