Как Windows определяет тип исполняемого файла

Когда вы дважды щёлкаете по файлу, Windows за доли секунды решает, можно ли его выполнить и как именно. Решение принимается не по расширению .exe, как многие привыкли думать, а по содержимому файла: специальным сигнатурам и структурам в его начале. Расширение влияет лишь на то, запустит ли систему Проводник попытку запуска, но окончательный вердикт выносит загрузчик на основе анализа байтов. Понимание этого механизма объясняет, почему переименованный текстовый файл с расширением .exe не запустится, а файл без расширения, но с корректной внутренней структурой, может быть выполнен вручную.

В этой статье разберём весь путь: от первых двух байтов файла до передачи управления коду программы. Материал будет полезен разработчикам, системным администраторам и всем, кто хочет понимать, что именно проверяет операционная система перед запуском программы.

Первый фильтр: расширение и ассоциации

Прежде чем система заглянет внутрь файла, срабатывает самый поверхностный механизм — расширение имени. Проводник использует его, чтобы выбрать действие: для .exe и .com это запуск, для .txt — открытие в редакторе, для .dll — обычно ничего (библиотеку напрямую не выполняют). Эти соответствия хранятся в реестре и могут меняться настройками системы.

Важно понимать ограничения этого этапа. Расширение — это просто часть имени файла, которую пользователь может свободно изменить. Поэтому оно служит подсказкой для оболочки, а не гарантией типа содержимого. Файл virus.txt.exe выглядит в проводнике как текстовый документ, если отображение расширений отключено, но при запуске будет исполнен как программа. Отсюда практический совет: включите показ расширений файлов, чтобы видеть реальное имя того, что собираетесь открыть.

Сигнатура MZ: первые два байта

Если оболочка решила запустить файл, подключается загрузчик Windows (в современных версиях это компонент ядра и подсистема создания процессов). Первое, что он проверяет внутри файла, — два начальных байта. У всех исполняемых файлов Windows они образуют ASCII-символы «MZ» — инициалы Марка Збиковски, архитектора формата исполняемых файлов MS-DOS. Эта сигнатура сохранилась с 1980-х годов и остаётся обязательной.

Если первые байты не равны 0x4D 0x5A («MZ»), файл отвергается сразу: система сообщит, что приложение не является допустимым образом Win32 или имеет повреждённый заголовок. Именно поэтому переименованный документ или архив не запустится, каким бы ни было его расширение.

Структура PE-формата: что лежит внутри EXE

Все современные исполняемые файлы Windows — программы, библиотеки DLL, драйверы режима ядра (SYS), экранные заставки (SCR) и элементы панелей управления (CPL) — используют один и тот же контейнер: формат PE (Portable Executable). Различаются они не структурой, а значениями полей внутри неё.

Устройство PE-файла упрощённо выглядит так:

  • Заголовок DOS — начинается с сигнатуры MZ и содержит смещение к PE-заголовку; исторически здесь же размещалась небольшая DOS-программа-заглушка, которая выводит сообщение вроде «This program cannot be run in DOS mode», если файл пытаются запустить в среде DOS.
  • PE-сигнатура — четыре байта «PE\0\0» (0x50 0x45 0x00 0x00) по адресу, указанному в DOS-заголовке. Это второй контрольный рубеж проверки.
  • Заголовок COFF — описывает целевую архитектуру процессора (поле Machine), количество секций, метку времени компиляции и характеристики файла.
  • Опциональный заголовок — несмотря на название, обязателен для исполняемых образов. Здесь заданы магическое число (PE32 для 32-битных файлов, PE32+ для 64-битных), точка входа, базовые адреса загрузки, требования к версии подсистемы и её тип (консольное или графическое приложение).
  • Таблица секций — перечень секций с правами доступа: исполняемый код (.text), данные (.data, .rdata), ресурсы (.rsrc), таблица импорта и другие.
  • Сами секции — содержимое файла: машинный код, данные, ресурсы, строки импорта и экспорта.

Загрузчик последовательно валидирует эти структуры. Ошибка на любом этапе — неверная сигнатура, некорректные размеры секций, выходящие за пределы файла, противоречивые поля — приводит к отказу в запуске с соответствующим кодом ошибки.

Как определяется разрядность и совместимость

