Статья РЕ-файлы – знакомство с ресурсом "RT_VERSION"

Исполняемые файлы имеют фишку, на которую начинающие программисты редко обращают внимание – это директория "VERSION" в секции-ресурсов. Её отсутствие характеризует кодера не с лучшей стороны, если только он не упустил эту инфу умышленно, чтобы замести за собой следы. Все зарекомендовавшие себя легальные разработчики стараются полностью заполнять этот дир, ведь согласитесь, что свойства файла "OllyDbg" в правом окне рисунка ниже вызывают больше доверия, чем подноготная того-же шпиона "Kerberos" на левом..

Здесь, значение полей "тип, размер, дата" система получает своими методами, а не считывает их из секции-ресурсов. Остальные-же поля хранятся в каталоге "RT_VERSION" самого файла, и на выходе из этой статьи мы узнаем, как их туда поместить. Отметим, что заполнять свойства подобного рода можно только в исполняемых файлах типа РЕ-32 (Portable-Executable 32/64 бит), в числе которых: EXE, SYS, DRV, DLL, OCX и CPL:


Property.png



Оглавление:

1. Секция-ресурсов файла, и типы каталогов в ней;
2. Проблемы языковых стандартов:
3.0. Практика – перечисление всех языков и кодовых страниц системы;
3.1. Практика – чтение ресурса "VERSIONINFO" из файла;
4. Эпилог.
------------------------------------------------------


1. Секция-ресурсов и типы каталогов в ней

Ресурсы в приложении – это связанные с ним статические данные, предназначенные в первую очередь для описания стандартных компонентов софта типа: меню, диалоговых окон, внешних шрифтов, иконок, курсоров, изображений, звуков, текстовых строк, манифестов и прочего хлама. В инклуде фасма "equates\kernel32.inc" прописаны константы возможных вариантов, которые можно найти обычным поиском по префиксу RT – ResourceType. Всего зарегистрировано в нём 24-типа, но мы остановимся только на одном из них – ресурсе "RT_VERSION" под номером 16:


Код:
;// Resource types
;//*********************
   RT_CURSOR       = 1
   RT_BITMAP       = 2
   RT_ICON         = 3
   RT_MENU         = 4
   RT_DIALOG       = 5
   RT_STRING       = 6
   RT_FONTDIR      = 7
   RT_FONT         = 8
   RT_ACCELERATOR  = 9
   RT_RCDATA       = 10
   RT_MESSAGETABLE = 11
   RT_GROUP_CURSOR = 12
   RT_GROUP_ICON   = 14
   RT_VERSION      = 16
   RT_DLGINCLUDE   = 17
   RT_PLUGPLAY     = 19
   RT_VXD          = 20
   RT_ANICURSOR    = 21
   RT_ANIICON      = 22
   RT_HTML         = 23
   RT_MANIFEST     = 24

Если сравнивать данные в секции-ресурсов с классическими видами данных по образу внешних DLL-библиотек, то они имеют ряд перечисленных ниже преимуществ:

• Ресурс компилируется вместе с приложением в единый исполняемый файл, и становится доступным из его тушки прямым вызовом функций FindResource() и LoadResource() с указанием соответствующих констант (см.лист выше). В результате отпадает необходимость таскать за собой лишний груз в виде библиотек, картинок и прочих внешних файлов.
• Исходя из того, что в описании каждого ресурса имеются поле для установки языка и кодировки (Language/CodePage), в ресурсах можно хранить нескольких языковых наборов, что даёт возможность создавать программы с многоязычным интерфейсом MUI – MultiLanguage User Interface. Для этого, на этапе инициализации приложения запрашивается текущий язык системы, и в соответствии с ним через LoadResource() в память подгружается требуемый набор.
• Уже скомпилированный двоичный ресурс без проблем поддаётся правке, при помощи специальных утилит из категории "Редактор ресурсов". Так, если вы потеряли исходные тексты программ, можно будет поменять иконку на новую, или подправить пару текстовых строк в диалоговом окне.

