github.com/saferwall/pe@v1.5.2/version.go (about)

     1  // Copyright 2018 Saferwall. All rights reserved.
     2  // Use of this source code is governed by Apache v2 license
     3  // license that can be found in the LICENSE file.
     4  
     5  package pe
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/binary"
    10  	"fmt"
    11  )
    12  
    13  const (
    14  	// VersionResourceType identifies the version resource type in the resource directory
    15  	VersionResourceType = 16
    16  
    17  	// VsVersionInfoString is the UTF16-encoded string that identifies the VS_VERSION_INFO block
    18  	VsVersionInfoString = "VS_VERSION_INFO"
    19  
    20  	// VsFileInfoSignature is the file info signature
    21  	VsFileInfoSignature uint32 = 0xFEEF04BD
    22  
    23  	// StringFileInfoString is the UTF16-encoded string that identifies the StringFileInfo block
    24  	StringFileInfoString = "StringFileInfo"
    25  	// VarFileInfoString is the UTF16-encoded string that identifies the VarFileInfoString block
    26  	VarFileInfoString = "VarFileInfo"
    27  
    28  	// VsVersionInfoStringLength specifies the length of the VS_VERSION_INFO structure
    29  	VsVersionInfoStringLength uint32 = 6
    30  	// StringFileInfoLength specifies length of the StringFileInfo structure
    31  	StringFileInfoLength uint32 = 6
    32  	// StringTableLength specifies the length of the StringTable structure
    33  	StringTableLength uint32 = 6
    34  	// StringLength specifies the length of the String structure
    35  	StringLength uint32 = 6
    36  	// LangIDLength specifies the length of the language identifier string.
    37  	// It is represented as 8-digit hexadecimal number stored as a Unicode string.
    38  	LangIDLength uint32 = 8*2 + 1
    39  )
    40  
    41  // VsVersionInfo represents the organization of data in
    42  // a file-version resource. It is the root structure that
    43  // contains all other file-version information structures.
    44  type VsVersionInfo struct {
    45  	// Length is the length, in bytes, of the VS_VERSIONINFO structure.
    46  	// This length does not include any padding that aligns any
    47  	// subsequent version resource data on a 32-bit boundary.
    48  	Length uint16 `json:"length"`
    49  	// ValueLength is the length, in bytes, of arbitrary data associated
    50  	// with the VS_VERSIONINFO structure.
    51  	// This value is zero if there is no any data associated with the
    52  	// current version structure.
    53  	ValueLength uint16 `json:"value_length"`
    54  	// Type represents as many zero words as necessary to align the StringFileInfo
    55  	// and VarFileInfo structures on a 32-bit boundary. These bytes are not included
    56  	// in ValueLength.
    57  	Type uint16 `json:"type"`
    58  }
    59  
    60  func (pe *File) parseVersionInfo(e ResourceDirectoryEntry) (*VsVersionInfo, error) {
    61  	offset := pe.GetOffsetFromRva(e.Data.Struct.OffsetToData)
    62  	b, err := pe.ReadBytesAtOffset(offset, e.Data.Struct.Size)
    63  	if err != nil {
    64  		return nil, err
    65  	}
    66  	var v VsVersionInfo
    67  	if err := binary.Read(bytes.NewBuffer(b), binary.LittleEndian, &v); err != nil {
    68  		return nil, err
    69  	}
    70  	b, err = pe.ReadBytesAtOffset(offset+VsVersionInfoStringLength, uint32(v.ValueLength))
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	vsVersionString, err := DecodeUTF16String(b)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	if vsVersionString != VsVersionInfoString {
    79  		return nil, fmt.Errorf("invalid VS_VERSION_INFO block. %s", vsVersionString)
    80  	}
    81  	return &v, nil
    82  }
    83  
    84  // VsFixedFileInfo contains version information for a file.
    85  // This information is language and code page independent.
    86  type VsFixedFileInfo struct {
    87  	// Signature contains the value 0xFEEF04BD. This is used
    88  	// with the `key` member of the VS_VERSIONINFO structure
    89  	// when searching a file for the VS_FIXEDFILEINFO structure.
    90  	Signature uint32 `json:"signature"`
    91  	// StructVer is the binary version number of this structure.
    92  	// The high-order word of this member contains the major version
    93  	// number, and the low-order word contains the minor version number.
    94  	StructVer uint32 `json:"struct_ver"`
    95  	// FileVersionMS denotes the most significant 32 bits of the file's
    96  	// binary version number.
    97  	FileVersionMS uint32 `json:"file_version_ms"`
    98  	// FileVersionLS denotes the least significant 32 bits of the file's
    99  	// binary version number.
   100  	FileVersionLS uint32 `json:"file_version_ls"`
   101  	// ProductVersionMS represents the most significant 32 bits of the
   102  	// binary version number of the product with which this file was distributed.
   103  	ProductVersionMS uint32 `json:"product_version_ms"`
   104  	// ProductVersionLS represents the most significant 32 bits of the
   105  	// binary version number of the product with which this file was distributed.
   106  	ProductVersionLS uint32 `json:"product_version_ls"`
   107  	// FileFlagMask contains a bitmask that specifies the valid bits in FileFlags.
   108  	// A bit is valid only if it was defined when the file was created.
   109  	FileFlagMask uint32 `json:"file_flag_mask"`
   110  	// FileFlags contains a bitmask that specifies the Boolean attributes of the file.
   111  	// For example, the file contains debugging information or is compiled with debugging
   112  	// features enabled if FileFlags is equal to 0x00000001L (VS_FF_DEBUG).
   113  	FileFlags uint32 `json:"file_flags"`
   114  	// FileOS represents the operating system for which this file was designed.
   115  	FileOS uint32 `json:"file_os"`
   116  	// FileType describes the general type of file.
   117  	FileType uint32 `json:"file_type"`
   118  	// FileSubtype specifies the function of the file. The possible values depend on the value of FileType.
   119  	FileSubtype uint32 `json:"file_subtype"`
   120  	// FileDateMS are the most significant 32 bits of the file's 64-bit binary creation date and time stamp.
   121  	FileDateMS uint32 `json:"file_date_ms"`
   122  	// FileDateLS are the least significant 32 bits of the file's 64-bit binary creation date and time stamp.
   123  	FileDateLS uint32 `json:"file_date_ls"`
   124  }
   125  
   126  // Size returns the size of this structure in bytes.
   127  func (f *VsFixedFileInfo) Size() uint32 { return uint32(binary.Size(f)) }
   128  
   129  func (f *VsFixedFileInfo) GetStringFileInfoOffset(e ResourceDirectoryEntry) uint32 {
   130  	return alignDword(VsVersionInfoStringLength+uint32(2*len(VsVersionInfoString)+1)+f.Size(), e.Data.Struct.OffsetToData)
   131  }
   132  
   133  func (f *VsFixedFileInfo) GetOffset(e ResourceDirectoryEntry, pe *File) uint32 {
   134  	offset := pe.GetOffsetFromRva(e.Data.Struct.OffsetToData) + VsVersionInfoStringLength
   135  	offset += uint32(2*len(VsVersionInfoString)) + 1
   136  	return alignDword(offset, e.Data.Struct.OffsetToData)
   137  }
   138  
   139  func (pe *File) parseFixedFileInfo(e ResourceDirectoryEntry) (*VsFixedFileInfo, error) {
   140  	var f VsFixedFileInfo
   141  	offset := f.GetOffset(e, pe)
   142  	b, err := pe.ReadBytesAtOffset(offset, f.Size())
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	if err := binary.Read(bytes.NewBuffer(b), binary.LittleEndian, &f); err != nil {
   147  		return nil, err
   148  	}
   149  	if f.Signature != VsFileInfoSignature {
   150  		return nil, fmt.Errorf("invalid file info signature %d", f.Signature)
   151  	}
   152  	return &f, nil
   153  }
   154  
   155  // StringFileInfo represents the organization of data in a
   156  // file-version resource. It contains version information
   157  // that can be displayed for a particular language and code page.
   158  type StringFileInfo struct {
   159  	Length      uint16
   160  	ValueLength uint16
   161  	Type        uint16
   162  }
   163  
   164  func (s *StringFileInfo) GetStringTableOffset(offset uint32) uint32 {
   165  	return offset + StringFileInfoLength + uint32(2*len(StringFileInfoString)) + 1
   166  }
   167  
   168  func (s *StringFileInfo) GetOffset(rva uint32, e ResourceDirectoryEntry, pe *File) uint32 {
   169  	offset := pe.GetOffsetFromRva(e.Data.Struct.OffsetToData) + rva
   170  	return alignDword(offset, e.Data.Struct.OffsetToData)
   171  }
   172  
   173  func (pe *File) parseStringFileInfo(rva uint32, e ResourceDirectoryEntry) (*StringFileInfo, string, error) {
   174  	var s StringFileInfo
   175  	offset := s.GetOffset(rva, e, pe)
   176  	b, err := pe.ReadBytesAtOffset(offset, StringFileInfoLength)
   177  	if err != nil {
   178  		return nil, "", err
   179  	}
   180  	if err := binary.Read(bytes.NewBuffer(b), binary.LittleEndian, &s); err != nil {
   181  		return nil, "", err
   182  	}
   183  	b, err = pe.ReadBytesAtOffset(offset+StringFileInfoLength, uint32(len(StringFileInfoString)*2)+1)
   184  	if err != nil {
   185  		return nil, "", err
   186  	}
   187  	str, err := DecodeUTF16String(b)
   188  	return &s, str, err
   189  }
   190  
   191  // StringTable represents the organization of data in a
   192  // file-version resource. It contains language and code
   193  // page formatting information for the version strings
   194  type StringTable struct {
   195  	Length      uint16
   196  	ValueLength uint16
   197  	Type        uint16
   198  }
   199  
   200  func (s *StringTable) GetStringOffset(offset uint32, e ResourceDirectoryEntry) uint32 {
   201  	return alignDword(offset+StringTableLength+LangIDLength, e.Data.Struct.OffsetToData)
   202  }
   203  
   204  func (s *StringTable) GetOffset(rva uint32, e ResourceDirectoryEntry, pe *File) uint32 {
   205  	offset := pe.GetOffsetFromRva(e.Data.Struct.OffsetToData) + rva
   206  	return alignDword(offset, e.Data.Struct.OffsetToData)
   207  }
   208  
   209  func (pe *File) parseStringTable(rva uint32, e ResourceDirectoryEntry) (*StringTable, error) {
   210  	var s StringTable
   211  	offset := s.GetOffset(rva, e, pe)
   212  	b, err := pe.ReadBytesAtOffset(offset, StringTableLength)
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  	if err := binary.Read(bytes.NewBuffer(b), binary.LittleEndian, &s); err != nil {
   217  		return nil, err
   218  	}
   219  	// Read the 8-digit hexadecimal number stored as a Unicode string.
   220  	// The four most significant digits represent the language identifier.
   221  	// The four least significant digits represent the code page for which
   222  	// the data is formatted.
   223  	b, err = pe.ReadBytesAtOffset(offset+StringTableLength, (8*2)+1)
   224  	if err != nil {
   225  		return nil, err
   226  	}
   227  	langID, err := DecodeUTF16String(b)
   228  	if err != nil {
   229  		return nil, err
   230  	}
   231  	if len(langID) != int(LangIDLength/2) {
   232  		return nil, fmt.Errorf("invalid language identifier length. Expected: %d, Got: %d",
   233  			LangIDLength/2,
   234  			len(langID))
   235  	}
   236  	return &s, nil
   237  }
   238  
   239  // String Represents the organization of data in a
   240  // file-version resource. It contains a string that
   241  // describes a specific aspect of a file, for example,
   242  // a file's version, its copyright notices, or its trademarks.
   243  type String struct {
   244  	Length      uint16
   245  	ValueLength uint16
   246  	Type        uint16
   247  }
   248  
   249  func (s *String) GetOffset(rva uint32, e ResourceDirectoryEntry, pe *File) uint32 {
   250  	offset := pe.GetOffsetFromRva(e.Data.Struct.OffsetToData) + rva
   251  	return alignDword(offset, e.Data.Struct.OffsetToData)
   252  }
   253  
   254  // variant of GetOffset which also returns the number of bytes which were added
   255  // to achieve 32-bit alignment. The padding value needs to be added to the
   256  // string length to figure out the offset of the next string
   257  func (s *String) getOffsetAndPadding(rva uint32, e ResourceDirectoryEntry, pe *File) (uint32, uint16) {
   258  	unalignedOffset := pe.GetOffsetFromRva(e.Data.Struct.OffsetToData) + rva
   259  	alignedOffset := alignDword(unalignedOffset, e.Data.Struct.OffsetToData)
   260  	return alignedOffset, uint16(alignedOffset - unalignedOffset)
   261  }
   262  
   263  func (pe *File) parseString(rva uint32, e ResourceDirectoryEntry) (string, string, uint16, error) {
   264  	var s String
   265  	offset, padding := s.getOffsetAndPadding(rva, e, pe)
   266  	b, err := pe.ReadBytesAtOffset(offset, StringLength)
   267  	if err != nil {
   268  		return "", "", 0, err
   269  	}
   270  	if err := binary.Read(bytes.NewBuffer(b), binary.LittleEndian, &s); err != nil {
   271  		return "", "", 0, err
   272  	}
   273  	const maxKeySize = 100
   274  	b, err = pe.ReadBytesAtOffset(offset+StringLength, maxKeySize)
   275  	if err != nil {
   276  		return "", "", 0, err
   277  	}
   278  	key, err := DecodeUTF16String(b)
   279  	if err != nil {
   280  		return "", "", 0, err
   281  	}
   282  	valueOffset := alignDword(uint32(2*(len(key)+1))+offset+StringLength, e.Data.Struct.OffsetToData)
   283  	b, err = pe.ReadBytesAtOffset(valueOffset, uint32(s.Length))
   284  	if err != nil {
   285  		return "", "", 0, err
   286  	}
   287  	value, err := DecodeUTF16String(b)
   288  	if err != nil {
   289  		return "", "", 0, err
   290  	}
   291  	// The caller of this function uses the string length as an offset to find
   292  	// the next string in the file. We need add the alignment padding here
   293  	// since the caller is unaware of the byte alignment, and will add the
   294  	// string length to the unaligned offset to get the address of the next
   295  	// string.
   296  	totalLength := s.Length + padding
   297  	return key, value, totalLength, nil
   298  }
   299  
   300  // ParseVersionResources parses file version strings from the version resource
   301  // directory. This directory contains several structures starting with VS_VERSION_INFO
   302  // with references to children StringFileInfo structures. In addition, StringFileInfo
   303  // contains the StringTable structure with String entries describing the name and value
   304  // of each file version strings.
   305  func (pe *File) ParseVersionResources() (map[string]string, error) {
   306  	vers := make(map[string]string)
   307  	if pe.opts.OmitResourceDirectory {
   308  		return vers, nil
   309  	}
   310  	for _, e := range pe.Resources.Entries {
   311  		if e.ID != VersionResourceType {
   312  			continue
   313  		}
   314  
   315  		directory := e.Directory.Entries[0].Directory
   316  
   317  		for _, e := range directory.Entries {
   318  			ver, err := pe.parseVersionInfo(e)
   319  			if err != nil {
   320  				return vers, err
   321  			}
   322  			ff, err := pe.parseFixedFileInfo(e)
   323  			if err != nil {
   324  				return vers, err
   325  			}
   326  
   327  			offset := ff.GetStringFileInfoOffset(e)
   328  
   329  			for {
   330  				f, n, err := pe.parseStringFileInfo(offset, e)
   331  				if err != nil || f.Length == 0 {
   332  					break
   333  				}
   334  
   335  				switch n {
   336  				case StringFileInfoString:
   337  					tableOffset := f.GetStringTableOffset(offset)
   338  					for {
   339  						table, err := pe.parseStringTable(tableOffset, e)
   340  						if err != nil {
   341  							break
   342  						}
   343  						stringOffset := table.GetStringOffset(tableOffset, e)
   344  						for stringOffset < tableOffset+uint32(table.Length) {
   345  							k, v, l, err := pe.parseString(stringOffset, e)
   346  							if err != nil {
   347  								break
   348  							}
   349  							vers[k] = v
   350  							if l == 0 {
   351  								stringOffset = tableOffset + uint32(table.Length)
   352  							} else {
   353  								stringOffset = stringOffset + uint32(l)
   354  							}
   355  						}
   356  						// handle potential infinite loops
   357  						if uint32(table.Length)+tableOffset > tableOffset {
   358  							break
   359  						}
   360  						if tableOffset > uint32(f.Length) {
   361  							break
   362  						}
   363  					}
   364  				case VarFileInfoString:
   365  					break
   366  				default:
   367  					break
   368  				}
   369  
   370  				offset += uint32(f.Length)
   371  
   372  				// StringFileInfo/VarFileinfo structs consumed?
   373  				if offset >= uint32(ver.Length) {
   374  					break
   375  				}
   376  			}
   377  		}
   378  	}
   379  	return vers, nil
   380  }