Как получить PE FileVersionInfo из потока? - PullRequest
1 голос
/ 26 мая 2020

Учитывая переносимый исполняемый файл (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

Какой хороший способ сделать это?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...