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 }