PDA

Просмотр полной версии : FASM - формат и назначение манифестов


Marylin
25.04.2026, 15:38
В программинге существует много деталей, которые отделяют любительскую поделку от профессионального софта. Одной из таких деталей является манифест приложения - XML-файл, который управляет тем, как ОС взаимодействует с программой. В Visual Studio и других IDE этот файл добавляется парой кликов мыши. Но как быть, если мы пишем на чистом ассемблере? В отличие от распространённого мнения, fasm предоставляет элегантные механизмы для встраивания манифестов. Более того, работа с ними на ассме даёт нам полный контроль над каждым байтом ресурсов. В этой статье мы разберём, что такое манифесты, зачем они нужны, и как правильно встраивать их в наши проекты.

1. Введение
2. Ресурс RT_MANIFEST
3. Информация о версии VERSIONINFO
4. Реализация на практике
5. Под занавес

1. Введение

Манифест - это XML, который встраивается в исполняемый EXE/DLL в качестве ресурса. ОС читает этот файл при запуске программы, чтобы определить её поведение. Представьте манифест как паспорт вашей программы. В нём указано:

1. Кто вы (идентификатор сборки)
2. Что вам нужно (зависимости от библиотек элементов управления comctl32.dll)
3. Какие у вас права (требуется ли запуск от имени админа)
4. С какой версией Win вы совместимы (важно для корректной работы таких функций как GetVersionEx).

При отсутствии манифеста ОС считает, что перед ней стандартная программа для XP, и применяет к ней устаревшие правила поведения. Именно из-за отсутствия правильного манифеста многие ASM-программы выглядят древними, и имеют проблемы с отображением интерфейса на современных Win10/11.

1.1. Зачем нужен манифест вFASM-проектах?

Манифест критически важен по нескольким причинам:

• Активация новых визуальных стилей Common Controls v6.0. Без манифеста кнопки, полосы прокрутки и другие элементы управления будут иметь вид из Win2k. Манифест же подключает современную либу comctl32.dll, даруя программе современный внешний вид (тени, прозрачность, округлые края, и т.п).

• Правильное определение версии ОС. Начиная с Win8 функция

GetVersionEx()

перестала возвращать реальную версию системы, если приложение не имеет совместимого манифеста. Без него программа всегда будет думать, что работает на Win8, даже если запущена на последней Win11.

• Запрос прав админа через UAC (User Account Control). Если коду нужно писать в системные каталоги или реестр, она должна запросить повышение прав. Манифест позволяет указать уровень привилегий:

asInvoker

= права текущего юзера,

requireAdministrator

= требовать права админа, или

highestAvailable

= макс.доступные.

• Профессиональный вид. Встраивание манифеста с описанием программы, версии и другой информацией через ресурс VERSIONINFO, придаёт приложению ухоженный вид.

2. Тип ресурса "RT_MANIFEST"

Для встраивания используется специальный ресурс RT_MANIFEST, константа которого равна 24 (см.инклуд фасма \equates\kernel32.inc). Для стандартного EXE используется ID=1. Существует два способа встроить XML манифеста:

• Внедрение из внешнего файла (рекомендуется для больших XML)
• Встраивание XML прямо в код (удобно для коротких манифестов)

2.1. Подключение внешнегоXML-файла

Это самый чистый и поддерживаемый метод. Вы сохраняете манифест в отдельный файл (например, app.manifest), а затем в fasm указываете путь к этому файлу с помощью директивы

file

. Вот структура файла app.manifest (минимальный пример):

XML:






Код для подключения этого файла:

C-подобный:



section
'.rsrc'
resource data readable
;
// Определяем каталог для манифестов
directory RT_MANIFEST
,
manifests
;
// Определяем сам ресурс: ID=1, язык нейтральный, данные из блока 'resmanifest'
resource manifests
,
1
,
LANG_NEUTRAL
,
resmanifest
;
// Подключаем внешний файл!
resdata resmanifest
file
'app.manifest'
endres


2.2. ВстраиваниеXMLнепосредстве но в код

Данный метод удобен, когда манифест короткий (например только для UAC, или только для визуальных стилей). XML просто вставляется как строка байт с помощью db. Пример для запроса прав админа (requireAdministrator) будет выглядеть так:

C-подобный:



section
'.rsrc'
resource data readable

directory RT_MANIFEST
,
manifests
resource manifests
,
1
,
LANG_NEUTRAL
,
resmanifest
;
// XML-строка прямо в коде
resdata resmanifest
db
''
db
''
db
' '
db
' '
db
' '
db
' '
db
' '
db
' '
db
' '
db
' '
db
''
endres


