Class: PEheader

Inherits:
Object
  • Object
show all
Defined in:
lib/metadata/util/win32/peheader.rb

Overview

Notes:

The peheader object member 'icons' is an array of icons in the file. Sub 0 is the application
icon, 1 is usually the document icon.  The format is the same as an .ico file.  The simple test
writes found icons to the root dir as icon0.ico, icon1.ico, etc.  Any icon editor will be able
to open them and display each resolution contained in the icon (if more than one).

Constant Summary collapse

IMAGE_NT_SIGNATURE =
"PE\0\0"
IMAGE_DOS_SIGNATURE =
"MZ"
IMAGE_DIRECTORY_ENTRY_EXPORT =

PE Header structures defined

From WINNT.H

// Directory Entries

// Export Directory

0
IMAGE_DIRECTORY_ENTRY_IMPORT =

// Import Directory

1
IMAGE_DIRECTORY_ENTRY_RESOURCE =

// Resource Directory

2
IMAGE_DIRECTORY_ENTRY_EXCEPTION =

// Exception Directory

3
IMAGE_DIRECTORY_ENTRY_SECURITY =

// Security Directory

4
IMAGE_DIRECTORY_ENTRY_BASERELOC =

// Base Relocation Table

5
IMAGE_DIRECTORY_ENTRY_DEBUG =

// Debug Directory

6
IMAGE_DIRECTORY_ENTRY_COPYRIGHT =

// Description String

7
IMAGE_DIRECTORY_ENTRY_GLOBALPTR =

// Machine Value (MIPS GP)

8
IMAGE_DIRECTORY_ENTRY_TLS =

// TLS Directory

9
IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG =

// Load Configuration Directory

10
IMAGE_NUMBEROF_DIRECTORY_ENTRIES =
16
RT_CURSOR =

From WinUser.h /*

  • Predefined Resource Types

*/

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_ICON =
14
RT_VERSION =
16
IMAGE_FILE_HEADER =
BinaryStruct.new([
  'v',   'Machine',
  'v',   'NumberOfSections',
  'V',   'TimeDateStamp',
  'V',   'PointerToSymbolTable',
  'V',   'NumberOfSymbols',
  'v',   'SizeOfOptionalHeader',
  'v',   'Characteristics',
])
SIZEOF_IMAGE_FILE_HEADER =
IMAGE_FILE_HEADER.size
IMAGE_DOS_HEADER =

// DOS .EXE header