В большинстве случаях, на практике ресурсы создаются в виде трехуровневого древа, хотя документированный предел вложенных каталогов 32-бита (более 4 млрд). Иерархия секции такова, что имеется корневая директория верхнего уровня, внутри которой перечисляются все используемые данным файлом, типы ресурсов. Теперь, каждому типу нужно присвоить произвольное имя, чтобы в последующем можно было обращаться к нему. Дело в том, что внутри одной секции вполне-себе могут мирно сосуществовать несколько ресурсов одинакового типа, и в этом случае без уникальных имён никак не обойтись. Например чтобы указать версию на двух языках английский/французский и одно меню, можно прописать в корневую директорию три типа по такой схеме:

Код:
directory  RT_VERSION, ver1,\
           RT_VERSION, ver2,\
           RT_MENU, menus

Следующим в иерархии каталогов идёт описатель конкретного типа. По заданным нами характеристикам его оформляет макрос "resource" компилятора. Он заносит в базу имя ресурса, его идентификатор, язык данных, и указатель на эти данные. В общем случае, зарытую в два этих каталога информацию можно рассматривать как структуру-заголовка "Header" каждого ресурса, которая предваряет собственно полезные данные с версией файла. Их разбором и оформлением в надлежащий вид занимается ещё один макрос "versioninfo", которому нужно передать целых шесть аргументов:

• аргумент(1) – имя ресурса, которое мы определили на предыдущем этапе;
• аргумент(2) – операционная система РЕ-файла: как-правило это константа VOS __ WINDOWS32;
• аргумент(3) – тип нашего файла: VFT_APP = программа, VFT_DLL = библиотека, VFT_DRV = драйвер;
• аргумент(4) – подтип файла, в большинстве случаях не нужен с константой VFT2_UNKNOWN;
• аргумент(5) – идентификатор языка строковых данных;
• аргумент(6) – кодовая страница языка.

Для ресурса типа "VERSION", инженерами Microsoft предопределены всего 12 полей данных (на рис.ниже выделены коричневым), которые программист может комбинировать в любом порядке. Имена этих полей являются системными константами для некоторых API-функций типа VerQueryValue() для извлечения содержимого поля, поэтому нужно указывать их строго соблюдая правописание, иначе функция будет возвращать ошибку "Неправильный аргумент". Это создаёт определённые неудобства, а потому советую создать заранее подготовленный текстовый шаблон секции, и подключать его к своему проекту посредством вызова include. На рисунке ниже, различными цветами выделена информация, которую нам дозволено менять – остальное всё константы:

RT_VER.png


Если скомпилировать этот исходник и просмотреть свойства полученного файла, то можно обнаружить, что система отображает не все поля версии, а только те, которые считает нужным. Но это вовсе не означает, что перечисленных полей нет в файле, и чтобы получить полный их лист, в следующей части мы напишем небольшое приложение. Авансом скажу, что в карманах этих штанин можно будет найти достаточно интересную информацию, которую скрывает от нас мастдай – например ф.и.о. создателя в комментах, и прочее:

mVer.png



2. Проблема языковых стандартов

Теперь, отложив скучную теорию в стол, немного попрактикуемся..
Но для начала, нужно рассмотреть весьма мерзкий подарочек от создавших API индусов. Учитывая, что у них там растёт хороший "ганджубас", в этом нет ничего удивительного, ведь только перемещаясь по открытым галлюциногенами порталам можно было написать функцию, ожидающую в аргументе hex-значение в (!)текстовом виде. Хоть это и верх извращения, остаётся только принять эту суровую действительность, поскольку мелкософт ввёл нас в фактический режим апартеида, полностью лишив права голоса.

Если стоит задача чтения из исполняемого файла ресурса "VERSION", то нас ожидают проблемы с указанием языка и кодировки "Language & CodePage". Причём это должны быть строго валидные значения, и никаких нулей там по-умолчанию нет. Дело в том, что функция VerQueryValue() извлекает информацию о версии из указанного ресурса. Однако чтобы подобраться к этой версии, необходимо сначала потянуть за функцию GetFileVersionInfoSize() (возвращает общий размер информации), а затем её выхлоп передать в функцию GetFileVersionInfo(), которая и сбросит необработанные RAW-данные в буфер.

