Привет! 👋 Вы когда-нибудь задумывались, как Windows понимает, что запускать, когда вы кликаете на game.exe? Всё дело в PE-формате (Portable Executable) - секретном языке Windows для программ и библиотек. Сегодня мы станем цифровыми археологами и разберём его по кирпичикам! 🕵️♂️
❓ Что такое PE-файл?
PE (Portable Executable) - это стандартный формат исполняемых файлов в Windows. Под эту "шапку" попадают:
- .exe (программы)
- .dll (библиотеки)
- .sys (драйверы)
- .ocx (ActiveX-компоненты)
Представьте его как контейнер с инструкциями для ОС: "Где код?", "Какие библиотеки нужны?", "Сколько памяти выделить?". Без PE Windows просто не поймёт, что делать с файлом.
🧩 Анатомия PE-файла: Главные "органы"
Структура напоминает слоёный пирог (или луковицу 😄). Двигаемся от начала файла вглубь:
- DOS-заголовок (IMAGE_DOS_HEADER)
- Исторический артефакт! Содержит:
- Сигнатуру MZ (инициалы создателя - Марка Збиковского).
- Поле e_lfanew - ключевой указатель на PE-заголовок (наше "золото"!).
typedef struct _IMAGE_DOS_HEADER {
WORD e_magic; // "MZ" (0x5A4D)
// ... другие поля ...
LONG e_lfanew; // Смещение до PE-заголовка!
} IMAGE_DOS_HEADER;
- DOS-Stub
- Маленькая программа, которая выводит: "Эта программа не может быть запущена в DOS". Напоминание о древних временах! 💾
- PE-сигнатура (4 байта)
- Буквы PE\0\0 (код: 0x50450000). Если их нет - файл битый или это не PE!
- COFF-заголовок (IMAGE_FILE_HEADER)
- Технические характеристики:
- Архитектура (x86/x64)
- Количество секций
- Временная метка компиляции
- Признаки (DLL? Исполняемый?).
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; // Архитектура (0x8664 = x64)
WORD NumberOfSections; // Число секций!
DWORD TimeDateStamp; // Когда скомпилирован?
// ... и другие поля ...
} IMAGE_FILE_HEADER;
- Опциональный заголовок (IMAGE_OPTIONAL_HEADER)
- Несмотря на имя - обязателен для EXE! Содержит "магию":
- Magic Number: 0x10B (32-бит) или 0x20B (64-бит).
- Entry Point (RVA) - Адрес первой инструкции!
- ImageBase - Виртуальный адрес загрузки в память.
- Размеры кода/данных.
- Data Directories - Таблица указателей на критичные части (импорт, экспорт, ресурсы).
📚 Секции - где хранится "жизнь"
После заголовков идут секции - блоки с разным назначением. Каждая описывается структурой IMAGE_SECTION_HEADER:
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[8]; // Имя (например, ".text", ".rdata")
DWORD VirtualSize; // Размер в памяти
DWORD VirtualAddress; // RVA (адрес относительно ImageBase)
DWORD SizeOfRawData; // Размер на диске
DWORD PointerToRawData; // Смещение в файле
// ... атрибуты (код/данные/доступ) ...
} IMAGE_SECTION_HEADER;
Основные секции:
- .text: Код программы (инструкции CPU).
- .data: Инициализированные данные (глобальные переменные).
- .rdata: Только для чтения (строки, константы).
- .idata: Импорт функций (какие DLL и функции используем).
- .edata: Экспорт функций (если это DLL).
- .rsrc: Ресурсы (иконки, картинки, меню).
🔍 Читаем PE-файл на C++: Практика!
Вот как можно прочитать базовую информацию о PE-файле:
#include <iostream>
#include <fstream>
#include <Windows.h>
void AnalyzePE(const char* filename) {
std::ifstream file(filename, std::ios::binary);
if (!file) {
std::cerr << "Error opening file!" << std::endl;
return;
}
// Читаем DOS-заголовок
IMAGE_DOS_HEADER dosHeader;
file.read((char*)&dosHeader, sizeof(dosHeader));
// Проверяем сигнатуру "MZ"
if (dosHeader.e_magic != IMAGE_DOS_SIGNATURE) {
std::cerr << "Not a valid PE file!" << std::endl;
return;
}
// Переходим к PE-заголовку
file.seekg(dosHeader.e_lfanew, std::ios::beg);
DWORD peSignature;
file.read((char*)&peSignature, sizeof(peSignature));
// Проверяем "PE\0\0"
if (peSignature != IMAGE_NT_SIGNATURE) {
std::cerr << "PE signature not found!" << std::endl;
return;
}
// Читаем COFF-заголовок
IMAGE_FILE_HEADER fileHeader;
file.read((char*)&fileHeader, sizeof(fileHeader));
// Выводим информацию
std::cout << "🌟 PE File Info 🌟\n";
std::cout << "Architecture: " <<
(fileHeader.Machine == IMAGE_FILE_MACHINE_AMD64 ? "x64" : "x86") << "\n";
std::cout << "Number of Sections: " << fileHeader.NumberOfSections << "\n";
// Читаем Optional Header (32/64 зависит от Machine!)
if (fileHeader.SizeOfOptionalHeader > 0) {
IMAGE_OPTIONAL_HEADER optHeader;
file.read((char*)&optHeader, sizeof(optHeader));
std::cout << "Entry Point (RVA): 0x" << std::hex << optHeader.AddressOfEntryPoint << "\n";
}
}
int main() {
AnalyzePE("C:\\Windows\\notepad.exe");
return 0;
}
Что делает код:
- Открывает файл в бинарном режиме.
- Проверяет DOS-заголовок и сигнатуру MZ.
- Переходит к PE-заголовку через e_lfanew.
- Проверяет сигнатуру PE\0\0.
- Читает COFF-заголовок и выводит архитектуру/число секций.
- Читает Optional Header и показывает точку входа.
💡 Зачем это знать?
- Отладка: Понимание структуры помогает анализировать сбои.
- Безопасность: Анализ вредоносных программ (malware) начинается с разбора PE.
- Оптимизация: Ручная настройка секций может уменьшить размер файла.
- Reverse Engineering: Без этого - как без карты в джунглях! 🌴
🔚 Заключение
PE-формат - фундамент Windows-программ. Теперь вы знаете его "скелет"! 🦴 Чтобы углубиться:
- Изучите Data Directories (особенно импорт/экспорт).
- Поэкспериментируйте с утилитой PEView или CFF Explorer.
У вас есть сила понимать Windows изнутри! 💪 Делитесь своими находками в комментариях - что самое интересное вы обнаружили в PE-файлах? 🚀