BinaryStruct.new([       # // DOS .EXE header
  'v',   'e_magic',                     # // Magic number
  'v',   'e_cblp',                      # // Bytes on last page of file
  'v',   'e_cp',                        # // Pages in file
  'v',   'e_crlc',                      # // Relocations
  'v',   'e_cparhdr',                   # // Size of header in paragraphs
  'v',   'e_minalloc',                  # // Minimum extra paragraphs needed
  'v',   'e_maxalloc',                  # // Maximum extra paragraphs needed
  'v',   'e_ss',                        # // Initial (relative) SS value
  'v',   'e_sp',                        # // Initial SP value
  'v',   'e_csum',                      # // Checksum
  'v',   'e_ip',                        # // Initial IP value
  'v',   'e_cs',                        # // Initial (relative) CS value
  'v',   'e_lfarlc',                    # // File address of relocation table
  'v',   'e_ovno',                      # // Overlay number
  'v',   nil,                           # // Reserved words - e_res[4]
  'v',   nil,                           # // Reserved words - e_res[4]
  'v',   nil,                           # // Reserved words - e_res[4]
  'v',   nil,                           # // Reserved words - e_res[4]
  'v',   'e_oemid',                     # // OEM identifier (for e_oeminfo)
  'v',   'e_oeminfo',                   # // OEM information; e_oemid specific
  'v',   nil,                           # // Reserved words - e_res2[10]
  'v',   nil,                           # // Reserved words - e_res2[10]
  'v',   nil,                           # // Reserved words - e_res2[10]
  'v',   nil,                           # // Reserved words - e_res2[10]
  'v',   nil,                           # // Reserved words - e_res2[10]
  'v',   nil,                           # // Reserved words - e_res2[10]
  'v',   nil,                           # // Reserved words - e_res2[10]
  'v',   nil,                           # // Reserved words - e_res2[10]
  'v',   nil,                           # // Reserved words - e_res2[10]
  'v',   nil,                           # // Reserved words - e_res2[10]
  'V',   'e_lfanew',                    # // File address of new exe header
])
SIZEOF_IMAGE_DOS_HEADER =
IMAGE_DOS_HEADER.size
IMAGE_OPTIONAL_HEADER32 =
BinaryStruct.new([
  #    //
  #    // Standard fields.
  #    //

  'v',    'Magic',
  'c',    'MajorLinkerVersion',
  'c',    'MinorLinkerVersion',
  'V',    'SizeOfCode',
  'V',    'SizeOfInitializedData',
  'V',    'SizeOfUninitializedData',
  'V',    'AddressOfEntryPoint',
  'V',    'BaseOfCode',
  'V',    'BaseOfData',
  #
  #    //
  #    // NT additional fields.
  #    //
  #
  'V',    'ImageBase',
  'V',    'SectionAlignment',
  'V',    'FileAlignment',
  'v',    'MajorOperatingSystemVersion',
  'v',    'MinorOperatingSystemVersion',
  'v',    'MajorImageVersion',
  'v',    'MinorImageVersion',
  'v',    'MajorSubsystemVersion',
  'v',    'MinorSubsystemVersion',
  'V',    'Win32VersionValue',
  'V',    'SizeOfImage',
  'V',    'SizeOfHeaders',
  'V',    'CheckSum',
  'v',    'Subsystem',
  'v',    'DllCharacteristics',
  'V',    'SizeOfStackReserve',
  'V',    'SizeOfStackCommit',
  'V',    'SizeOfHeapReserve',
  'V',    'SizeOfHeapCommit',
  'V',    'LoaderFlags',
  'V',    'NumberOfRvaAndSizes',
])
SIZEOF_IMAGE_OPTIONAL_HEADER32 =
IMAGE_OPTIONAL_HEADER32.size
IMAGE_OPTIONAL_HEADER64 =
BinaryStruct.new([
  'v',    'Magic',
  'c',    'MajorLinkerVersion',
  'c',    'MinorLinkerVersion',
  'V',    'SizeOfCode',
  'V',    'SizeOfInitializedData',
  'V',    'SizeOfUninitializedData',
  'V',    'AddressOfEntryPoint',
  'V',    'BaseOfCode',
  'Q',    'ImageBase',
  'V',    'SectionAlignment',
  'V',    'FileAlignment',
  'v',    'MajorOperatingSystemVersion',
  'v',    'MinorOperatingSystemVersion',
  'v',    'MajorImageVersion',
  'v',    'MinorImageVersion',
  'v',    'MajorSubsystemVersion',
  'v',    'MinorSubsystemVersion',
  'V',    'Win32VersionValue',
  'V',    'SizeOfImage',
  'V',    'SizeOfHeaders',
  'V',    'CheckSum',
  'v',    'Subsystem',
  'v',    'DllCharacteristics',
  'Q',    'SizeOfStackReserve',
  'Q',    'SizeOfStackCommit',
  'Q',    'SizeOfHeapReserve',
  'Q',    'SizeOfHeapCommit',
  'V',    'LoaderFlags',
  'V',    'NumberOfRvaAndSizes',
])
SIZEOF_IMAGE_OPTIONAL_HEADER64 =
IMAGE_OPTIONAL_HEADER64.size
IMAGE_DATA_DIRECTORY =
BinaryStruct.new([
  'V',   :virtualAddress,
  'V',   :size,
])
SIZEOF_IMAGE_DATA_DIRECTORY =
IMAGE_DATA_DIRECTORY.size
IMAGE_SECTION_HEADER =
BinaryStruct.new([
  'a8',  'Name',
  #    union {
  #            DWORD   PhysicalAddress;
  #            DWORD   VirtualSize;
  #    } Misc;
  'V',   :VirtualSize,
  'V',   :virtualAddress,
  'V',   'SizeOfRawData',
  'V',   :PointerToRawData,
  'V',   'PointerToRelocations',
  'V',   'PointerToLinenumbers',
  'v',   'NumberOfRelocations',
  'v',   'NumberOfLinenumbers',
  'V',   'Characteristics',
])
SIZEOF_IMAGE_SECTION_HEADER =
IMAGE_SECTION_HEADER.size
IMAGE_IMPORT_DESCRIPTOR =
BinaryStruct.new([
  #    union {
  #        DWORD   Characteristics;           #// 0 for terminating null import descriptor
  #        DWORD   OriginalFirstThunk;        #// RVA to original unbound IAT (PIMAGE_THUNK_DATA)
  #    };
  'V',  'Characteristics',
  'V',  'TimeDateStamp',                  # // 0 if not bound,
  # // -1 if bound, and real date\time stamp
  # //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
  # // O.W. date/time stamp of DLL bound to (Old BIND)

  'V',   'ForwarderChain',                # // -1 if no forwarders
  'V',   'Name',
  'V',   'FirstThunk',                    # // RVA to IAT (if bound this IAT has actual addresses)
])
SIZEOF_IMAGE_IMPORT_DESCRIPTOR =
IMAGE_IMPORT_DESCRIPTOR.size
IMAGE_RESOURCE_DIRECTORY =

General resource definitions.

BinaryStruct.new([
  'L',  :characteristics,
  'L',  :timeDateStamp,
  'S',  :majorVersion,
  'S',  :minorVersion,
  'S',  :numberOfNamedEntries,              # Number of named entries that follow this struc (first).
  'S',  :numberOfIdEntries,               # Number of ID entries that follow this struc (second).
])
SIZEOF_IMAGE_RESOURCE_DIRECTORY =
IMAGE_RESOURCE_DIRECTORY.size
IMAGE_RESOURCE_DIRECTORY_ENTRY =
BinaryStruct.new([
  'L',  :name,                              # Name or ID. If bit 31 = 0 then ID. If bit 31 = 1, then
  # bits 0-30 are an offset (from start of rsrc) to IMAGE_RESOURCE_DIR_STRING_U.
  'L',  :offsetToData,                      # Ptr to dir or data. If bit 31 = 0, then ptr to
  # IMAGE_REDSOURCE_DATA_ENTRY. If bit 31 = 1, then bits 0-30 are ptr to IMAGE_RESOURCE_DIRECTORY.
])
SIZEOF_IMAGE_RESOURCE_DIRECTORY_ENTRY =