Только теперь можно будет через VerQueryValue() в цикле запрашивать по одной строке каждого из 12-ти текстовых полей версии. До этого момента вроде всё нормально – мелкософт часто практикует альянс из трёх последовательных функций. Но посмотрим на прототип этой функции:


C-подобный:
BOOL VerQueryValueA
  pBlock      dd  0  ;// указатель на буфер функции GetFileVersionInfo()
  lpSubBlock  dd  0  ;// указатель на строку с именем требуемой информации
  lplpBuffer  dd  0  ;// указатель на DWORD, куда вернётся адрес запрощенных данных
  puLen       dd  0  ;// указатель на DWORD, куда вернётся размер данных

;//**********************************
;// Варианты аргумента "lpSubBlock"
;//**********************************
;// возвращает линк на корневую структуру "VS_FIXEDFILEINFO"
lpSubBlock    db  '\',0

;// возвращает линк на "Lang-CP".
lpSubBlock    db  '\VarFileInfo\Translation',0

;// возвращает значение поля, указанного в "StrName"
lpSubBlock    db  '\StringFileInfo\Lang-CP\StrName',0

Обратите внимание на формат аргумента для чтения строковых данных '\StringFileInfo\Lang-CP\StrName'. Например если мы хотим прочитать из версии поле "LegalCopyright", которое хранится в кодировке 1033/1252 (Lang/CodePage в hex=0409/04E4), то должны передать в функцию строку следующего вида.. Причём повторюсь, что кодировка должна быть не от фонаря, а валидная, иначе хоть данные и будут присутствовать в файле, функция всё-равно вернёт ошибку:

C-подобный:
 invoke  VerQueryValueA,pBlock,<'\StringFileInfo\040904E4\LegalCopyright',0>,lpBuffer,puLen

Как можно было до такого додуматься, и что мешало разделить эту строку на три самостоятельных аргумента? Когда не надо, они пихают в функции по 10-параметров, а здесь решили сэкономить на них. Тогда и кодировку можно было-бы передавать в человеческом hex-виде, и в цикле подставлять название следующего из возможных 12-ти полей версии. В общем одни лайки.. Теперь-же, приходится разгребать эту "кучу-навоза" вручную, динамически в цикле формируя требуемую строку из нескольких составляющих.

Но и это ещё не всё.. Как видно из описания, среди прочего функция может возвращать так-нужную нам строку "Lang/CodePage" (второй вариант в прототипе). Однако на практике делает она это криво, и иногда возвращает совсем не то, что ожидалось. Поэтому и здесь приходится ручками забирать инфу прямо из структуры "StringFileInfo". Как показали личные опыты, VerQueryValue() ищет кодировку в дочерней структуре "VarFileInfo", а она там не фиктивная. Вот и доверяй после этого мелкомягким индусам..

На рисунке ниже скрин буфера с данными о версии из секции-ресурсов, где зелёным выделено поле с валидным языком Lang, и кодовой страницей CP (0409h=1033 , 04B0h=1200):


VerBuff.png



3.0. Практика – перечисление всех языков и кодовых страниц системы

Ради интереса напишем небольшую программу, которая сбросит на консоль коды, и связанные с ними текстовые строки всех поддерживаемых текущей ОС языков, и их кодовых страниц. Забегая вперёд скажу, что пример можно рассматривать как модуль для следующей программы, где будет представлен вариант чтения всех полей "VERSION" исполняемого файла. Алгоритм данного модуля держится всего на двух функциях – VerLanguageName() для запроса языка по переданному в аргументе коду, и родственной по функционалу GetCPInfoExA(), которая возвращает кодовую страницу СР в текстовом виде. Вот их прототипы (обе прописаны в kernel32.dll):


C-подобный:
;// Возвращает размер строки, 0 = ошибка
;//**************************************
DWORD VerLanguageNameA
  wLang      dd 0   ;// код языка
  szLang     dd 0   ;// получим указатель на строку языка
  cchLang    dd 0   ;// получим размер строки