Из опционального заголовка система узнаёт, какой код содержит файл. Магическое число 0x10B соответствует 32-битному формату PE32, а 0x20B — 64-битному PE32+. Поле Machine уточняет целевой процессор: x86, x64, ARM, ARM64 и другие. На его основе загрузчик решает, сможет ли данный процессор выполнить код напрямую.

Здесь работает несколько уровней совместимости:

  • 64-битная Windows не может исполнять 16-битные приложения; их поддержка была удалена из 64-битных выпусков системы.
  • 64-битная Windows выполняет 32-битные x86-программы через подсистему WOW64, которая транслирует обращения к системе между 32-битным процессом и 64-битным ядром.
  • На процессорах ARM64 эмуляция позволяет запускать и x86-, и x64-приложения, хотя производительность эмулируемого кода ниже нативного.
  • Если архитектура файла не поддерживается ни напрямую, ни через эмуляцию, запуск завершится ошибкой несовместимости.

Отдельный случай — файлы, помеченные как пригодные только для управляемого кода .NET. В них машинный код минимален: небольшая заглушка передаёт управление среде CLR, которая уже компилирует промежуточный язык CIL в инструкции конкретного процессора. Для загрузчика такой файл выглядит как обычный PE-образ с особыми записями в каталоге данных.

Подсистемы: консоль, GUI и драйверы

Поле Subsystem в опциональном заголовке сообщает, в каком окружении должен работать процесс. Значение CUI означает консольное приложение: Windows создаст для него окно консоли, если запуск произошёл не из командной строки. Значение GUI — графическое приложение, которому консоль не нужна. Существуют и другие подсистемы, например NATIVE для процессов, работающих до полноценной инициализации окружения Win32, — так устроены некоторые системные компоненты.

Драйверы режима ядра тоже оформлены как PE-файлы, но загружает их не обычный загрузчик процессов, а менеджер ввода-вывода ядра, который предъявляет к образу дополнительные требования: обязательную цифровую подпись (для 64-битных версий Windows), корректные поля, характерные для драйверов, и совместимость с моделью драйверов текущей версии системы.

Импорт, зависимости и подготовка к запуску

Определив, что файл структурно корректен и совместим с системой, загрузчик переходит к практической подготовке процесса:

  1. Читает таблицу импорта — список DLL и функций, которые нужны программе.
  2. Загружает каждую требуемую библиотеку (рекурсивно обрабатывая их собственные импорты). Если нужная DLL отсутствует или в ней нет ожидаемой функции, запуск прерывается с сообщением о недостающем компоненте.
  3. Резервирует адресное пространство и отображает секции файла в память с правами, заданными в таблице секций: код — на исполнение, данные — на чтение и запись.
  4. Выполняет релокации, если модуль не удалось загрузить по предпочтительному адресу (актуально для библиотек и при включённом ASLR).
  5. Передаёт управление на точку входа, адрес которой указан в опциональном заголовке. С этого момента выполняется код самой программы — сначала инициализационный код среды выполнения, затем логика приложения.

Точка входа — важный нюанс: она указывает не на функцию main или WinMain, как думают начинающие разработчики, а на стартовый код, добавляемый компоновщиком. Он инициализирует среду выполнения языка (например, CRT для C/C++), обрабатывает статические конструкторы и только потом вызывает основную функцию программы.

Проверки безопасности при запуске

Современные версии Windows добавляют к структурной валидации слои защиты, которые могут заблокировать даже корректный PE-файл:

  • Цифровая подпись Authenticode: для драйверов она обязательна, для обычных приложений — фактор репутации. SmartScreen и антивирусные механизмы оценивают распространённость и подпись файла и могут предупредить о малоизвестной программе.
  • Политики контроля приложений: AppLocker и WDAC позволяют администраторам разрешать выполнение только подписанных файлов или программ из доверенных каталогов.
  • Механизмы эксплуатации памяти: DEP запрещает исполнение кода из страниц данных, ASLR рандомизирует адреса загрузки, Control Flow Guard ограничивает непрямые переходы. Наличие этих флагов задаётся при сборке, а принудительное применение зависит от настроек системы.
  • Антивирусная проверка: Defender и сторонние решения сканируют образ до или во время запуска и могут снять его с выполнения.