IMAGE_REDSOURCE_DATA_ENTRY. If bit 31 = 1, then bits 0-30 are ptr to IMAGE_RESOURCE_DIRECTORY.

IMAGE_RESOURCE_DIRECTORY_ENTRY.size
IMAGE_RESOURCE_DATA_ENTRY =

NOTE: Skipping string resource name because it is self-referencing: typedef struct _IMAGE_RESOURCE_DIR_STRING_U

WORD    Length;
WCHAR   NameString[ 1 ];

IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U; The member NameString is Length characters long, so the final size of the structure is unknown. This is just handled without BinaryStruct.

BinaryStruct.new([
  'L',  :offsetToData,                      # This offset is an RVA.
  'L',  :size,                              # Size in bytes.
  'L',  :codePage,                          # Code page (for strings).
  'L',  :reserved1,
])
SIZEOF_IMAGE_RESOURCE_DATA_ENTRY =
IMAGE_RESOURCE_DATA_ENTRY.size
GRPICONDIR =

Icon specific resource definitions.

BinaryStruct.new([
  'S',  :idReserved1,
  'S',  :idType,                            # 1 for icons.
  'S',  :idCount,                           # Count of images.
  'a*', :data,                              # Array of GRPICONDIRENTRY.
])
SIZEOF_GRPICONDIR =

TODO: BinaryStruct.sizeof ignores the *

GRPICONDIR.size
GRPICONDIRENTRY =
BinaryStruct.new([
  'C',  :bWidth,                            # Pixel width of image.
  'C',  :bHeight,                           # Pixel height of image.
  'C',  :bColorCount,                       # Colors in image (0 if >= 8bpp).
  'C',  :bReserved1,
  'S',  :wPlanes,                           # Color planes.
  'S',  :wBitCount,                         # Bits per pixel.
  'L',  :dwBytesInRes,                      # Bytes in this resource.
  'S',  :nID,                               # Resource ID.
  # NOTE: In an .ico file, last member is 'L', 'dwImageOffset', an offset
  # from the beginning of the file to the BITMAPINFOHEADER of the icon data.
])
SIZEOF_GRPICONDIRENTRY =

NOTE: In an .ico file, last member is ‘L’, ‘dwImageOffset’, an offset from the beginning of the file to the BITMAPINFOHEADER of the icon data.

GRPICONDIRENTRY.size
MESSAGE_RESOURCE_DATA =

Messagetable specific resource definitions.

BinaryStruct.new([
  'L',  :numberOfBlocks,                    # Length of data array.
  'a*', :data,                              # Array of MESSAGE_RESOURCE_BLOCK.
])
SIZEOF_MESSAGE_RESOURCE_DATA =

TODO: BinaryStruct.sizeof ignores the *

MESSAGE_RESOURCE_DATA.size
MESSAGE_RESOURCE_BLOCK =
BinaryStruct.new([
  'L',  :loId,
  'L',  :hiId,
  'L',  :offsetToEntries,                 # RVA?
])
SIZEOF_MESSAGE_RESOURCE_BLOCK =
MESSAGE_RESOURCE_BLOCK.size
SIZEOF_MRB =
12
MESSAGE_RESOURCE_ENTRY =
BinaryStruct.new([
  'S',  :length,                            # String length.
  'S',  :flags,                           # Encoding (see below).
])
SIZEOF_MRE =
4
SIZEOF_MESSAGE_RESOURCE_ENTRY =
MESSAGE_RESOURCE_ENTRY.size
MESSAGE_RESOURCE_ANSI =

Text follows here.

0x0000
MESSAGE_RESOURCE_UNICODE =

If set text is ANSI.

0x0001
VS_VERSION_INFO_HEADER =

If set text is UNICODE.

BinaryStruct.new([
  'a32', 'sig',
  's',   nil,
  's',   nil,
  's',   nil,
  's',   nil,
  's',   nil,
  'S',   'fminor',
  'S',   'fmajor',
  'S',   'fbuild',
  'S',   'frev',
  'S',   'pminor',
  'S',   'pmajor',
  'S',   'pbuild',
  'S',   'prev',
])
SIZEOF_VS_VERSION_INFO_HEADER =
VS_VERSION_INFO_HEADER.size
STRING_INFO_HEADER =
BinaryStruct.new([
  'a30', 'sig',
  'V',   'data_length',
  's',   'type',
  'a8',  'lang',
  'a8',  'code_page',
])
SIZEOF_STRING_INFO_HEADER =
STRING_INFO_HEADER.size
VERSION_STRING_HEADER =
BinaryStruct.new([
  's', 'zero',
  's', 'slen',
  's', 'vlen',
  's', 'type',
])
SIZEOF_VERSION_STRING_HEADER =
VERSION_STRING_HEADER.size
STRINGFILEINFO =
"S\0t\0r\0i\0n\0g\0F\0i\0l\0e\0I\0n\0f\0o\0\0\0"
VS_VERSION_INFO =
"V\0S\0_\0V\0E\0R\0S\0I\0O\0N\0_\0I\0N\0F\0O\0\0\0"
IMAGE_SIZEOF_NT_OPTIONAL32_HEADER =
224
IMAGE_SIZEOF_NT_OPTIONAL64_HEADER =
240
IMAGE_NT_OPTIONAL_HDR32_MAGIC =
0x10b
IMAGE_NT_OPTIONAL_HDR64_MAGIC =
0x20b