;// Возвращает: 1 = ОК, 0 = ошибка
;//**************************************
BOOL GetCPInfoExA
  CodePage   dd 0   ;// код языковой страницы
  dwFlags    dd 0   ;// резерв = 0
  lpCPInfo   dd 0   ;// указатель на структуру "CPIINFOEXA",
                    ;//  ...в которой по смещению(24) лежит строка

Программа ниже будет исправно отрабатывать на всей линейки Win любой разрядности. При старте нужно указать тип запроса: 1 = показать информацию о поддерживаемых системой языках, 2 = набор кодовых страниц. Я оформил их в отдельные процедуры, и добавил в исходник секцию-ресурсов, с описанием версии файла.

C-подобный:
format   pe console
include 'win32ax.inc'
entry    start
;//----------
.data
counter  dd  0   ;// порядковый номер
total    dd  0   ;// всего найдено в системе
buff     db  0   ;// пригодится..
;//----------
.code
proc  GetLanguageList    ;//<---- Процедура поддержки языков
        cinvoke  printf,<'1',\
                         10,' ***************************',10,\
                         10,' Language list...',10,0>
         invoke  Sleep,1500   ;// пауза, чтобы осмотреться
         xor     ecx,ecx      ;// EAX=0. Будет счётчиком цикла,
                              ;//  ...и сразу константой языка.
@@:      push    ecx
         invoke  VerLanguageName,ecx,buff,128  ;// получить язык по константе!
         cmp     eax,13h      ;// остеить ошибки
         jz      @nul         ;//
         invoke  CharToOem,buff,buff  ;// преобразовать в ОЕМ для консоли
        cinvoke  printf,<10,' %04Xh = %05d = %s',0>,[counter],[counter],buff
         inc     [total]      ;// счётчик найденных +1
@nul:    pop     ecx          ;//
         inc     [counter]    ;// номер языка +1
         inc     ecx          ;// цикл +1
         cmp     ecx,0xffff   ;// проверить на макс.
         jnz     @b           ;// повторить, если не равно
                              ;// иначе: распечать всего найденных
        cinvoke  printf,<10,10,' Total language found: %d',10,0>,[total]
         ret
endp
;//==================================================================
proc  GetCodePageList    ;//<---- Процедура поддержки кодовых страниц
        cinvoke  printf,<'2',\
                         10,' ***************************',10,\
                         10,' CodePage list...',10,0>
         invoke  Sleep,1500
         xor     ecx,ecx      ;// EAX=0. Счётчик и константа СР
@@:      push    ecx          ;//
         invoke  GetCPInfoExA,ecx,0,buff   ;// получить строку по константе!
         or      eax,eax      ;// отсеить ошибки
         jz      @null        ;//
         invoke  CharToOem,buff+24,buff+24  ;// строку в ОЕМ для консоли
        cinvoke  printf,<10,' %04Xh = %s',0>,[counter],buff+24
         inc     [total]      ;// найденных +1
@null:   pop     ecx          ;//
         inc     [counter]    ;// код СР +1
         inc     ecx          ;//
         cmp     ecx,0xffff   ;// проверить цикл на переполнение
         jnz     @b           ;//
        cinvoke  printf,<10,10,' Total CodePage found: %d',10,0>,[total]
         ret
endp

;//***** ТОЧКА ВХОДА ***********************
start:   invoke  SetConsoleTitle,<'***Language/CodePage Info v0.1***',0>
        cinvoke  printf,<10,' ********** MENU ***********',\
                         10,' 1 = Language,  2 = CodePage',\
                         10,' Choise number: ',0>

@@:     cinvoke  getch    ;// парсим выбор юзера
         cmp     al,'1'   ;//
         je      @lang    ;// если нажал (1)
         cmp     al,'2'   ;//
         jne     @b       ;// вернуться, если не (2)
         call    GetCodePageList   ;// значит (2),
         jmp     @exit             ;//  ..и на выход.
@lang:   call    GetLanguageList   ;// уходим на введённую единицу

@exit:  cinvoke  getch             ;// GAME OVER!
        cinvoke  exit,0            ;//

