Class: PEdump

Inherits:
Object show all
Defined in:
lib/pedump.rb,
lib/pedump/ne.rb,
lib/pedump/pe.rb,
lib/pedump/tls.rb,
lib/pedump/core.rb,
lib/pedump/logger.rb,
lib/pedump/packer.rb,
lib/pedump/version.rb,
lib/pedump/security.rb,
lib/pedump/resources.rb,
lib/pedump/sig_parser.rb,
lib/pedump/composite_io.rb,
lib/pedump/version_info.rb,
lib/pedump/ne/version_info.rb

Overview

pedump.rb by zed_0xff

http://zed.0xff.me
http://github.com/zed-0xff

Defined Under Namespace

Modules: IMAGE_OPTIONAL_HEADER, Readable, SigParser, Unpacker, Version Classes: BITMAPINFOHEADER, CLI, ColoredLogger, Comparer, CompositeIO, DOSStub, ExportedFunction, IMAGE_FILE_HEADER, IMAGE_OPTIONAL_HEADER32, IMAGE_OPTIONAL_HEADER64, IMAGE_RESOURCE_DIRECTORY, IMAGE_SECTION_HEADER, ImportedFunction, Loader, Logger, NE, PE, Packer, Resource, RichHdr, STRING, StringFileInfo, StringTable, VS_FIXEDFILEINFO, VS_VERSIONINFO, Var, VarFileInfo, VersionString, WIN_CERTIFICATE

Constant Summary collapse

VERSION =
Version::STRING
MZ =
create_struct( "a2v13Qv2V6",
  :signature,
  :bytes_in_last_block,
  :blocks_in_file,
  :num_relocs,
  :header_paragraphs,
  :min_extra_paragraphs,
  :max_extra_paragraphs,
  :ss,
  :sp,
  :checksum,
  :ip,
  :cs,
  :reloc_table_offset,
  :overlay_number,
  :reserved0,           #  8 reserved bytes
  :oem_id,
  :oem_info,
  :reserved2,           # 20 reserved bytes
  :reserved3,
  :reserved4,
  :reserved5,
  :reserved6,
  :lfanew
)
IMAGE_DATA_DIRECTORY =
create_struct( "VV", :va, :size, :type )
IMAGE_SUBSYSTEMS =
%w'UNKNOWN NATIVE WINDOWS_GUI WINDOWS_CUI' + [nil,'OS2_CUI',nil,'POSIX_CUI',nil] +
%w'WINDOWS_CE_GUI EFI_APPLICATION EFI_BOOT_SERVICE_DRIVER EFI_RUNTIME_DRIVER EFI_ROM XBOX' +
[nil, 'WINDOWS_BOOT_APPLICATION']
IMAGE_IMPORT_DESCRIPTOR =
create_struct 'V5',
:OriginalFirstThunk,
:TimeDateStamp,
:ForwarderChain,
:Name,
:FirstThunk,
# manual:
:module_name,
:original_first_thunk,
:first_thunk
IMAGE_EXPORT_DIRECTORY =
create_struct 'V2v2V2l2V3',
:Characteristics,
:TimeDateStamp,
:MajorVersion,          # These fields appear to be unused and are set to 0.
:MinorVersion,          # These fields appear to be unused and are set to 0.
:Name,
:Base,                  # The starting ordinal number for exported functions
:NumberOfFunctions,
:NumberOfNames,
:AddressOfFunctions,
:AddressOfNames,
:AddressOfNameOrdinals,
# manual:
:name, :entry_points, :names, :name_ordinals, :functions,
:description
IMAGE_TLS_DIRECTORY32 =
create_struct 'V6',
:StartAddressOfRawData,
:EndAddressOfRawData,
:AddressOfIndex,
:AddressOfCallBacks,
:SizeOfZeroFill,
:Characteristics
IMAGE_TLS_DIRECTORY64 =
create_struct 'Q4V2',
:StartAddressOfRawData,
:EndAddressOfRawData,
:AddressOfIndex,
:AddressOfCallBacks,
:SizeOfZeroFill,
:Characteristics
CUR_ICO_HEADER =
create_struct('v3',
  :wReserved, # always 0
  :wResID,    # always 2
  :wNumImages # Number of cursor images/directory entries
)
CURDIRENTRY =
create_struct 'v4Vv',
:wWidth,
:wHeight, # Divide by 2 to get the actual height.
:wPlanes,
:wBitCount,
:dwBytesInImage,
:wID
CURSOR_HOTSPOT =
create_struct 'v2', :x, :y
ICODIRENTRY =
create_struct 'C4v2Vv',
:bWidth,
:bHeight,
:bColors,
:bReserved,
:wPlanes,
:wBitCount,
:dwBytesInImage,
:wID
ROOT_RES_NAMES =