Instance Method Summary collapse

Constructor Details

#initialize(path) ⇒ PEheader

Returns a new instance of PEheader.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/metadata/util/win32/peheader.rb', line 19

def initialize(path)
  @fname = path
  @dataDirs = []
  @sectionTable = []
  @baseResDir = nil

  if path.class != String
    @fHnd = path
    fileseek(0, 'init')
    fBuf = fileread(2)
  else
    # Do some basic file validation
    raise Errno::ENOENT, "File [#{@fname}] does not exist." if File.exist?(@fname) == false
    raise "File [#{@fname}] is empty." if File.zero?(@fname)

    # Open file and read contents into buffer
    @fHnd = File.open(@fname, "rb")
    fBuf = fileread(2)
    @fHnd.close
  end

  # Check for the MZ header
  raise "Version Information header not found in file [#{@fname}]" unless fBuf[0..1] == IMAGE_DOS_SIGNATURE

  readPE
end

Instance Method Details

#adjustAddress(rva) ⇒ Object



411
412
413
414
415
416
417
418
419
420
# File 'lib/metadata/util/win32/peheader.rb', line 411

def adjustAddress(rva)
  @sectionTable.each do |s|
    # Is the RVA within this section?
    if  (rva >= s[:virtualAddress]) && (rva < (s[:virtualAddress] + s[:VirtualSize]))
      delta = s[:virtualAddress] - s[:PointerToRawData]
      return rva - delta
    end
  end
  nil
end

#assembleIcons(iconEntries, grpIcons) ⇒ Object



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/metadata/util/win32/peheader.rb', line 194

def assembleIcons(iconEntries, grpIcons)
  # For each major sub in grpIcons, construct an .ico blob.
  icons = []
  0.upto(grpIcons.size - 1) do |fileIdx|
    # Write icon directory.
    baseOffset = 16 * grpIcons[fileIdx].size + 6
    thisOffset = 0
    ico = StringIO.new
    ico.write([0].pack('S'))                      # idReserved
    ico.write([1].pack('S'))                      # idType
    ico.write([grpIcons[fileIdx].size].pack('S')) # idCount
    0.upto(grpIcons[fileIdx].size - 1) do |iconIdx|
      icon = grpIcons[fileIdx][iconIdx]
      # Write icon dir entry.
      ico.write([icon[:bWidth]].pack('C'))
      ico.write([icon[:bHeight]].pack('C'))
      ico.write([icon[:bColorCount]].pack('C'))
      ico.write([0].pack('C'))
      ico.write([icon[:wPlanes]].pack('S'))
      ico.write([icon[:wBitCount]].pack('S'))
      ico.write([icon[:dwBytesInRes]].pack('L'))
      ico.write([baseOffset + thisOffset].pack('L'))
      thisOffset += icon[:dwBytesInRes]
    end
    # Write icon data.
    0.upto(grpIcons[fileIdx].size - 1) { |iconIdx| ico.write(getIconById(iconEntries, grpIcons[fileIdx][iconIdx][:nID])) }
    # Save it as a string.
    ico.rewind
    icons << ico.read
  end
  icons
end

#dumpResourceDirectory(resDir, level, data_hash, rsc_id = nil) ⇒ Object



284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/metadata/util/win32/peheader.rb', line 284

def dumpResourceDirectory(resDir, level, data_hash, rsc_id = nil)
  resDirEntry = getResourceDirectoryEntry(resDir)

  # Process each entry in the directory.
  # Note: Named entries are listed first.
  1.upto(resDir[:numberOfNamedEntries]) do
    dumpResourceEntry(resDirEntry, level + 1, data_hash, rsc_id)
    resDirEntry = getNextResourceDirectoryEntry(resDirEntry)
  end

  1.upto(resDir[:numberOfIdEntries]) do
    dumpResourceEntry(resDirEntry, level + 1, data_hash, rsc_id)
    resDirEntry = getNextResourceDirectoryEntry(resDirEntry)
  end
end

#dumpResourceEntry(resDirEntry, level, data_hash, rsc_id) ⇒ Object



300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/metadata/util/win32/peheader.rb', line 300