;//----- ИМПОРТ ----------
section '.idata' import data readable
library  msvcrt,'msvcrt.dll',kernel32,'kernel32.dll',user32,'user32.dll'
import   msvcrt, printf,'printf',getch,'_getch',exit,'exit'
include 'api\kernel32.inc'
include 'api\user32.inc'

;//----- РЕСУРСЫ ---------
section '.rsrc' resource data readable
directory    RT_VERSION,ver1
resource     ver1, 1, LANG_NEUTRAL, vInfo1
versioninfo  vInfo1,\
             VOS__WINDOWS32, VFT_APP, VFT2_UNKNOWN,\
             LANG_ENGLISH + SUBLANG_DEFAULT, 1252,\   ;//<-------- Lang/CodePage
            'CompanyName'     , 'https://codeby.net',\
            'LegalCopyright'  , 'Copyright 2020-2021 (c)Marylin',\
            'ProductName'     , 'Lang/CodePage info',\
            'ProductVersion'  , '6.1.7601.3821',\
            'FileDescription' , 'Win LCID identifier',\
            'FileVersion'     , '0.0.1',\
            'OriginalFilename', 'LangCP.exe',\
            'InternalName'    , 'LangCP_32/64',\
            'Comment'         , 'codeby.net/forums/assembler.229/',\
            'PrivateBuild'    , '0.0.2',\
            'SpecialBuild'    , '00.01.00'

Lang_CP.png



3.1. Практика – чтение ресурса "VERSIONINFO" из файла

Под занавес напишем ещё одну прожку, для просмотра свойств исполняемых файлов типа: exe,sys,dll,drv,ocx,cpl. Её код вытащит из секции-ресурсов блок с данными "VERSION" и распечатает на консоли его содержимое в форматированном виде. Алгоритм собрал в себе всё выше/изложенное в этой теме, и состоит из следующих частей:

1. Запросить у юзера расширение файлов.

2. Функцией FindFirstFile() начать поиск первого файла в текущем каталоге и если ошибка, то сразу на выход [9]. Имя найденного файла функция сбрасывает в свою структуру "WIN32_FIND_DATA" по смещению(44) – больше нам от неё ничего нужно.

3. Передать это имя функции GetFileVersionInfoSize(), чтобы узнать размер данных "RT_VERSION" в файле. Если функция вернёт нуль, значит в файле нет информации о версии, и сразу прыгаем в конец цикла [8], для поиска следующего файла в каталоге.

4. Если мы здесь, значит файл содержит нужную нам информацию, и мы считываем её в свой буфер посредством GetFileVersionInfo().

5. По смещению 86h от начала приёмного буфера будет лежать Unicode-строка с языком и кодовой страницей "Lang/CP". Теперь нужно перевести её в ANSI (отсеить парные нули), и вставить в глобальную строку, которая будет определять собой аргумент для запроса конкретного поля версии. В дисциплинах такую операцию называют "конкатенация", или склеивание двух и более строк, в одну. Поскольку маркером конца строки всегда является нуль, то я не стал заморачиваться с этим, и оформил строку так:


C-подобный:
;// Константы полей "VERSION".
;// В первом байте хранится длина строки
;//**************************************
n01        db  11,'CompanyName',0
n02        db  14,'LegalCopyright',0
n03        db  15,'LegalTrademarks',0
n04        db  11,'ProductName',0
n05        db  14,'ProductVersion',0
n06        db  15,'FileDescription',0
n07        db  11,'FileVersion',0
n08        db  16,'OriginalFilename',0
n09        db  12,'InternalName',0
n10        db  12,'PrivateBuild',0
n11        db  12,'SpecialBuild',0
n12        db  07,'Comment',0

queryStr   db  '\StringFileInfo\'        ;// начало строки (всегда постоянна)
cp         db  '........\'               ;// резерв под 8 символьную hex-строку Lang/CP (будь она не ладна)
space      db  '.................: ',0   ;// резерв под макс.16 символов константы 'OriginalFilename',
                                         ;//   .. +2 для форматированного вывода на консоль.

