📄 Тайны Windows: Разбираем PE-файлы как настоящий детектив!

Тайны Windows: Разбираем PE-файлы как настоящий детектив!

PE-заголовок

Привет! ? Вы когда-нибудь задумывались, как Windows понимает, что запускать, когда вы кликаете на game.exe? Всё дело в PE-формате (Portable Executable) — секретном языке Windows для программ и библиотек. Сегодня мы станем цифровыми археологами и разберём его по кирпичикам! ?️‍♂️

❓ Что такое PE-файл?

PE (Portable Executable) — это стандартный формат исполняемых файлов в Windows. Под эту «шапку» попадают:

  • .exe (программы)
  • .dll (библиотеки)
  • .sys (драйверы)
  • .ocx (ActiveX-компоненты)

Представьте его как контейнер с инструкциями для ОС: «Где код?», «Какие библиотеки нужны?», «Сколько памяти выделить?». Без PE Windows просто не поймёт, что делать с файлом.

? Анатомия PE-файла: Главные «органы»

Структура напоминает слоёный пирог (или луковицу ?). Двигаемся от начала файла вглубь:

  1. DOS-заголовок (IMAGE_DOS_HEADER)
  2. Исторический артефакт! Содержит:
  3. Сигнатуру MZ (инициалы создателя — Марка Збиковского).
  4. Поле e_lfanew — ключевой указатель на PE-заголовок (наше «золото»!).
typedef struct _IMAGE_DOS_HEADER {
WORD e_magic; // "MZ" (0x5A4D)
// ... другие поля ...
LONG e_lfanew; // Смещение до PE-заголовка!
} IMAGE_DOS_HEADER;
  1. DOS-Stub
  2. Маленькая программа, которая выводит: «Эта программа не может быть запущена в DOS». Напоминание о древних временах! ?
  3. PE-сигнатура (4 байта)
  4. Буквы PE\0\0 (код: 0x50450000). Если их нет — файл битый или это не PE!
  5. COFF-заголовок (IMAGE_FILE_HEADER)
  6. Технические характеристики:
  7. Архитектура (x86/x64)
  8. Количество секций
  9. Временная метка компиляции
  10. Признаки (DLL? Исполняемый?).
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; // Архитектура (0x8664 = x64)
WORD NumberOfSections; // Число секций!
DWORD TimeDateStamp; // Когда скомпилирован?
// ... и другие поля ...
} IMAGE_FILE_HEADER;
  1. Опциональный заголовок (IMAGE_OPTIONAL_HEADER)
  2. Несмотря на имя — обязателен для EXE! Содержит «магию»:
  3. Magic Number: 0x10B (32-бит) или 0x20B (64-бит).
  4. Entry Point (RVA) — Адрес первой инструкции!
  5. ImageBase — Виртуальный адрес загрузки в память.
  6. Размеры кода/данных.
  7. 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;
}

Что делает код:

  1. Открывает файл в бинарном режиме.
  2. Проверяет DOS-заголовок и сигнатуру MZ.
  3. Переходит к PE-заголовку через e_lfanew.
  4. Проверяет сигнатуру PE\0\0.
  5. Читает COFF-заголовок и выводит архитектуру/число секций.
  6. Читает Optional Header и показывает точку входа.

? Зачем это знать?

  • Отладка: Понимание структуры помогает анализировать сбои.
  • Безопасность: Анализ вредоносных программ (malware) начинается с разбора PE.
  • Оптимизация: Ручная настройка секций может уменьшить размер файла.
  • Reverse Engineering: Без этого — как без карты в джунглях! ?

? Заключение

PE-формат — фундамент Windows-программ. Теперь вы знаете его «скелет»! ? Чтобы углубиться:

  • Изучите Data Directories (особенно импорт/экспорт).
  • Поэкспериментируйте с утилитой PEView или CFF Explorer.

У вас есть сила понимать Windows изнутри! ? Делитесь своими находками в комментариях — что самое интересное вы обнаружили в PE-файлах? ?

// КОММЕНТАРИИ (0)

// ОСТАВИТЬ КОММЕНТАРИЙ

// Ваш email не будет опубликован. Обязательные поля помечены *