def dumpResourceEntry(resDirEntry, level, data_hash, rsc_id)
  # 1.upto(level) {print "  "}

  resDirEntry[:name] = getResourceDirectoryEntryName(resDirEntry)
  resDirEntry[:level] = level
  if resDirEntry[:isDir]

    # Filter by resource type so we do not process every available resource
    return if level == 1 && rsc_id && resDirEntry[:name] != rsc_id

    resDir = getResourceDirectory(resDirEntry)
    resDirEntry[:numberOfIdEntries] = resDir[:numberOfIdEntries]
    resDirEntry[:numberOfNamedEntries] = resDir[:numberOfNamedEntries]
    # puts "DIR: #{resDirEntry.inspect}"

    data_hash[resDirEntry[:name]] = resDirEntry
    resDirEntry[:children] = {}

    dumpResourceDirectory(resDir, level, resDirEntry[:children], rsc_id)
  else
    data_hash[resDirEntry[:name]] = resDirEntry
    resDirEntry[:data] = IMAGE_RESOURCE_DATA_ENTRY.decode(@dataDirs[IMAGE_DIRECTORY_ENTRY_RESOURCE][:data][resDirEntry[:offsetToData]..-1])
    # puts "RSC: #{resDirEntry.inspect}"
  end
end

#fileread(length) ⇒ Object



85
86
87
88
89
90
# File 'lib/metadata/util/win32/peheader.rb', line 85

def fileread(length)
  # st = Time.now
  data = @fHnd.read(length)
  # $log.warn "read time [#{Time.now-st}]" if $log
  data
end

#fileseek(offset = 0, _message = nil) ⇒ Object

These file methods are here to assist in debugging



79
80
81
82
83
# File 'lib/metadata/util/win32/peheader.rb', line 79

def fileseek(offset = 0, _message = nil)
  # st = Time.now
  @fHnd.seek(offset, IO::SEEK_SET)
  # $log.warn "seek time [#{Time.now-st}] from [#{message}]" if $log
end

#find_all_resources(rsc, rsc_id = nil, &blk) ⇒ Object



383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
# File 'lib/metadata/util/win32/peheader.rb', line 383

def find_all_resources(rsc, rsc_id = nil, &blk)
  # Resource Directory Levels:
  # 1 = Resource Type
  # 2 = Resource Identifier
  # 3 = Resource Langauge ID
  rsc.each do |lang_id, item|
    if item[:isDir]
      rsc_id = item[:name] if item[:level] == 2
      find_all_resources(item[:children], rsc_id, &blk)
    else
      item[:rsc_id]  = rsc_id
      item[:lang_id] = lang_id
      yield(item)
    end
  end
end

#get_resources_by_type(rt, locale_id = nil) ⇒ Object



365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/metadata/util/win32/peheader.rb', line 365

def get_resources_by_type(rt, locale_id = nil)
  if (rsc = getDataEntries(@fBuf, rt)[rt])
    resources = []
    find_all_resources(rsc[:children]) { |r| resources << r }
    return if resources.empty?

    # Finding a resource is often by locale.  If we do not find the requested
    # locale then return the first one.
    unless locale_id.nil?
      local_rsc = resources.detect { |r| r[:lang_id] == locale_id }
      resources = local_rsc.nil? ? [resources.first] : [local_rsc]
    end

    # Yield the resource data to the caller
    resources.each { |r| yield(r) }
  end
end

#getBaseResDir(_fBuf) {|@baseResDir| ... } ⇒ Object

Yields:

  • (@baseResDir)


349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/metadata/util/win32/peheader.rb', line 349

def getBaseResDir(_fBuf)
  if @baseResDir.nil?
    rsc = @dataDirs[IMAGE_DIRECTORY_ENTRY_RESOURCE]
    rsc[:offset] = rsc[:virtualAddress]
    unless rsc[:offset].zero?
      rsc[:offset] = adjustAddress(rsc[:offset])
      # Read in the resource part the file
      fileseek(rsc[:offset], 'getBaseResDir')
      rsc[:data] = fileread(rsc[:size])
      @baseResDir = IMAGE_RESOURCE_DIRECTORY.decode(rsc[:data][0, SIZEOF_IMAGE_RESOURCE_DIRECTORY])
      @baseResDir[:offset_into_data] = 0
    end
  end
  yield(@baseResDir) unless @baseResDir.nil?
end

#getDataDirs(fBuf, offset) ⇒ Object

////////////////////////////////////////////////////////////////////////// //



111
112
113
114
115
116
117
118
119
# File 'lib/metadata/util/win32/peheader.rb', line 111

def getDataDirs(fBuf, offset)
  offset += @IMAGE_OPTIONAL_HEADER.size
  IMAGE_NUMBEROF_DIRECTORY_ENTRIES.times do
    ddHash = IMAGE_DATA_DIRECTORY.decode(fBuf[offset..-1])
    offset += SIZEOF_IMAGE_DATA_DIRECTORY
    @dataDirs.push(ddHash)
  end
  offset
end

#getDataEntries(fBuf, rsc_id = nil) ⇒ Object

Walk the resource directories and collect all the directories and resource pointers



278
279
280
281
282
# File 'lib/metadata/util/win32/peheader.rb', line 278

def getDataEntries(fBuf, rsc_id = nil)
  result = {}
  getBaseResDir(fBuf) { |baseResDir| dumpResourceDirectory(baseResDir, 0, result, rsc_id) }
  result