В результате, если теперь заполнить поля cp и spase, получим единую строку, которую можно будет вскормить в виде аргумента сл.функции чтения. Правда в зависимости от длинны строковой константы, нужно будет вставлять терминальный нуль в её хвост, но это уже дело техники (см.исходник).

6. На данном этапе имеем всё, чтобы считать очередную "строку версии" из файла – зовём для этого функцию VerQueryValue() и получаем указатель на запрошенную ANSI-строку. Поскольку часто разработчик заполняет строки кириллицей (а мы выводим их на консоль), то имеет смысл функцией CharToOem() сразу конвертировать их в кодировку СР-866.

7. Мотаем предыдущий пункт(6) ровно 12-раз, в цикле подставляя следующую текстовую константу в поле space. Это позволит проверить в файле все без исключения поля "VERSION" и если какое-нибудь из них пустое, функция VerQueryValue() будет возвращать ошибку, а мы пропускать вывод его на консоль.

8. По окончании внутреннего цикла из 12 итераций, сбрасываем все переменные в дефолт и функцией FindNextFile() переходим к поиску следующего файла в текущем каталоге. Если файлов с указанным расширением больше нет, функция вернёт ошибку, сигнализируя, что нам пора на выход из программы. Иначе, уходим на начало глобального цикла к этапу [3].

9. Заключительный этап требует всего-лишь освобождения дескриптора поиска функцией FindClose() и ожидания любой клавиши, чтобы осмотреться.

Ну и теперь собственно код, который реализует на практике описанный выше алгоритм:


C-подобный:
format   pe console
include 'win32ax.inc'
entry    start
;//----------
.data
nameTable  dd  n01,n02,n03,n04,n05,n06,n07,n08,n09,n10,n11,n12
nextOffs   dd  0

n01        db  11,'CompanyName',0
n02        db  14,'LegalCopyright',0
n03        db  15,'LegalTrademarks',0
n04        db  11,'ProductName',0
n05        db  14,'ProductVersion',0
n06        db  15,'FileDescription',0
n07        db  11,'FileVersion',0
n08        db  16,'OriginalFilename',0
n09        db  12,'InternalName',0
n10        db  12,'PrivateBuild',0
n11        db  12,'SpecialBuild',0
n12        db  07,'Comment',0

queryStr   db  '\StringFileInfo\'
cp         db  '00000000\'
space      db  '.................: ',0

fMask      db  '*.'
extens     db  32 dup(0)  ;// под маску файлов EXE,DLL и прочии.

align      16
fData      db  128 dup(0) ;// структура "FIND_DATA"
fName      =   fData+44   ;// смещение имени файла в ней
fHndl      dd  0          ;// дескриптор поиска

lpBuff     dd  0          ;// указатель на строки
lpLen      dd  0          ;// под размер этих строк

counter    dd  12         ;// всего строковых констант
codePage   db  64 dup(0)  ;// под текст кодовой страницы
endPtr     dd  0          ;// для fn. strtol()

align 16
verSize    dd  0          ;// размер данных VERSION
verData    rb  4096       ;// 4К буфер под эти данные
;//----------

.code
start:   invoke  SetConsoleTitle,<'***File Version Info v0.1***',0>
        cinvoke  printf,<10,' Support file..: exe,sys,dll,drv,ocx,cpl',\
                         10,' Type extension: ',0>
        cinvoke  scanf,<'%s'>,extens   ;// запрашиваем расширение файла

;//===== Начинаем поиск первого файла в текущей папке
         invoke  FindFirstFile, fMask, fData
         mov     [fHndl],eax         ;// запомнить хэндл поиска
         cmp     eax,-1              ;// проверить на ошибку
         jnz     @findAnyFile        ;// ОК!
        cinvoke  printf,<' ERROR! File not found.',0>
         jmp     @exit               ;// иначе: Error

;// покажем имя найденного файла
@findAnyFile:
        cinvoke  printf,<10,10,' File: %s',\
                         10,' ********************',0>,fName

;//===== Вывод VersionInfo из секции-ресурсов
;// для начала нужно узнать размер данных
         invoke  GetFileVersionInfoSize,fName,verSize
         push    eax
        cinvoke  printf,<10,' Version data size..:  %u byte',0>,eax
         pop     ecx
         or      ecx,ecx     ;// проверить наличие VersionInfo в ресурсах
         je      @fuck       ;// если прокол..