numeration is started from 1

[nil] + # numeration is started from 1
%w'CURSOR BITMAP ICON MENU DIALOG STRING FONTDIR FONT ACCELERATORS RCDATA' +
%w'MESSAGETABLE GROUP_CURSOR' + [nil] + %w'GROUP_ICON' + [nil] +
%w'VERSION DLGINCLUDE' + [nil] + %w'PLUGPLAY VXD ANICURSOR ANIICON HTML MANIFEST'
IMAGE_RESOURCE_DIRECTORY_ENTRY =
create_struct 'V2',
:Name, :OffsetToData,
:name, :data
IMAGE_RESOURCE_DATA_ENTRY =
create_struct 'V4',
:OffsetToData, :Size, :CodePage, :Reserved
@@logger =
nil

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io = nil, params = {}) ⇒ PEdump

Returns a new instance of PEdump.



25
26
27
28
29
30
31
32
33
# File 'lib/pedump.rb', line 25

def initialize io = nil, params = {}
  if io.is_a?(Hash)
    @io, params = nil, io
  else
    @io = io
  end
  @force = params[:force]
  @logger = @@logger = Logger.create(params)
end

Instance Attribute Details

#fnameObject

Returns the value of attribute fname.



19
20
21
# File 'lib/pedump.rb', line 19

def fname
  @fname
end

#forceObject

Returns the value of attribute force.



19
20
21
# File 'lib/pedump.rb', line 19

def force
  @force
end

#ioObject

Returns the value of attribute io.



19
20
21
# File 'lib/pedump.rb', line 19

def io
  @io
end

#loggerObject

Returns the value of attribute logger.



19
20
21
# File 'lib/pedump.rb', line 19

def logger
  @logger
end

Class Method Details

.create_struct(fmt, *args) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/pedump/core.rb', line 58

def create_struct fmt, *args
  size = fmt.scan(/([a-z])(\d*)/i).map do |f,len|
    [len.to_i, 1].max *
      case f
      when /[aAC]/ then 1
      when 'v' then 2
      when 'V','l' then 4
      when 'Q' then 8
      else raise "unknown fmt #{f.inspect}"
      end
  end.inject(&:+)

  Struct.new( *args ).tap do |x|
    x.const_set 'FORMAT', fmt
    x.const_set 'SIZE',  size
    x.class_eval do
      def pack
        to_a.pack self.class.const_get('FORMAT')
      end
      def empty?
        to_a.all?{ |t| t == 0 || t.nil? || t.to_s.tr("\x00","").empty? }
      end
    end
    x.extend Readable
  end
end

.dump(fname, params = {}) ⇒ Object



297
298
299
# File 'lib/pedump.rb', line 297

def self.dump fname, params = {}
  new(fname, params).dump
end

.loggerObject



55
# File 'lib/pedump/core.rb', line 55

def logger;    @@logger;   end

.logger=(l) ⇒ Object



56
# File 'lib/pedump/core.rb', line 56

def logger= l; @@logger=l; end

.quietObject



301
302
303
304
305
306
307
# File 'lib/pedump.rb', line 301

def self.quiet
  oldlevel = @@logger.level
  @@logger.level = ::Logger::FATAL
  yield
ensure
  @@logger.level = oldlevel
end

Instance Method Details