end

#getIconById(icons, id) ⇒ Object

Find a particular raw icon.



228
229
230
231
# File 'lib/metadata/util/win32/peheader.rb', line 228

def getIconById(icons, id)
  icons.each { |icon| return icon[:icon] if icon[:rsc_id] == id }
  nil
end

#getIconDirEntries(_fBuf) ⇒ Object



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/metadata/util/win32/peheader.rb', line 177

def getIconDirEntries(_fBuf)
  # Read icon directory.
  grpIcons = []
  # iconDirEntries = getDataEntries(RT_GROUP_ICON, fBuf)
  get_resources_by_type(RT_GROUP_ICON) do |icon_rsc|
    ent = icon_rsc[:data]
    ent[:offset] = adjustAddress(ent[:offsetToData])
    fileseek(ent[:offset], 'getIconDirEntries')
    iconDir = fileread(ent[:size])
    iconDir = GRPICONDIR.decode(iconDir)
    grpIconDirEntries = []
    0.upto(iconDir[:idCount] - 1) { |i| grpIconDirEntries << GRPICONDIRENTRY.decode(iconDir[:data][i * SIZEOF_GRPICONDIRENTRY, SIZEOF_GRPICONDIRENTRY]) }
    grpIcons << grpIconDirEntries
  end
  grpIcons
end

#getIcons(fBuf) ⇒ Object



158
159
160
161
162
# File 'lib/metadata/util/win32/peheader.rb', line 158

def getIcons(fBuf)
  iconEntries = getRawIcons(fBuf)
  grpIcons = getIconDirEntries(fBuf)
  assembleIcons(iconEntries, grpIcons)
end

#getImportListObject



422
423
424
425
426
427
428
429
# File 'lib/metadata/util/win32/peheader.rb', line 422

def getImportList
  return nil if imports.nil?
  unless imports.empty?
    import_list = ""
    imports.each { |i| import_list += i + ", " }
    return import_list.rstrip.chomp(",")
  end
end

#getImportsObject



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/metadata/util/win32/peheader.rb', line 130

def getImports
  imports_libs = []
  import = @dataDirs[IMAGE_DIRECTORY_ENTRY_IMPORT]
  import[:offset] = import[:virtualAddress]
  if import[:offset] != 0
    import[:offset] = adjustAddress(import[:offset])
    fileseek(import[:offset], 'getImports')
    data = fileread(import[:size])
    offset = 0
    loop do
      iiHash = IMAGE_IMPORT_DESCRIPTOR.decode(data[offset..-1])
      break if iiHash['Name'] == 0
      offset += SIZEOF_IMAGE_IMPORT_DESCRIPTOR
      iiHash['Name'] = adjustAddress(iiHash['Name']) - import[:offset]

      # Check if we have enough data.  This happens if the import data only contains pointers
      if (data.length <= iiHash['Name'])
        size = iiHash['Name'] - data.length + 4096
        data += fileread(size)
      end

      nameEnd = iiHash['Name'] + data[iiHash['Name']..-1].index("\0") - 1
      imports_libs.push(data[iiHash['Name']..nameEnd].downcase)
    end
  end
  imports_libs
end

#getMessagetables(requested_locale = 0x0409) ⇒ Object



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/metadata/util/win32/peheader.rb', line 233

def getMessagetables(requested_locale = 0x0409)
  # Read message table resources.
  messagetables = {}
  get_resources_by_type(RT_MESSAGETABLE, requested_locale) do |msg_resource|
    # Get the block directory for this messagetable.
    msg_data = msg_resource[:data]
    offset = adjustAddress(msg_data[:offsetToData]) - @dataDirs[IMAGE_DIRECTORY_ENTRY_RESOURCE][:offset]
    blkdir = MESSAGE_RESOURCE_DATA.decode(@dataDirs[IMAGE_DIRECTORY_ENTRY_RESOURCE][:data][offset, msg_data[:size]])
    0.upto(blkdir[:numberOfBlocks] - 1) do |i|
      # Break out each block.
      blk = MESSAGE_RESOURCE_BLOCK.decode(blkdir[:data][i * SIZEOF_MRB, SIZEOF_MRB])
      adrs = blk[:offsetToEntries] - 4

      # Grab the block's strings.
      blk[:loId].upto(blk[:hiId]) do |idx|
        ent1 = MESSAGE_RESOURCE_ENTRY.decode(blkdir[:data][adrs, SIZEOF_MRE])
        if ent1[:length] > 0
          len = ent1[:length] - SIZEOF_MRE
          str = blkdir[:data][adrs + SIZEOF_MRE, len]
          (ent1[:flags] == MESSAGE_RESOURCE_UNICODE) ? str.UnicodeToUtf8! : str.AsciiToUtf8!
          str.gsub!(/\000/, "")
          messagetables[idx] = str
          adrs += len
        end
        adrs += SIZEOF_MRE
      end
    end
  end
  messagetables
end

#getNextResourceDirectoryEntry(resDirEntry) ⇒ Object



337
338
339
# File 'lib/metadata/util/win32/peheader.rb', line 337