После сборки и запуска программы с таким манифестом, Win будет показывать диалог UAC, запрашивая разрешение на выполнение с правами админа. Определить, что манифест исправно подключён к программе можно по наличию "щита" на иконке исполняемого файла, как это показано ниже (слева xml, справа exe).

https://forum.antichat.xyz/attachments/4951807/img_eea027d691.png

2.3. Альтернативный метод

Хотя встраивание манифеста в ресурсы является предпочтительным и надёжным способом, существует ещё вариант. Если вы поместите файл с именем MySoft.exe.manifest в ту же папку, что и MySoft.exe, системный загрузчик образов подхватит его на автомате. Этот метод удобен на этапе отладки, но для финальной сборки использовать его не рекомендуется по сл.причинам:

• При копировании программы можно забыть скопировать манифест.
• Внешние файлы могут быть изменены/удалены антивирусами или юзером.
• Это выглядит непрофессионально.

3. Добавление ресурса VERSIONINFO

Хотя манифест - это важнейший ресурс, профессиональное приложение должно содержать и блок VERSIONINFO. Именно эти данные отображаются в свойствах файла, когда мы кликаем по нему правой кнопкой мыши. В fasm для его создания используется спец.структура, и директива

versioninfo

.

C-подобный:



section
'.rsrc'
resource data readable
;
// Определяем каталоги: RT_VERSION и RT_MANIFEST
directory RT_VERSION
,
versions
,
\
RT_MANIFEST
,
manifests
;
// Описываем ресурс версии: ID=1, язык нейтральный, данные из блока 'version'
resource versions
,
1
,
LANG_NEUTRAL
,
version
;
// Блок VERSIONINFO
versioninfo version
,
VOS__WINDOWS32
,
VFT_APP
,
VFT2_UNKNOWN
,
LANG_ENGLISH
+
SUBLANG_DEFAULT
,
0
,
\
'FileDescription'
,
'Краткое описание приложения'
,
\
'LegalCopyright'
,
'(C)2024, античат '
,
\
'FileVersion'
,
'1.1.0.0'
,
\
'ProductVersion'
,
'1.0.0.0'
,
\
'OriginalFilename'
,
'MyApp.exe'
;
// Описываем ресурс манифеста
resource manifests
,
1
,
LANG_NEUTRAL
,
resmanifest

resdata resmanifest
file
'app.manifest'
endres


Обратите внимание, что оба ресурса и манифест и версия имеют одинаковый идентификатор

ID=1

. Это принципиально важно, т.к. при других значениях загрузчик посчитает блок невалидным и проигнорирует его. Но как могут быть одинаковые ID у двух ресурсов, ведь по логике вещей это не допустимо?

Короткий ответ:

ID=1

в разных типах ресурсов

RT_MANIFEST

и

RT_VERSION

- это разные пространства имён. Они не конфликтуют, потому что идентифицируются не только по ID, а именно парой

Type + ID

. В секции .rsrc ресурсы организованы в трёхуровневую иерархию:


1. Type: RT_VERSION(16), RT_MANIFEST(24), RT_DIALOG(5) и т.д.



2. Name/ID: Уникальный идентификатор внутри этого типа.



3. Language: LANG_ENGLISH, LANG_NEUTRAL и т.д.


Обычно код создаёт в секции .rsrc три разные ветки:


• Ветка RT_DIALOG -> ID 37 -> Lang



• Ветка RT_MANIFEST -> ID 01 -> Lang



• Ветка RT_VERSION -> ID 01 -> Lang


Для загрузчика образов

RT_MANIFEST#1

и

RT_VERSION#1

- это совершенно разные объекты, как файлы C:\Windows\System32\kernel32.dll и D:\MyProject\kernel32.dll. Путь к ним разный, поэтому они живут мирно. Так почему-же версия не отображается, если ID не 1?

Секрет в том, как система ищет инфу о версии. Когда в свойствах файла мы выбираем "Подробно", под катом вызывается

GetFileVersionInfo()

с таким алго: -"Найти в секции ресурсов тип RT_VERSION(16) с идентификатором VS_VERSION_INFO(1)". Если мы ставим ID=1, Win находит ресурс и версия отображается. Если-же задать ID=2 (42, 1000), api ищет ID=1, не находит его и молча решает «Версии нет». Никакой ошибки не будет, просто поля версии в свойствах файла останутся пустыми.