Поэтому сообщение об ошибке запуска не всегда означает повреждённый файл: причина может быть в политике организации, отсутствии подписи у драйвера или срабатывании защиты.

Почему расширения .exe, .dll и .sys различаются, если формат один

Как видно, внутренний формат у всех этих файлов общий. Расширение нужно прежде всего человеку и оболочке: оно подсказывает назначение файла и определяет поведение Проводника. Загрузчик же смотрит на поля PE-заголовка: у DLL установлен флаг, указывающий, что образ является библиотекой, у драйверов — свои комбинации характеристик. Теоретически библиотеку можно переименовать в .exe, и загрузчик откажется создавать из неё процесс именно из-за внутренних флагов, а не из-за имени.

Это разделение ролей удобно использовать на практике: если нужно быстро понять, что представляет собой незнакомый файл, достаточно посмотреть его первые байты в hex-редакторе. Сигнатура MZ укажет на исполняемый образ Windows, поле Machine — на архитектуру, а наличие экспорта без точки входа подскажет, что перед вами скорее библиотека.

Сравнение способов определения типа файла

Механизм Что проверяет Можно ли обмануть переименованием
Расширение имени Выбор действия в Проводнике и ассоциации Да, имя меняется свободно
Сигнатура MZ Принадлежность к семейству исполняемых форматов Нет, проверяются байты содержимого
PE-заголовок Архитектуру, разрядность, подсистему, точку входа Нет, требуется корректная структура
Флаги образа Является ли файл программой, библиотекой или драйвером Нет, флаги заданы внутри заголовков
Подпись и политики Доверие к издателю и правила выполнения Нет, подпись привязана к содержимому

Типичные вопросы и заблуждения

«Файл называется .exe, значит он исполняемый». Не обязательно. Без корректных MZ- и PE-сигнатур внутри загрузчик его отвергнет. И наоборот: скрипты PowerShell и пакетные файлы тоже «исполняются», но другим механизмом — интерпретатором, а не загрузчиком PE.

«Windows определяет тип файла по содержимому всегда». Для запуска — да, но для выбора значка и действия в Проводнике используется именно расширение. Поэтому вредоносные файлы часто маскируют расширением документа: оболочку это вводит в заблуждение, пока пользователь сам не запустит такой файл.

«Один и тот же EXE работает на любой версии Windows». Формат PE стабилен десятилетиями, поэтому старый файл обычно корректно распознаётся и новой системой. Но программа может зависеть от отсутствующих библиотек, устаревших API или неподдерживаемой архитектуры — тогда отказ произойдёт уже после успешного распознавания формата.

«DLL нельзя запустить». Как программу — действительно нельзя: флаги образа не позволят создать процесс. Но библиотека выполняет свой код, когда её функции вызывает другая программа, либо при явной загрузке через механизм LoadLibrary.

Что делать дальше: практические выводы

Главный принцип прост: расширение — это подсказка для оболочки, а истину о типе файла несёт его содержимое. Из этого следуют конкретные шаги для разных ситуаций:

  • Если файл не запускается с сообщением о недопустимом образе — проверьте, не повреждена ли загрузка и те ли это байты: сравните размер с источником, заново скачайте файл.
  • Если появляется ошибка о недостающей DLL — проблема не в распознавании формата, а в зависимостях: установите требуемую библиотеку или среду выполнения, указанную разработчиком.
  • Если система блокирует заведомо нужную программу — проверьте, не действует ли политика организации, состояние SmartScreen и антивируса, а для драйверов — наличие подписи.
  • Если нужно выяснить тип неизвестного файла — посмотрите первые байты в hex-редакторе или используйте встроенную команду file-подобных утилит; сигнатура MZ и поля PE дадут ответ надёжнее, чем имя файла.
  • Если вы разработчик — учитывайте, что требования к образу (разрядность, подсистема, флаги безопасности) задаются параметрами компоновщика, и именно они определяют, где и как файл сможет выполниться.

Понимание цепочки «расширение → сигнатура → PE-заголовок → зависимости → точка входа» превращает загадочные ошибки запуска в диагностируемые ситуации: всегда можно определить, на каком этапе система отказалась выполнять файл, и что именно нужно исправить.

PEFile.ru