#_dump_handle(h) ⇒ Object



413
414
415
416
417
418
419
420
# File 'lib/pedump.rb', line 413

def _dump_handle h
  return unless pe(h) # also calls mz(h)
  rich_hdr h
  resources h
  imports h   # also calls tls(h)
  exports h
  packer h
end

#_read_resource_directory_tree(f) ⇒ Object



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/pedump/resources.rb', line 12

def _read_resource_directory_tree f
  return nil unless pe(f) && pe(f).ioh && f
  res_dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::RESOURCE]
  return [] if !res_dir || (res_dir.va == 0 && res_dir.size == 0)
  res_va = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::RESOURCE].va
  res_section = @pe.section_table.find{ |t| t.VirtualAddress == res_va }
  unless res_section
    logger.warn "[?] can't find resource section for va=0x#{res_va.to_s(16)}"
    return []
  end
  f.seek res_section.PointerToRawData
  IMAGE_RESOURCE_DIRECTORY.base = res_section.PointerToRawData
  #@resource_data_base = res_section.PointerToRawData - res_section.VirtualAddress
  IMAGE_RESOURCE_DIRECTORY.read(f)
end

#_scan_pe_resources(f = @io, dir = nil) ⇒ Object



326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
# File 'lib/pedump/resources.rb', line 326

def _scan_pe_resources f=@io, dir=nil
  dir ||= resource_directory(f)
  return nil unless dir
  dir.entries.map do |entry|
    case entry.data
      when IMAGE_RESOURCE_DIRECTORY
        if dir == @resource_directory # root resource directory
          entry_type =
            if entry.Name & 0x8000_0000 == 0
              # root resource directory & entry name is a number
              ROOT_RES_NAMES[entry.Name] || entry.name
            else
              entry.name
            end
          _scan_pe_resources(f,entry.data).each do |res|
            res.type = entry_type
            res.parse f
          end
        else
          _scan_pe_resources(f,entry.data).each do |res|
            res.name = res.name == "##{res.lang}" ? entry.name : "#{entry.name} / #{res.name}"
            res.id ||= entry.Name if entry.Name.is_a?(Numeric) && entry.Name < 0x8000_0000
          end
        end
      when IMAGE_RESOURCE_DATA_ENTRY
        Resource.new(
          nil,          # type
          entry.name,
          nil,          # id
          entry.Name,   # lang
          #entry.data.OffsetToData + @resource_data_base,
          va2file(entry.data.OffsetToData),
          entry.data.Size,
          entry.data.CodePage,
          entry.data.Reserved
        )
      else
        logger.error "[!] invalid resource entry: #{entry.data.inspect}"
        nil
    end
  end.flatten.compact
end

#data_directory(f = @io) ⇒ Object



422
423
424
# File 'lib/pedump.rb', line 422

def data_directory f=@io
  pe(f) && pe.ioh && pe.ioh.DataDirectory
end

#dos_stub(f = @io) ⇒ Object



322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'lib/pedump.rb', line 322

def dos_stub f=@io
  @dos_stub ||=
    begin
      return nil unless mz = mz(f)
      dos_stub_offset = mz.header_paragraphs.to_i * 0x10
      dos_stub_size   = mz.lfanew.to_i - dos_stub_offset
      if dos_stub_offset < 0
        logger.warn "[?] invalid DOS stub offset #{dos_stub_offset}"
        nil
      elsif f && dos_stub_offset > f.size
        logger.warn "[?] DOS stub offset beyond EOF: #{dos_stub_offset}"
        nil
      elsif dos_stub_size < 0
        logger.warn "[?] invalid DOS stub size #{dos_stub_size}"
        nil
      elsif dos_stub_size == 0
        # no DOS stub, it's ok
        nil
      elsif !f
        # no open file, it's ok
        nil
      else
        return nil if dos_stub_size == MZ::SIZE && dos_stub_offset == 0
        if dos_stub_size > 0x1000
          logger.warn "[?] DOS stub size too big (#{dos_stub_size}), limiting to 0x1000"
          dos_stub_size = 0x1000
        end
        f.seek dos_stub_offset
        DOSStub.new(f.read(dos_stub_size)).tap do |dos_stub|
          dos_stub.offset = dos_stub_offset
          if dos_stub['Rich']
            if @rich_hdr = RichHdr.from_dos_stub(dos_stub)
              dos_stub[dos_stub.index(@rich_hdr)..-1] = ''
            end
          end
        end
      end
    end