А почему-же манифест работает с ID=1?
Здесь похожая, но не такая строгая история. Win подхватывает манифест в нескольких случаях:

1. Встроенный, ищется по хард

ID=1

.
2. Внешний (appname.exe.manifest). Если встроенного

ID=1

нет, загрузчик ищет манифест рядом с EXE.
3. Динамическая загрузка через

FindResource() + LoadResource()

. Если мы врукопашную ищем манифест, то сработает любой ID, однако при старте процесса загрузчик этого не делает.

Вот сводная таблица разных вариантов:


Ресурс Тип ID Как работает



----------- --- -- -------------



RT_MANIFEST 24 1 Загрузчик ищет строго ID=1 для встроенного манифеста.



RT_VERSION 16 1 GetFileVersionInfo() ищет строго ID=1.



RT_DIALOG 05 37 DialogBoxParam() использует ID=37, хотя будет работать и при ID=1.


4. Реализация на практике

Ниже представлен код программы, которая запрашивает желаемую привилегию (как у текущего юзера, админ, или макс.возможную), и на основании выбора генерит готовый файл-манифеста, который можно будет подключить к своей программе. Помимо того, что код создаёт внешний XML-файл, он и сам в своей тушке имеет оба ресурса - и

RT_VERSION

и

RT_MANIFEST

для отображения визуальных стилей Win10/11.

Как результат получим такое окно. Чтобы обозначить разницу, здесь я сделал 2 скрина - слева с манифестом, а справа без. Как видим стиль кнопок приобрёл уже другой вид: с голубой подсветкой, края округлённые, а если в окне были-бы и другие элементы управления (полосы прокрутки, списки комбо/лист-бокс, и прочее), то изменились-бы и они на более современные. В общем профит от манифестов на лицо.

https://forum.antichat.xyz/attachments/4951807/img_08949f2701.png
Просмотреть содержимое манифеста (и даже подправить пару строк) можно в крутой тулзе "CFF-Explorer" как показано на скрине ниже. Ну и конечно специально заточенная для этих нужд утилита "Resource Hacker" позволяет создавать манифесты из своих шаблонов - вписываем в строку "Name" имя своей прожки, и всё пучком. Одним словом, здесь есть из чего выбирать:

https://forum.antichat.xyz/attachments/4951807/img_197f12bd34.png

https://forum.antichat.xyz/attachments/4951807/img_130c3363f0.png
А по коду, после того-как получили дескриптор открытого файла (здесь я использовал функцию _lopen(), которая под катом вызывает CreateFile() с дефолтными настройками безопасности), можно в любой момент сбрасывать в него фрагменты данных, и они будут дописываться в конец по текущему указателю. Этот указатель хранится в файловом объекте ядра, и при каждой записи увеличивается на указанное при вызове

_lwrite()

кол-во байт. Он будет всегда смотреть в конец файла, пока мы не закроем дескриптор по

CloseHandle()

. Вот внутренняя структура любого файла в отладчике WinDbg, где по смещению(0x68) лежит позиция указателя в файле:

Код:



0: kd> dt _file_object
nt!_FILE_OBJECT
+0x000 Type : Int2B
+0x002 Size : Int2B
+0x008 DeviceObject : Ptr64 _DEVICE_OBJECT
+0x010 Vpb : Ptr64 _VPB Атрибуты доступа
+0x04e SharedWrite : UChar |
+0x04f SharedDelete : UChar |
+0x050 Flags : Uint4B ---+
+0x058 FileName : _UNICODE_STRING
+0x068 CurrentByteOffset : _LARGE_INTEGER


5. Заключение

Манифесты в FASM это не магия и не проблема. Освоив технику вы перестаёте быть "тем парнем, который пишет ассм под DOS", и становитесь проф.разработчиком под Windows, способным создавать современные приложения с правильным поведением и внешним видом. Используйте директиву

resdata

для встраивания XML, не забывайте про

RT_MANIFEST

и

ID=1

, добавляйте

VERSIONINFO

для информации о файле, и ваши программы будут выглядеть так, как будто они написаны на самом современном компиляторе. В мире FASM нет границ: если вы можете описать это в коде - ассемблер это соберёт.

Ссылки по теме

MSDN - Описание всех элементов манифеста:
Манифесты приложения - Win32 apps

MSDN - Включение визуальных стилей:
Включение визуальных стилей - Win32 apps

Зачем приложению манифест:
Зачем Win32-приложению манифест?