def getNextResourceDirectoryEntry(resDirEntry)
  getNextResourceResourceEntry(resDirEntry, IMAGE_RESOURCE_DIRECTORY_ENTRY, SIZEOF_IMAGE_RESOURCE_DIRECTORY_ENTRY)
end

#getNextResourceResourceEntry(resEntry, rsc_type, size) ⇒ Object



341
342
343
344
345
346
347
# File 'lib/metadata/util/win32/peheader.rb', line 341

def getNextResourceResourceEntry(resEntry, rsc_type, size)
  offset = resEntry[:offset_into_data] + size
  entry = rsc_type.decode(@dataDirs[IMAGE_DIRECTORY_ENTRY_RESOURCE][:data][offset..-1])
  entry[:offset_into_data] = offset
  entry[:isDir] = bit?(entry[:offsetToData], 31)
  entry
end

#getRawIcons(_fBuf) ⇒ Object



164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/metadata/util/win32/peheader.rb', line 164

def getRawIcons(_fBuf)
  # Read raw icons.
  iconEntries = []
  get_resources_by_type(RT_ICON) do |icon_rsc|
    ent = icon_rsc[:data]
    ent[:offset] = adjustAddress(ent[:offsetToData])
    fileseek(ent[:offset], 'getRawIcons')
    icon_rsc[:icon] = fileread(ent[:size])
    iconEntries << icon_rsc
  end
  iconEntries
end

#getResourceDirectory(resDirEntry) ⇒ Object



326
327
328
329
330
331
# File 'lib/metadata/util/win32/peheader.rb', line 326

def getResourceDirectory(resDirEntry)
  offset = resDirEntry[:offsetToData] & 0x7fffffff
  resDir = IMAGE_RESOURCE_DIRECTORY.decode(@dataDirs[IMAGE_DIRECTORY_ENTRY_RESOURCE][:data][offset..-1])
  resDir[:offset_into_data] = offset
  resDir
end

#getResourceDirectoryEntry(resDir) ⇒ Object



333
334
335
# File 'lib/metadata/util/win32/peheader.rb', line 333

def getResourceDirectoryEntry(resDir)
  getNextResourceResourceEntry(resDir, IMAGE_RESOURCE_DIRECTORY_ENTRY, SIZEOF_IMAGE_RESOURCE_DIRECTORY)
end

#getResourceDirectoryEntryName(resDirEntry) ⇒ Object



400
401
402
403
404
405
406
407
408
409
# File 'lib/metadata/util/win32/peheader.rb', line 400

def getResourceDirectoryEntryName(resDirEntry)
  return resDirEntry[:name] unless bit?(resDirEntry[:name], 31)

  # The low 30 bits of the 'Name' member is an offset to an IMAGE_RESOURCE_DIRECTORY_STRING_U struct.
  str = ""
  ptr = (resDirEntry[:name] & 0x7fffffff)
  len = @dataDirs[IMAGE_DIRECTORY_ENTRY_RESOURCE][:data][ptr, 2].unpack('S')[0]
  str = @dataDirs[IMAGE_DIRECTORY_ENTRY_RESOURCE][:data][ptr + 2, len * 2]
  str.UnicodeToUtf8!
end

#getSectionTable(fBuf, fhHash, offset) ⇒ Object



121
122
123
124
125
126
127
128
# File 'lib/metadata/util/win32/peheader.rb', line 121

def getSectionTable(fBuf, fhHash, offset)
  fhHash['NumberOfSections'].times do
    shHash = IMAGE_SECTION_HEADER.decode(fBuf[offset..-1])
    offset += SIZEOF_IMAGE_SECTION_HEADER
    @sectionTable.push(shHash)
  end
  offset
end

#getVersioninfo(requested_locale = 0x0409) ⇒ Object

Get versioninfo resource.



265
266
267
268
269
270
271
272
273
274
275
# File 'lib/metadata/util/win32/peheader.rb', line 265

def getVersioninfo(requested_locale = 0x0409)
  aVersioninfoHash = {}
  get_resources_by_type(RT_VERSION, requested_locale) do |versionEntry|
    ent = versionEntry[:data]
    offset = adjustAddress(ent[:offsetToData]) - @dataDirs[IMAGE_DIRECTORY_ENTRY_RESOURCE][:offset]
    versionInfo = @dataDirs[IMAGE_DIRECTORY_ENTRY_RESOURCE][:data][offset, ent[:size]]
    # versionInfo is a VS_FIXEDFILEINFO structure followed by StringFileInfo.
    aVersioninfoHash = getVersionInfoHash(versionInfo)
  end
  aVersioninfoHash
end

#getVersionInfoHash(fBuf) ⇒ Object



431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
# File 'lib/metadata/util/win32/peheader.rb', line 431