end

#dump(f = @io) ⇒ Object

OPTIONAL: assigns @mz, @rich_hdr, @pe, etc



402
403
404
405
406
407
408
409
410
411
# File 'lib/pedump.rb', line 402

def dump f=@io
  if f.is_a?(String)
    File.open(f,'rb'){ |f| _dump_handle(f) }
  elsif f.is_a?(::IO)
    _dump_handle f
  elsif @io
    _dump_handle @io
  end
  self
end

#exports(f = @io) ⇒ Object



608
609
610
611
612
613
614
# File 'lib/pedump.rb', line 608

def exports f=@io
  if pe(f)
    pe_exports(f)
  elsif ne(f)
    ne(f).exports
  end
end

#imports(f = @io) ⇒ Object



479
480
481
482
483
484
485
# File 'lib/pedump.rb', line 479

def imports f=@io
  if pe(f)
    pe_imports(f)
  elsif ne(f)
    ne(f).imports
  end
end

#mz(f = @io) ⇒ Object



309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/pedump.rb', line 309

def mz f=@io
  @mz ||= f && MZ.read(f).tap do |mz|
    if mz.signature != 'MZ' && mz.signature != 'ZM'
      if @force
        logger.warn  "[?] no MZ signature. want: 'MZ' or 'ZM', got: #{mz.signature.inspect}"
      else
        logger.error "[!] no MZ signature. want: 'MZ' or 'ZM', got: #{mz.signature.inspect}. (not forced)"
        return nil
      end
    end
  end
end

#ne(f = @io) ⇒ Object



402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
# File 'lib/pedump/ne.rb', line 402

def ne f=@io
  return @ne if defined?(@ne)
  @ne ||=
    begin
      ne_offset = mz(f) && mz(f).lfanew
      if ne_offset.nil?
        logger.fatal "[!] NULL NE offset (e_lfanew)."
        nil
      elsif ne_offset > f.size
        logger.fatal "[!] NE offset beyond EOF."
        nil
      else
        f.seek ne_offset
        if f.read(2) == 'NE'
          f.seek ne_offset
          NE.read f
        else
          nil
        end
      end
    end
end

#ne?Boolean

Returns:

  • (Boolean)


435
436
437
# File 'lib/pedump.rb', line 435

def ne?
  @pe ? false : (@ne ? true : (pe ? false : (ne ? true : false)))
end

#packer(f = @io) ⇒ Object Also known as: packers

packer / compiler detection



763
764
765
766
767
768
769
770
771
772
773
# File 'lib/pedump.rb', line 763

def packer f=@io
  @packer ||= pe(f) && @pe.ioh &&
    begin
      if PEdump::Packer.all.size == 0
        logger.error "[?] no packer definitions found"
        nil
      else
        Packer.of f, :pedump => self
      end
    end
end

#pe(f = @io) ⇒ Object



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/pedump/pe.rb', line 95

def pe f=@io
  @pe ||=
    begin
      pe_offset = mz(f) && mz(f).lfanew
      if pe_offset.nil?
        logger.fatal "[!] NULL PE offset (e_lfanew). cannot continue."
        nil
      elsif pe_offset > f.size
        logger.fatal "[!] PE offset beyond EOF. cannot continue."
        nil
      else
        f.seek pe_offset
        PE.read f, :force => @force
      end
    end
end

#pe?Boolean

Returns:

  • (Boolean)


439
440
441
# File 'lib/pedump.rb', line 439

def pe?
  @pe ? true  : (@ne ? false : (pe ? true : false ))
end

#pe_exports(f = @io) ⇒ Object



616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
# File 'lib/pedump.rb', line 616