;//===== Копируем Version из файла в свой буфер "verData"
;// в регистре ECX лежит размер текущих данных
         invoke  GetFileVersionInfo,fName,0,ecx,verData

;// Не рабочий метод получения Lang/CodePage
;// лучше гарантированно вытащить их семью строками ниже
;//         invoke  VerQueryValue,verData,<'\VarFileInfo\Translation',0>,lpBuff,lpLen

;// Здесь нужно взять из буфера "язык и кодировку" (unicode),
;// чтобы передать их в функцию VerQueryValue() как ANSI.
         mov     esi,verData    ;// начало данных
         add     esi,0x86       ;// +86h = адрес Lang/CP (источник)
         mov     edi,cp         ;// приёмник для конкатенации строк
         mov     ecx,8          ;// длина Unicode-строки
@@:      lodsw                  ;// взять 2-байта
         stosb                  ;// сохранить 1 младший
         loop    @b             ;// повторить ECX-раз..

;// За одно покажем язык/кодировку пользователю
         mov     esi,cp         ;// источник
         mov     edi,codePage   ;// приёмник
         lodsd                  ;//
         stosd                  ;// скопировать из ESI в EDI 4-байта
         sub     edi,4          ;//   ...(edi на родину)
        cinvoke  strtol,codePage,endPtr,16   ;// перевести из строки в hex-число!
         push    eax
        cinvoke  printf,<10,' Lang/CodePage......:  %04d/',0>,eax  ;//<----- язык
         lodsd                  ;// вторые 4-байта
         stosd                  ;//
        cinvoke  strtol,codePage,endPtr,16
        cinvoke  printf,<'%04d',0>,eax       ;//<----- кодировка

;// покажем строкой изъятый язык (код лежит в EAX)
         pop     eax
         invoke  VerLanguageName,eax,codePage,64
         invoke  CharToOem,codePage,codePage
        cinvoke  printf,<' = %s',0>,codePage

;//***** ВНУТРЕННИЙ ЦИКЛ ИЗ 12 ИТЕРАЦИЙ *******************
@prnVersion:
         mov     edi,space     ;// адрес поля с текстовой константой
         mov     ecx,17        ;// его длина
         mov     al,'.'        ;// чем заполнить
         rep     stosb         ;// очищаем поле от мусора!

         mov     ebx,nameTable    ;// указатель на таблицу имён
         add     ebx,[nextOffs]   ;// + сл.смещение
         mov     esi,[ebx]        ;// ESI = адрес строковой константы
         mov     cl,byte[esi]     ;// ECX = длина этой константы (первый байт)
         inc     esi              ;// пропустить этот байт
         push    esi              ;// указатель на начало в стек
         mov     edi,space        ;// адрес конкатенации
         rep     movsb            ;// склеить строки!
         mov     byte[edi],0      ;// вставить в хвост нуль, для передачи в fn.
         push    edi              ;//
         invoke  VerQueryValue,verData,queryStr,lpBuff,lpLen  ;// получить данные!
         pop     edi esi          ;//
         mov     byte[edi],'.'    ;// убрать терминальный нуль (там есть ещё один)
         or      eax,eax          ;// проверить наличие инфы данного типа
         jz      @f               ;// пропустить печать, если нет..
                                  ;// иначе:
;// В некоторых файлах встречается кирилица 1251,
;// поправим её кодировку для вывода на консоль 866.
         invoke  CharToOem,[lpBuff],[lpBuff]
        cinvoke  printf,<10,'   %s %s',0>,space,[lpBuff]

@@:      add     [nextOffs],4   ;// сл.смещение в таблице
         dec     [counter]      ;// уменьшить счётчик 12-ти
         jnz     @prnVersion    ;// повторить вн.цикл, если он не нуль