def getVersionInfoHash(fBuf)
  viHash = {}

  # Find VS Version Info signature
  idx = fBuf.index(VS_VERSION_INFO)
  return viHash unless idx
  # raise "Version Information header not found in file" unless idx
  # $log.debug sprintf("Found at index: [0X%08X] (%d)", idx, idx)

  # Reduce buffer to just the signature to the end of the file
  fBuf = fBuf[idx..fBuf.length]
  offset = 0
  vhHash = VS_VERSION_INFO_HEADER.decode(fBuf[offset..(offset + SIZEOF_VS_VERSION_INFO_HEADER)])

  # Create VersionInfo hash
  viHash['FILEVERSION_HEADER']    = vhHash['fmajor'].to_s + "," + vhHash['fminor'].to_s + "," + vhHash['frev'].to_s + "," + vhHash['fbuild'].to_s
  viHash['PRODUCTVERSION_HEADER'] = vhHash['pmajor'].to_s + "," + vhHash['pminor'].to_s + "," + vhHash['prev'].to_s + "," + vhHash['pbuild'].to_s

  # Find the string file info signautre
  idx = fBuf.index(STRINGFILEINFO)
  return viHash unless idx
  # raise "String File information header not found in file [#{fname}]" unless idx

  offset = idx
  viEnd = offset + SIZEOF_STRING_INFO_HEADER
  viHash.merge!(STRING_INFO_HEADER.decode(fBuf[offset..viEnd]))
  viHash['sig'].UnicodeToUtf8!.tr!("\0", "")
  viHash['code_page'].UnicodeToUtf8!.tr!("\0", "")
  viHash['lang'].UnicodeToUtf8!.tr!("\0", "")

  # Read offsets for next string
  offset = viEnd
  vsHash = VERSION_STRING_HEADER.decode(fBuf[offset..offset + 6])

  # Calculate the amount of version info data
  offset_end = offset + viHash['data_length'] - SIZEOF_STRING_INFO_HEADER

  while offset < offset_end
    break unless vsHash['zero'] == 0
    break if vsHash['zero'].nil? || vsHash['vlen'].nil? || vsHash['slen'].nil?
    offset += SIZEOF_VERSION_STRING_HEADER
    name_len = vsHash['slen'] - 4 - (vsHash['vlen'] * 2) - 2
    name = fBuf[offset...offset + name_len]
    offset += name_len
    value_len = (vsHash['vlen'] * 2) - 2
    value = fBuf[offset...offset + value_len]
    break if name.nil? or value.nil? or name.empty?
    name.UnicodeToUtf8!.delete!("\0")
    # Do not allow spaces in the attribute names (will invalidate a XML file)
    name.tr!(" ", "_")
    value.UnicodeToUtf8!.delete!("\0")
    # $log.debug "[#{name}] => [#{value}]"
    viHash[name] = value
    offset += value_len + (vsHash['vlen'] % 2 * 2)

    # Read next offset header
    vsHash = VERSION_STRING_HEADER.decode(fBuf[offset..offset + 6])

    # This is a work-around.  In case the offset to the next record is slightly off
    unless vsHash['zero'] == 0
      offset -= 2
      # Read next offset header
      vsHash = VERSION_STRING_HEADER.decode(fBuf[offset..offset + 6])
    end
  end

  viHash
end

#iconsObject



96
97
98
# File 'lib/metadata/util/win32/peheader.rb', line 96

def icons
  @icons ||= getIcons(@fBuf)
end

#importsObject



92
93
94
# File 'lib/metadata/util/win32/peheader.rb', line 92

def imports
  @imports ||= getImports
end

#messagetablesObject



100
101
102
# File 'lib/metadata/util/win32/peheader.rb', line 100

def messagetables
  @messagetables ||= getMessagetables
end

#readPEObject



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/metadata/util/win32/peheader.rb', line 46

def readPE
  if @fname.class != String
    @fHnd = @fname
    fileseek(0, 'readPE')
  else
    # Open file
    @fHnd = File.open(@fname, "rb")
  end

  # Read contents into buffer
  # TODO: determine the proper amount of data to load here
  @fBuf = fileread(10240)

  # Read offsets for next string
  dhHash = IMAGE_DOS_HEADER.decode(@fBuf)
  offset = dhHash['e_lfanew'] # Offset to PE header
  raise "PE header not found in file [#{@fname}]" unless @fBuf[offset...offset + 4] == IMAGE_NT_SIGNATURE
  offset += 4
  fhHash = IMAGE_FILE_HEADER.decode(@fBuf[offset..-1])
  offset += SIZEOF_IMAGE_FILE_HEADER

  @is64Bit = fhHash['SizeOfOptionalHeader'] == IMAGE_SIZEOF_NT_OPTIONAL64_HEADER
  @IMAGE_OPTIONAL_HEADER = (@is64Bit == true) ? IMAGE_OPTIONAL_HEADER64 : IMAGE_OPTIONAL_HEADER32

  # Commented out the following, since it is not currently being used.
  # ohHash = @IMAGE_OPTIONAL_HEADER.decode(@fBuf[offset, @IMAGE_OPTIONAL_HEADER.size])

  # Read all the data directories & section table.
  offset = getDataDirs(@fBuf, offset)
  offset = getSectionTable(@fBuf, fhHash, offset)
end

#versioninfoObject



104
105
106
# File 'lib/metadata/util/win32/peheader.rb', line 104

def versioninfo
  @versioninfo ||= getVersioninfo
end