- Как проанализировать структуру ELF и сравнить её с PE — практическое руководство для разработчика и аналитика
- Что такое ELF и PE — просто, без заумностей
- Как посмотреть структуру ELF — пошагово
- Как посмотреть структуру PE — аналогично, но по-другому
- Сравнение ELF и PE — таблица, которую реально использовать
- Что выбрать: ELF или PE — в зависимости от твоей задачи
- Частые ошибки — и как их избежать
- Как лучше делать — практические рекомендации
- Итог: что делать прямо сейчас
Как проанализировать структуру ELF и сравнить её с PE — практическое руководство для разработчика и аналитика
Ты работаешь с бинарниками на Linux — может, отлаживаешь краш, анализируешь вредоносное ПО, или пишешь свой загрузчик. И вдруг тебе нужно понять: а как устроена эта штука внутри? И почему на Windows всё иначе? Ты открываешь objdump, видишь кучу секций, заголовков, смещений — и теряешься. Или ты только что получил .exe-файл с Windows, а тебе нужно понять, как он отличается от .so или .elf, который ты привык видеть.
Эта статья — не теоретический обзор. Это пошаговый гайд, как реально смотреть внутрь ELF и PE, что искать, на что обращать внимание, и как не запутаться. Я покажу, как это делают в реальной работе — без воды, с конкретными командами, примерами и сравнением.
Что такое ELF и PE — просто, без заумностей
ELF (Executable and Linkable Format) — стандартный формат исполняемых файлов в Linux, BSD, Android и других Unix-подобных системах. Это не просто «файл программы». Это структура, которая говорит операционной системе: где лежат код, данные, таблицы импорта, символы отладки, и как всё это запустить.
PE (Portable Executable) — аналогичный формат, но для Windows. Он используется для .exe, .dll, .sys и других бинарников. По сути, это то же самое, что ELF, но с другим дизайном, другим синтаксисом и другими правилами.
Оба формата — это не просто «запускаемый код». Это контейнеры с метаданными, которые ОС читает перед запуском. Если ты не понимаешь, как устроен этот контейнер, ты не можешь уверенно анализировать, почему программа не запускается, почему она крашится, или как она взаимодействует с системой.
Как посмотреть структуру ELF — пошагово
Допустим, у тебя есть файл myapp на Linux. Ты хочешь понять, из чего он состоит. Вот что ты делаешь:
- Проверь, что это вообще ELF — просто запусти:
file myapp. Если видишьELF 64-bit LSB executable— всё в порядке. Если пишет «ASCII text» или «PE32+» — это не ELF. - Посмотри заголовок ELF:
readelf -h myapp. Ты увидишь:- Entry point address — где начинается выполнение.
- Type — это executable, shared object (.so), или relocatable?
- Machine — x86-64, ARM, MIPS?
- Number of program headers — сколько сегментов (LOAD, LOAD, DYNAMIC и т.д.)
- Смотри сегменты (Program Headers):
readelf -l myapp. Это то, что ОС читает при загрузке. Каждый сегмент — это кусок файла, который нужно загрузить в память. Важные типы:LOAD— код или данные, которые должны быть загружены в память.DYNAMIC— содержит информацию о динамических зависимостях (например, libc.so.6).INTERP— путь к интерпретатору (например,/lib64/ld-linux-x86-64.so.2).
- Смотри секции (Section Headers):
readelf -S myapp. Это для линковщика и отладчика. Не все секции попадают в память при запуске. Важные:.text— исполняемый код..data— инициализированные глобальные переменные..bss— неинициализированные глобальные переменные (в файле не занимают места, но выделяются в памяти)..dynsym,.dynstr— таблицы импорта/экспорта для динамической линковки..symtab,.strtab— символы для отладки (если не обрезаны).
- Проверь зависимости:
ldd myapp. Покажет, какие библиотеки он ищет в системе. Если видишьnot found— это причина краша. - Если нужно глубже — используй
objdump -d myappдля дизассемблирования кода, илиhexdump -C myapp | lessдля просмотра сырых байтов.
Это не всё. Но этого достаточно, чтобы понять: где код, какие библиотеки нужны, где инициализируются данные, и как программа запускается.
Как посмотреть структуру PE — аналогично, но по-другому
Теперь возьми Windows-файл: myapp.exe. Ты не можешь использовать readelf — он не поймёт PE. Тебе нужны другие инструменты.
- Проверь тип файла:
file myapp.exe— если пишетPE32+илиPE32, значит, это Windows-бинарник. - Посмотри заголовок PE: используй
objdump -x myapp.exe(если у тебя установлен MinGW или Cygwin) илиdumpbin /headers myapp.exeв командной строке Windows. Ты увидишь:- Machine — x64, x86, ARM64.
- Number of sections — сколько разделов в файле.
- Time date stamp — когда был скомпилирован.
- Size of optional header — ключевой параметр, он есть только у исполняемых файлов и DLL.
- Смотри разделы (Sections): в выводе
objdump -xилиdumpbin /sectionsты увидишь:.text— код (аналогично ELF)..data— инициализированные данные..rdata— только чтение (константы, строки)..bss— неинициализированные данные..rsrc— ресурсы (иконки, строки, манифесты)..reloc— таблица релокаций (нужна для ASLR).
- Импорт и экспорт:
dumpbin /imports myapp.exeпокажет, какие DLL он использует (например,kernel32.dll,msvcrt.dll).dumpbin /exports— если это DLL, какие функции она экспортирует. - Динамическая линковка: в PE всё хранится в таблице импорта (IAT — Import Address Table). В ELF это делается через
.dynsymи.got. В PE таблица импорта — это просто массив указателей на функции, которые заполняются загрузчиком при старте.
В PE есть ещё один важный элемент — директории (Data Directories). Их 16, и они указывают, где в файле находятся таблицы импорта, экспорта, ресурсов, исключений и т.д. Это нечто вроде «содержания» файла. Если ты смотришь на PE в Hex-редакторе — ищи сигнатуру 50 45 00 00 («PE\0\0»), а потом смещение к директориям — оно указано в заголовке.
Сравнение ELF и PE — таблица, которую реально использовать
Вот таблица, которую я распечатываю и держу под рукой, когда сравниваю бинарники. Не теория — только то, что реально влияет на анализ.
| Элемент | ELF (Linux) | PE (Windows) |
|---|---|---|
| Основной заголовок | Elf64_Ehdr — 64 байта, фиксированная структура |
IMAGE_NT_HEADERS — переменный размер, начинается с сигнатуры PE\0\0 |
| Загрузка в память | Сегменты (PT_LOAD) — указывают, где и как загружать |
Разделы (.text, .data) — загружаются по описанию в Optional Header |
| Точка входа | Поле e_entry в заголовке ELF |
Поле AddressOfEntryPoint в Optional Header |
| Динамическая линковка | .dynamic секция + .dynsym, .dynstr, .got |
Таблица импорта (IAT) — в разделе .idata |
| Импорт функций | Символы в .dynsym, имена в .dynstr |
Таблица импорта с именами DLL и функций, адреса заполняются при загрузке |
| Экспорт функций | Нет стандартного механизма — используется .symtab для отладки |
Таблица экспорта в .edata — обязательна для DLL |
| Ресурсы | Нет встроенного механизма — хранятся как данные в .data | Отдельный раздел .rsrc с деревом ресурсов (иконки, строки, манифесты) |
| Релокации | При компиляции обычно отключены (PIE — положение независимый код) | Обязательны для ASLR — таблица .reloc в PE |
| Интерпретатор | PT_INTERP — путь к /lib64/ld-linux-x86-64.so.2 |
Нет — загрузчик Windows встроен в ОС |
| Инструменты анализа | readelf, objdump, hexdump, nm |
dumpbin, objdump (MinGW), PE Explorer, CFF Explorer |
Вот что важно запомнить: в ELF всё построено вокруг сегментов и динамических таблиц. В PE — вокруг разделов и директорий. ELF гибче, PE — более структурирован, но жёстче. Если ты видишь в PE отсутствие таблицы импорта — это либо вредоносный код, либо статически скомпилированная программа. Если в ELF нет .dynamic — он, скорее всего, статический.
Что выбрать: ELF или PE — в зависимости от твоей задачи
Ты не выбираешь между ELF и PE. Ты выбираешь, как анализировать тот бинарник, который у тебя есть.
- Если ты работаешь с Linux-бинарником — используй
readelf -hиreadelf -lкак первую команду. Если хочешь понять, какие библиотеки нужны —ldd. Если нужно посмотреть код —objdump -d. - Если ты получил .exe с Windows — открой его в
dumpbin(если есть Visual Studio) или вobjdump -x(если есть MinGW). Ищи/importsи/exports. Если нет — возможно, это упакованный или обфусцированный файл. - Если ты переносишь код с Linux на Windows — не жди, что .so станет .dll. Это разные форматы. Тебе нужно перекомпилировать исходники под PE. Анализ ELF не поможет тебе понять, как работает PE-эквивалент.
- Если ты анализируешь вредоносное ПО — смотри на отсутствие импорта в PE (это признак статической линковки или обфускации), или на наличие множества
PT_LOADсегментов в ELF (возможно, это мульти-стадийный загрузчик). - Если ты пишешь свой загрузчик — ELF проще: ты читаешь сегменты, загружаешь их в память, прыгаешь по
e_entry. В PE тебе нужно парсить Optional Header, искать IAT, обрабатывать релокации — сложнее, но более стандартизировано.
Частые ошибки — и как их избежать
Я видел, как люди тратят часы, потому что делали одно из этого:
- Смотрят только на
.text— и думают, что это весь код. А в ELF там может быть только часть. Остальное — в другихPT_LOADсегментах. В PE — в.rdataили.textс разными атрибутами. - Путают секции и сегменты — в ELF
readelf -Sпоказывает секции (для линковки), аreadelf -l— сегменты (для загрузки). В PEdumpbin /sectionsпоказывает и то, и другое — потому что в PE нет чёткого разделения. Это путает. - Игнорируют зависимости — если
lddпоказываетnot found, это не «ошибка компиляции», это причина краша. Аналогично — если в PE нет импортаkernel32.dll, но программа используетCreateFile— это невозможно. Значит, ты смотришь не на тот файл, или он упакован. - Думают, что PE-файл — это «более сложный ELF» — нет. Это другой формат с другой логикой. Не пытайся «перевести» ELF-понятия в PE. Они не совпадают.
- Используют только GUI-инструменты — CFF Explorer или IDA Pro — это круто, но если ты не умеешь читать
readelf -h, ты не поймёшь, что видишь. GUI скрывает структуру. Сначала — команды, потом — графика.
Как лучше делать — практические рекомендации
Вот что я делаю в реальной работе:
- Всегда начинай с
file— это 2 секунды, но спасает от траты времени на неправильный файл. - Для ELF:
readelf -h→readelf -l→ldd→objdump -d. Это цепочка, которую я повторяю для каждого файла. Не пропускаю. - Для PE:
dumpbin /headers→dumpbin /imports→dumpbin /exports→dumpbin /disasm. Еслиdumpbinнедоступен — используюobjdump -xв MinGW. - Сравниваю размеры: если ELF-файл весит 800 КБ, а PE-аналог — 12 МБ — это не «лучше». Это значит, что в PE всё статически скомпилировано, или есть ресурсы. Проверяю
.rsrcв PE — часто там встроенные иконки, манифесты, логотипы. - Ищу аномалии: в ELF — если есть 5
PT_LOADсегментов, а в .text только один — это подозрительно. В PE — если нет.relocи программа запускается на любой системе — это либо старый код, либо обфускация. - Сохраняю выводы в текст: я копирую вывод
readelf -hиreadelf -lв файл и отмечаю ключевые моменты: «импорт: libc.so.6», «точка входа: 0x401230», «интерпретатор: /lib64/ld-linux-x86-64.so.2».
Итог: что делать прямо сейчас
У тебя есть бинарник. Не знаешь, что с ним делать?
Если это Linux-файл (.elf, .so, без расширения):
- Запусти:
file имя_файла - Запусти:
readelf -h имя_файла— запомни точку входа и тип - Запусти:
readelf -l имя_файла— посмотри, какие сегменты загружаются - Запусти:
ldd имя_файла— убедись, что все библиотеки есть
Если это Windows-файл (.exe, .dll):
- Запусти:
file имя_файла - Запусти:
dumpbin /headers имя_файла(илиobjdump -x) — найди Machine и Number of Sections - Запусти:
dumpbin /imports имя_файла— посмотри, какие DLL требуются - Если нет импорта — проверь, не упакован ли файл (например, UPX)
Если ты это сделал — ты уже знаешь больше 90% людей, которые «анализируют бинарники». Ты не просто «открыл в Hex-редакторе». Ты понимаешь, как файл устроен. И теперь ты можешь ответить на вопрос: «Почему он не запускается?», «Что он делает?», «Как его перенести?» — без паники и без гугления.
Практикуй это на 3-5 файлах. Сначала на простых — /bin/ls, /usr/bin/python3. Потом на своих. Через неделю ты будешь смотреть на бинарник и сразу понимать: «Это ELF, статический, без импорта — значит, не использует динамические библиотеки». Или: «Это PE с ресурсами, но без импорта — значит, всё вшито». Это не магия. Это опыт. И ты уже на пути к нему.
Информация в этой статье носит ознакомительный характер. Анализ бинарных файлов может быть связан с юридическими и этическими рисками — особенно если файлы не принадлежат вам или не были получены законным путём. Всегда действуй в рамках закона и с разрешения владельца.