;//===== Продолжить поиск файлов..
@fuck:   mov     [nextOffs],0       ;// очистить все переменные от мусора
         mov     [counter],12       ;// ^^^^
         invoke  FindNextFile,[fHndl],fData  ;// продолжить поиск файлов в каталоге
         or      eax,eax            ;// 0 = ошибка
         jnz     @findAnyFile       ;// иначе: мотаем внешний цикл дальше

;//===== КОНЕЦ ПРОГРАММЫ ===================================
@exit:   invoke  FindClose,[fHndl]  ;// хэндл поиска в топку
        cinvoke  getch,verData      ;// ждём клаву...
        cinvoke  exit,0

;//----- ИМПОРТ ----------
section '.idata' import data readable
library  msvcrt,  'msvcrt.dll',version,'version.dll',\
         kernel32,'kernel32.dll',user32,'user32.dll'
import   msvcrt,  printf,'printf',scanf,'scanf',\
                  getch,'_getch',exit,'exit',strtol,'strtol'
import   version, GetFileVersionInfoSize,'GetFileVersionInfoSizeA',\
                  GetFileVersionInfo,'GetFileVersionInfoA',\
                  VerQueryValue,'VerQueryValueA'
include 'api\kernel32.inc'
include 'api\user32.inc'

;//----- РЕСУРСЫ ----------
section '.rsrc' resource data readable
directory    RT_VERSION,ver1
resource     ver1,1,LANG_NEUTRAL,vInfo
versioninfo  vInfo, VOS__WINDOWS32, VFT_APP, VFT2_UNKNOWN,\
             LANG_ENGLISH + SUBLANG_DEFAULT, 1252,\
            'CompanyName'     , 'https://codeby.net',\
            'LegalCopyright'  , 'Copyright 2020-2021 (c)Marylin',\
            'ProductName'     , 'VERSION-INFO',\
            'ProductVersion'  , '6.1.7601.3821',\
            'FileDescription' , 'PE-file version Identifier',\
            'FileVersion'     , '0.0.1.0',\
            'OriginalFilename', 'VerInfo.exe',\
            'InternalName'    , 'VerInfo32/64',\
            'Comment'         , 'codeby.net/forums/assembler.229/',\
            'LegalTrademarks' , 'FreeWare',\
            'PrivateBuild'    , '0.00.02.00',\
            'SpecialBuild'    , '0.01.00.01'

VerRsrc.png



Рассмотрим бегло этот лог..
Значит в начале я указал искать в текущем каталоги только файлы типа EXE, на что получил версии трёх (не считая своего) файлов. Судя по логу, первый является кейгеном к переводчику "Socrat-Personal", причём разработчик использует шаблон, поскольку константы есть, только они не заполнены. Иначе прожка пропустила-бы их, как в случае с последним файлом "ProcessMM.exe".

Вторым в логе красуется файл-ядра системы Ntoskrnl.exe, в котором заполнены уже почти все возможные поля, кроме коммента и Build. Я его честно скоммуниздил у десятки, о чём свидетельствует поле "FileVersion". Лог закрывает инфа о программе маппинга памяти процесса (Memory-Map), и его разраб не удосужился заполнить даже основные поля, ограничившись для галочки лишь версией. Здесь так-же видно, что у всех файлов язык 1033, а кодировка 1200 или 1252. Расшифровать эти коды можно с помощью предыдущей прожки.


4. Эпилог

В данной статье я попытался освятить только одну часть ресурсов, а ведь к ней присовокупляются ещё 23 типа, и каждый из них заслуживает отдельного внимания, т.к. является вполне самостоятельным компонентом. Чтобы ознакомится со-всеми ресурсами и посмотреть на их формат, можно воспользоваться программой "PE-Explorer". Эта крутая тулза не только просмотрщик, но и редактор любой области исполняемых файлов, начиная от РЕ-заголовка и заканчивая ресурсами. Огромным её плюсом считается возможность при помощи мастера создавать файлы манифестов – советую..


PE_exp.png


По уже отработанной привычке, в скрепку я положил два рассмотренных файла, которые не привязаны как к версии системы, так и к её разрядности. Всем удачи, пока!
 

Вложения

  • VERSION.ZIP
    3,2 КБ · Просмотры: 300
Последнее редактирование:
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!