def pe_exports f=@io
  return @exports if @exports
  return nil unless pe(f) && pe(f).ioh && f
  dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::EXPORT]
  return nil if !dir || (dir.va == 0 && dir.size == 0)
  va = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::EXPORT].va
  file_offset = va2file(va)
  return nil unless file_offset
  f.seek file_offset
  if f.eof?
    logger.info "[?] exports info beyond EOF"
    return nil
  end
  @exports = IMAGE_EXPORT_DIRECTORY.read(f).tap do |x|
    x.entry_points = []
    x.name_ordinals = []
    x.names = []
    if x.Name.to_i != 0 && (ofs = va2file(x.Name))
      f.seek ofs
      if f.eof?
        logger.warn "[?] export ofs 0x#{ofs.to_s(16)} beyond EOF"
        nil
      else
        x.name = f.gets("\x00").chomp("\x00")
      end
    end
    if x.NumberOfFunctions.to_i > 0
      if x.AddressOfFunctions.to_i !=0 && (ofs = va2file(x.AddressOfFunctions))
        f.seek ofs
        x.entry_points = []
        x.NumberOfFunctions.times do
          if f.eof?
            logger.warn "[?] got EOF while reading exports entry_points"
            break
          end
          x.entry_points << f.read(4).unpack('V').first
        end
      end
      if x.AddressOfNameOrdinals.to_i !=0 && (ofs = va2file(x.AddressOfNameOrdinals))
        f.seek ofs
        x.name_ordinals = []
        x.NumberOfNames.times do
          if f.eof?
            logger.warn "[?] got EOF while reading exports name_ordinals"
            break
          end
          x.name_ordinals << f.read(2).unpack('v').first + x.Base
        end
      end
    end
    if x.NumberOfNames.to_i > 0 && x.AddressOfNames.to_i !=0 && (ofs = va2file(x.AddressOfNames))
      f.seek ofs
      x.names = []
      x.NumberOfNames.times do
        if f.eof?
          logger.warn "[?] got EOF while reading exports names"
          break
        end
        x.names << f.read(4).unpack('V').first
      end
      nErrors = 0
      x.names.size.times do |i|
        begin
          f.seek va2file(x.names[i])
          x.names[i] = f.gets("\x00").to_s.chomp("\x00")
        rescue
          nErrors += 1
          if nErrors > 100
            logger.warn "[?] too many errors getting export names, stopped on #{i} of #{x.names.size}"
            x.names = x.names[0,i]
            break
          end
          nil
        end
      end
    end

    ord2name = {}
    if x.names && x.names.any?
      n = x.NumberOfNames
      if n > 2048
        logger.warn "[?] NumberOfNames too big (#{x.NumberOfNames}), limiting to 2048"
        n = 2048
      end
      n.times do |i|
        ord2name[x.name_ordinals[i]] ||= []
        ord2name[x.name_ordinals[i]] << x.names[i]
      end
    end

    x.functions = []
    x.entry_points.each_with_index do |ep,i|
      names = ord2name[i+x.Base].try(:join,', ')
      next if ep.to_i == 0 && names.nil?
      x.functions << ExportedFunction.new(names, i+x.Base, ep)
    end
  end
end

#pe_imports(f = @io) ⇒ Object



487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
# File 'lib/pedump.rb', line 487

