Когда вы дважды щёлкаете по файлу, Windows за доли секунды решает, можно ли его выполнить и как именно. Решение принимается не по расширению .exe, как многие привыкли думать, а по содержимому файла: специальным сигнатурам и структурам в его начале. Расширение влияет лишь на то, запустит ли систему Проводник попытку запуска, но окончательный вердикт выносит загрузчик на основе анализа байтов. Понимание этого механизма объясняет, почему переименованный текстовый файл с расширением .exe не запустится, а файл без расширения, но с корректной внутренней структурой, может быть выполнен вручную.
В этой статье разберём весь путь: от первых двух байтов файла до передачи управления коду программы. Материал будет полезен разработчикам, системным администраторам и всем, кто хочет понимать, что именно проверяет операционная система перед запуском программы.
- Первый фильтр: расширение и ассоциации
- Сигнатура MZ: первые два байта
- Структура PE-формата: что лежит внутри EXE
- Как определяется разрядность и совместимость
- Подсистемы: консоль, GUI и драйверы
- Импорт, зависимости и подготовка к запуску
- Проверки безопасности при запуске
- Почему расширения .exe, .dll и .sys различаются, если формат один
- Сравнение способов определения типа файла
- Типичные вопросы и заблуждения
- Что делать дальше: практические выводы
Первый фильтр: расширение и ассоциации
Прежде чем система заглянет внутрь файла, срабатывает самый поверхностный механизм — расширение имени. Проводник использует его, чтобы выбрать действие: для .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), корректные поля, характерные для драйверов, и совместимость с моделью драйверов текущей версии системы.
Импорт, зависимости и подготовка к запуску
Определив, что файл структурно корректен и совместим с системой, загрузчик переходит к практической подготовке процесса:
- Читает таблицу импорта — список DLL и функций, которые нужны программе.
- Загружает каждую требуемую библиотеку (рекурсивно обрабатывая их собственные импорты). Если нужная DLL отсутствует или в ней нет ожидаемой функции, запуск прерывается с сообщением о недостающем компоненте.
- Резервирует адресное пространство и отображает секции файла в память с правами, заданными в таблице секций: код — на исполнение, данные — на чтение и запись.
- Выполняет релокации, если модуль не удалось загрузить по предпочтительному адресу (актуально для библиотек и при включённом ASLR).
- Передаёт управление на точку входа, адрес которой указан в опциональном заголовке. С этого момента выполняется код самой программы — сначала инициализационный код среды выполнения, затем логика приложения.
Точка входа — важный нюанс: она указывает не на функцию 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-заголовок → зависимости → точка входа» превращает загадочные ошибки запуска в диагностируемые ситуации: всегда можно определить, на каком этапе система отказалась выполнять файл, и что именно нужно исправить.
