Учитывая переносимый исполняемый файл (PE), хранящийся в потоке, как я могу получить PE FileVersion
?
Background
Обычно, чтобы получить версию файла PE, вы вызываете:
VS_FIXEDFILEINFO GetFileVersion(String filename)
{
// filename: the name of the PE to get the "File Version" of. e.g. "contoso.exe"
//Get buffer size required
DWORD handle;
DWORD bufferSize = GetFileVersionInfoSize(filename, ref handle);
//Get the entire VersionInfo blob
Byte[bufferSize] buffer;
GetFileVersionInfo("contoso.exe", 0, bufferSize, ref buffer);
//Get the FixedFileInfo structure
VS_FIXEDFILEINFO fileInfo;
VerQueryValue(buffer, "\", out fileInfo, out bufferSize);
//UInt64 version = (fileInfo.dwFileVersionMS << 32) || (fileInfo.dwFileVersionLS); //0xaaaabbbbccccdddd
return fileInfo;
}
Версия потока
Для Windows GetFileVersionInfoSize
и GetFileVersionInfo
требуется файл:
UInt64 GetFileVersion(String filename)
В моем если у меня нет файла; у меня есть только PE, содержащийся в потоке:
UInt64 GetFileVersion(IStream stream)
Как я могу получить версию файла PE, когда PE находится в потоке?
Временное решение
Ужасный обходной путь, который я придумал, - это разобрать MZ (DOS_HEADER), PE (NT_HEADER), .rsrc
заголовок раздела, .rsrc
раздел вручную:
function GetFileVersionInfo(AStream: IStream): TVSFixedFileInfo;
function Read16(Offset: Int64): Word;
var
newPos: UInt64;
begin
AStream.Seek(Offset, STREAM_SEEK_SET, {out}newPos);
AStream.Read(@Result, sizeof(Result), nil);
end;
function Read32(Offset: Int64): Cardinal;
var
newPos: UInt64;
begin
AStream.Seek(Offset, STREAM_SEEK_SET, {out}newPos);
AStream.Read(@Result, sizeof(Result), nil);
end;
function ReadBytes(Offset: Int64; BytesToRead: Integer): RawByteString;
var
newPos: UInt64;
hr: HRESULT;
buffer: RawByteString;
begin
SetLength(buffer, BytesToRead);
hr := AStream.Seek(Offset, STREAM_SEEK_SET, {out}newPos);
OleCheck(hr);
hr := AStream.Read(@buffer[1], BytesToRead, nil);
OleCheck(hr);
Result := buffer;
end;
var
ntHeader: Cardinal;
nSections: Cardinal;
nDirectories: Cardinal;
sectionHeader: Cardinal;
i: Integer;
sectionName: AnsiString;
resource: Cardinal;
resourceLength: Integer;
rsrc: RawByteString;
n: Integer;
s: RawByteString;
fileVersionInfoSize: Word;
fileVersionInfo: RawByteString;
ffi: VS_FIXEDFILEINFO;
const
IMAGE_DOS_SIGNATURE = Word($5A4D);
IMAGE_NT_SIGNATURE = Cardinal($00004550);
IMAGE_NT_OPTIONAL_HDR32_MAGIC = Word($010B);
begin
//Check the DOS header "MZ" signature
if Read16(0) <> IMAGE_DOS_SIGNATURE then //0x5A4D
raise Exception.Create('Stream does not contain MZ signature');
//offset to new exe header
ntHeader := Read32($3C); //offset to new exe header
//Check the NT header "PE" signature
if Read32(ntHeader) <> IMAGE_NT_SIGNATURE then //0x00004550
raise Exception.Create('NT Header signature is not "PE\0\0"');
nSections := Read16(ntHeader+$06);
//Check the Optional header magic signature
if Read16(ntHeader+$18) <> IMAGE_NT_OPTIONAL_HDR32_MAGIC then //0x010B
raise Exception.Create('Optional Header magic invalid');
//Jump to first section header
nDirectories := Read32(ntHeader+$74);
sectionHeader := ntHeader+$74 + 4 + (nDirectories * 8);
//Enumerate each 28-byte section header, looking for the ".rsrc" section header
resource := 0;
resourceLength := 0;
for i := 0 to nSections-1 do
begin
sectionName := PAnsiChar(ReadBytes(sectionHeader, 8));
if sectionName = '.rsrc' then
begin
//Found the .rsrc section, read the "Pointer to Raw Data" at offset $14.
resource := Read32(sectionHeader+$14);
resourceLength := Read32(sectionHeader+$10);
Break;
end;
//Skip to the next section header
sectionHeader := sectionHeader+$28;
end;
if resource = 0 then
raise Exception.Create('.rsrc section not found');
//Read the entire variable length .rsrc section
rsrc := ReadBytes(resource, resourceLength);
{
VS_VERSIONINFO
==============
28 03 ;Length 0x0328
34 00 ;Value length 0x0034
00 00 ;Type 0:binary data, 1:string data. 0=binary data
56 00 53 00 ;L"VS_VERSION_INFO"
5F 00 56 00
45 00 52 00
53 00 49 00
4F 00 4E 00
5F 00 49 00
4E 00 46 00
4F 00 00 00
}
//Look for VS_VERSION_INFO in the resource section
s := #$34#$00#$00#$00#$56#$00#$53#$00#$5F#$00#$56#$00#$45#$00#$52#$00#$53#$00#$49#$00#$4F#$00#$4E#$00#$5F#$00#$49#$00#$4E#$00#$46#$00#$4F#$00#$00#$00;
n := Pos(s, rsrc);
if n <= 0 then
raise Exception.Create('Could not locate version resource section');
//Get the length of the entire VERSION entry
n := n - 2; //backup to the start of the VERSION entry
Move(rsrc[n], fileVersionInfoSize, 2);
fileVersionInfo := Copy(rsrc, n, fileVersionInfoSize);
//Seek past VS_VERSIONINFO
n := 2+Length(s);
//Skip 32-bit alignment padding
if (n mod 4) <> 0 then
n := n + (n mod 4);
//Populate VS_FIXEDFILEINFO structure
Move(fileVersionInfo[n+1], ffi, sizeof(ffi));
if ffi.dwSignature <> $FEEF04BD then
raise Exception.Create('Invalid FixedFileInfo signature');
Result := ffi;
{
Free tip: You can get the full 64-bit version from ffi with:
version: UInt64 := (ffi.dwFileVersionMS shl 32) or (ffi.dwFileVersionLS) //0xaaaabbbbccccdddd ==> aaaa.bbbb.cccc.dddd
or if you want to make it a string:
wMajor, wMinor, wRelease, wBuild: Word;
wMajor := HiWord(ffi.dwFileVersionMS);
wMinor := LoWord(ffi.dwFileVersionMS);
wRelease := HiWord(ffi.dwFileVersionLS);
wBuild := LoWord(ffi.dwFileVersionLS);
versionString := Format('%d.%d.%d.%d', [wMajor, wMinor, wRelease, wBuild]);
}
end;
Но сам анализ MZ, PE, заголовков разделов, разделов, раздела версий, информации о версиях и структур версий является деликатным, fr agile и подверженным ошибкам. Например, я не знаю, насколько хорошо он обрабатывает другие PE:
- Динамически подключаемые библиотеки (.dll)
- Драйверы (.sys)
- 32 по сравнению с 64-битными PE
Какой хороший способ сделать это?