def pe_imports f=@io
  return @imports if @imports
  return nil unless pe(f) && pe(f).ioh && f
  dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::IMPORT]
  return [] if !dir || (dir.va == 0 && dir.size == 0)
  file_offset = va2file(dir.va)
  return nil unless file_offset

  # scan TLS first, to catch many fake imports trick from
  # http://code.google.com/p/corkami/source/browse/trunk/asm/PE/manyimportsW7.asm
  tls_aoi = nil
  if (tls = tls(f)) && tls.any?
    tls_aoi = tls.first.AddressOfIndex.to_i - @pe.ioh.ImageBase.to_i
    tls_aoi = tls_aoi > 0 ? va2file(tls_aoi) : nil
  end

  f.seek file_offset
  r = []
  while true
    if tls_aoi && tls_aoi == file_offset+16
      # catched the neat trick! :)
      # f.tell + 12  =  offset of 'FirstThunk' field from start of IMAGE_IMPORT_DESCRIPTOR structure
      logger.warn "[!] catched the 'imports terminator in TLS trick'"
      # http://code.google.com/p/corkami/source/browse/trunk/asm/PE/manyimportsW7.asm
      break
    end
    t=IMAGE_IMPORT_DESCRIPTOR.read(f)
    break if t.Name.to_i == 0 # also catches EOF
    r << t
    file_offset += IMAGE_IMPORT_DESCRIPTOR::SIZE
  end

  logger.warn "[?] non-empty last IMAGE_IMPORT_DESCRIPTOR: #{t.inspect}" unless t.empty?
  @imports = r.each do |x|
    if x.Name.to_i != 0 && (ofs = va2file(x.Name))
      f.seek ofs
      x.module_name = f.gets("\x00").to_s.chomp("\x00")
    end
    [:original_first_thunk, :first_thunk].each do |tbl|
      camel = tbl.capitalize.to_s.gsub(/_./){ |char| char[1..-1].upcase}
      if x[camel].to_i != 0 && (ofs = va2file(x[camel]))
        f.seek ofs
        x[tbl] ||= []
        if pe.x64?
          x[tbl] << t while (t = f.read(8).unpack('Q').first) != 0
        else
          x[tbl] << t while (t = f.read(4).unpack('V').first) != 0
        end
      end
      cache = {}
      bits = pe.x64? ? 64 : 32
      idx = -1
      x[tbl] && x[tbl].map! do |t|
        idx += 1
        va = x[camel].to_i + idx*4
        cache[t] ||=
          if t & (2**(bits-1)) > 0                               # 0x8000_0000(_0000_0000)
            ImportedFunction.new(nil,nil,t & (2**(bits-1)-1),va) # 0x7fff_ffff(_ffff_ffff)
          elsif ofs=va2file(t, :quiet => true)
            f.seek ofs
            if f.eof?
              logger.warn "[?] import ofs 0x#{ofs.to_s(16)} beyond EOF"
              nil
            else
              ImportedFunction.new(
                f.read(2).unpack('v').first,
                f.gets("\x00").chomp("\x00"),
                nil,
                va
              )
            end
          elsif tbl == :original_first_thunk
            # OriginalFirstThunk entries can not be invalid, show a warning msg
            logger.warn "[?] invalid VA 0x#{t.to_s(16)} in #{camel}[#{idx}] for #{x.module_name}"
            nil
          elsif tbl == :first_thunk
            # FirstThunk entries can be invalid, so `info` msg only
            logger.info "[?] invalid VA 0x#{t.to_s(16)} in #{camel}[#{idx}] for #{x.module_name}"
            nil
          else
            raise "You are not supposed to be here! O_o"
          end
      end
      x[tbl] && x[tbl].compact!
    end
    if x.original_first_thunk && !x.first_thunk
      logger.warn "[?] import table: empty FirstThunk for #{x.module_name}"
    elsif !x.original_first_thunk && x.first_thunk
      logger.info "[?] import table: empty OriginalFirstThunk for #{x.module_name}"
    elsif logger.debug?
      # compare all but VAs
      if x.original_first_thunk != x.first_thunk
        logger.debug "[?] import table: OriginalFirstThunk != FirstThunk for #{x.module_name}"
      end
    end
  end
end

#resource_directory(f = @io) ⇒ Object



3
4
5
6
7
8
9
10
# File 'lib/pedump/resources.rb', line 3

def resource_directory f=@io
  @resource_directory ||=
    if pe(f)
      _read_resource_directory_tree(f)
    elsif ne(f)
      ne(f).resource_directory(f)
    end
end

#resources(f = @io) ⇒ Object

resources



746
747
748
749
750
751
752
753
# File 'lib/pedump.rb', line 746

def resources f=@io
  @resources ||=
    if pe(f)
      _scan_pe_resources(f)
    elsif ne(f)
      ne(f).resources(f)
    end
end

#rich_hdr(f = @io) ⇒ Object Also known as: rich_header, rich



362
363
364
# File 'lib/pedump.rb', line 362

def rich_hdr f=@io
  dos_stub(f) && @rich_hdr
end

#sections(f = @io) ⇒ Object Also known as: section_table



426
427
428
429
430
431
432
# File 'lib/pedump.rb', line 426

def sections f=@io
  if pe(f)
    pe.section_table
  elsif ne(f)
    ne.segments
  end
end

#security(f = @io) ⇒ Object Also known as: signature



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/pedump/security.rb', line 2

def security f=@io
  return nil unless pe(f) && pe(f).ioh && f
  dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::SECURITY]
  return nil if !dir || dir.va == 0

  # IMAGE_DIRECTORY_ENTRY_SECURITY
  # Points to a list of WIN_CERTIFICATE structures, defined in WinTrust.H.
  # Not mapped into memory as part of the image.
  # Therefore, the VirtualAddress field is a file offset, rather than an RVA.
  #
  # http://msdn.microsoft.com/en-us/magazine/bb985997.aspx

  f.seek dir.va
  r = []
  ofs = f.tell
  while !f.eof? && (f.tell-ofs < dir.size)
    r << WIN_CERTIFICATE.read(f)
  end
  r
end

#strings(f = @io) ⇒ Object



188
189
190
191
192
193
194
195
196
# File 'lib/pedump/resources.rb', line 188

def strings f=@io
  r = []
  Array(resources(f)).find_all{ |x| x.type == 'STRING'}.each do |res|
    res.data.each_with_index do |string,idx|
      r << STRING.new( ((res.id-1)<<4) + idx, res.lang, string ) unless string.empty?
    end
  end
  r
end

#tls(f = @io) ⇒ Object

TLS



719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
# File 'lib/pedump.rb', line 719

def tls f=@io
  @tls ||= pe(f) && pe(f).ioh && f &&
    begin
      dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::TLS]
      return nil if !dir || dir.va == 0
      return nil unless file_offset = va2file(dir.va)
      f.seek file_offset
      if f.eof?
        logger.info "[?] TLS info beyond EOF"
        return nil
      end

      klass = @pe.x64? ? IMAGE_TLS_DIRECTORY64 : IMAGE_TLS_DIRECTORY32
      nEntries = [1,dir.size / klass.const_get('SIZE')].max
      r = []
      nEntries.times do
        break if f.eof? || !(entry = klass.read(f))
        r << entry
      end
      r
    end
end

#va2file(va, h = {}) ⇒ Object



368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
# File 'lib/pedump.rb', line 368

def va2file va, h={}
  return nil if va.nil?

  sections.each do |s|
    if (s.VirtualAddress...(s.VirtualAddress+s.VirtualSize)).include?(va)
      return va - s.VirtualAddress + s.PointerToRawData
    end
  end

  # not found with regular search. assume any of VirtualSize was 0, and try with RawSize
  sections.each do |s|
    if (s.VirtualAddress...(s.VirtualAddress+s.SizeOfRawData)).include?(va)
      return va - s.VirtualAddress + s.PointerToRawData
    end
  end

  # still not found, bad/zero VirtualSizes & RawSizes ?

  # a special case - PE without sections
  return va if sections.empty?

  # check if only one section
  if sections.size == 1 || sections.all?{ |s| s.VirtualAddress.to_i == 0 }
    s = sections.first
    return va - s.VirtualAddress + s.PointerToRawData
  end

  # TODO: not all VirtualAdresses == 0 case

  logger.error "[?] can't find file_offset of VA 0x#{va.to_i.to_s(16)}" unless h[:quiet]
  nil
end

#version_info(f = @io) ⇒ Object



755
756
757
# File 'lib/pedump.rb', line 755

def version_info f=@io
  resources(f) && resources(f).find_all{ |res| res.type == 'VERSION' }.map(&:data).flatten
end