pedump 0.4.3 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.3
1
+ 0.4.4
@@ -1,43 +1,16 @@
1
1
  #!/usr/bin/env ruby
2
- require 'logger'
3
2
  require 'stringio'
4
- require 'pedump/composite_io'
3
+ require 'pedump/core'
5
4
  require 'pedump/pe'
6
- require 'pedump/version'
5
+ require 'pedump/resources'
6
+ require 'pedump/version_info'
7
+ require 'pedump/tls'
7
8
 
8
9
  # pedump.rb by zed_0xff
9
10
  #
10
11
  # http://zed.0xff.me
11
12
  # http://github.com/zed-0xff
12
13
 
13
- class String
14
- def xor x
15
- if x.is_a?(String)
16
- r = ''
17
- j = 0
18
- 0.upto(self.size-1) do |i|
19
- r << (self[i].ord^x[j].ord).chr
20
- j+=1
21
- j=0 if j>= x.size
22
- end
23
- r
24
- else
25
- r = ''
26
- 0.upto(self.size-1) do |i|
27
- r << (self[i].ord^x).chr
28
- end
29
- r
30
- end
31
- end
32
- end
33
-
34
- class File
35
- def checked_seek newpos
36
- @file_range ||= (0..size)
37
- @file_range.include?(newpos) && (seek(newpos) || true)
38
- end
39
- end
40
-
41
14
  class PEdump
42
15
  attr_accessor :fname, :logger, :force
43
16
 
@@ -51,66 +24,6 @@ class PEdump
51
24
  @logger = @@logger = params[:logger] || PEdump::Logger.new(STDERR)
52
25
  end
53
26
 
54
- class Logger < ::Logger
55
- def initialize *args
56
- super
57
- @formatter = proc do |severity,_,_,msg|
58
- # quick and dirty way to remove duplicate messages
59
- if @prevmsg == msg && severity != 'DEBUG' && severity != 'INFO'
60
- ''
61
- else
62
- @prevmsg = msg
63
- "#{msg}\n"
64
- end
65
- end
66
- @level = Logger::WARN
67
- end
68
- end
69
-
70
- module Readable
71
- def read file, size = nil
72
- size ||= const_get 'SIZE'
73
- data = file.read(size).to_s
74
- if data.size < size && PEdump.logger
75
- PEdump.logger.error "[!] #{self.to_s} want #{size} bytes, got #{data.size}"
76
- end
77
- new(*data.unpack(const_get('FORMAT')))
78
- end
79
- end
80
-
81
- class << self
82
- def logger; @@logger; end
83
- def logger= l; @@logger=l; end
84
-
85
- def create_struct fmt, *args
86
- size = fmt.scan(/([a-z])(\d*)/i).map do |f,len|
87
- [len.to_i, 1].max *
88
- case f
89
- when /[aAC]/ then 1
90
- when 'v' then 2
91
- when 'V' then 4
92
- when 'Q' then 8
93
- else raise "unknown fmt #{f.inspect}"
94
- end
95
- end.inject(&:+)
96
-
97
- Struct.new( *args ).tap do |x|
98
- x.const_set 'FORMAT', fmt
99
- x.const_set 'SIZE', size
100
- x.class_eval do
101
- def pack
102
- to_a.pack self.class.const_get('FORMAT')
103
- end
104
- def empty?
105
- to_a.all?{ |t| t == 0 || t.nil? || t.to_s.tr("\x00","").empty? }
106
- end
107
- end
108
- x.extend Readable
109
- end
110
- end
111
- end
112
-
113
-
114
27
  # http://www.delorie.com/djgpp/doc/exe/
115
28
  MZ = create_struct( "a2v13Qv2V6",
116
29
  :signature,
@@ -448,8 +361,37 @@ class PEdump
448
361
  end
449
362
  end
450
363
 
451
- def resource_directory f=nil
452
- @resource_directory ||= _read_resource_directory_tree(f)
364
+ def va2file va
365
+ return nil if va.nil?
366
+
367
+ sections.each do |s|
368
+ if (s.VirtualAddress...(s.VirtualAddress+s.VirtualSize)).include?(va)
369
+ return va - s.VirtualAddress + s.PointerToRawData
370
+ end
371
+ end
372
+
373
+ # not found with regular search. assume any of VirtualSize was 0, and try with RawSize
374
+ sections.each do |s|
375
+ if (s.VirtualAddress...(s.VirtualAddress+s.SizeOfRawData)).include?(va)
376
+ return va - s.VirtualAddress + s.PointerToRawData
377
+ end
378
+ end
379
+
380
+ # still not found, bad/zero VirtualSizes & RawSizes ?
381
+
382
+ # a special case - PE without sections
383
+ return va if sections.empty?
384
+
385
+ # check if only one section
386
+ if sections.size == 1 || sections.all?{ |s| s.VirtualAddress.to_i == 0 }
387
+ s = sections.first
388
+ return va - s.VirtualAddress + s.PointerToRawData
389
+ end
390
+
391
+ # TODO: not all VirtualAdresses == 0 case
392
+
393
+ logger.error "[?] can't find file_offset of VA 0x#{va.to_i.to_s(16)}"
394
+ nil
453
395
  end
454
396
 
455
397
  # OPTIONAL: assigns @mz, @rich_hdr, @pe, etc
@@ -499,14 +441,34 @@ class PEdump
499
441
  return nil unless pe(f) && pe(f).ioh && f
500
442
  dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::IMPORT]
501
443
  return [] if !dir || (dir.va == 0 && dir.size == 0)
502
- va = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::IMPORT].va
503
- file_offset = va2file(va)
444
+ file_offset = va2file(dir.va)
504
445
  return nil unless file_offset
446
+
447
+ # scan TLS first, to catch many fake imports trick from
448
+ # http://code.google.com/p/corkami/source/browse/trunk/asm/PE/manyimportsW7.asm
449
+ tls_aoi = nil
450
+ if (tls = tls(f)) && tls.any?
451
+ tls_aoi = tls.first.AddressOfIndex.to_i - @pe.ioh.ImageBase.to_i
452
+ tls_aoi = tls_aoi > 0 ? va2file(tls_aoi) : nil
453
+ end
454
+
505
455
  f.seek file_offset
506
456
  r = []
507
- until (t=IMAGE_IMPORT_DESCRIPTOR.read(f)).Name.to_i == 0
457
+ while true
458
+ if tls_aoi && tls_aoi == file_offset+16
459
+ # catched the neat trick! :)
460
+ # f.tell + 12 = offset of 'FirstThunk' field from start of IMAGE_IMPORT_DESCRIPTOR structure
461
+ logger.warn "[!] catched the 'imports terminator in TLS trick'"
462
+ # http://code.google.com/p/corkami/source/browse/trunk/asm/PE/manyimportsW7.asm
463
+ break
464
+ end
465
+ t=IMAGE_IMPORT_DESCRIPTOR.read(f)
466
+ break if t.Name.to_i == 0 # also catches EOF
508
467
  r << t
468
+ file_offset += IMAGE_IMPORT_DESCRIPTOR::SIZE
469
+ break if r.size == 3
509
470
  end
471
+
510
472
  logger.warn "[?] non-empty last IMAGE_IMPORT_DESCRIPTOR: #{t.inspect}" unless t.empty?
511
473
  @imports = r.each do |x|
512
474
  if x.Name.to_i != 0 && (va = va2file(x.Name))
@@ -643,356 +605,40 @@ class PEdump
643
605
  end
644
606
 
645
607
  ##############################################################################
646
- # resources
608
+ # TLS
647
609
  ##############################################################################
648
610
 
649
- IMAGE_RESOURCE_DIRECTORY = create_struct 'V2v4',
650
- :Characteristics, :TimeDateStamp, # 2dw
651
- :MajorVersion, :MinorVersion, :NumberOfNamedEntries, :NumberOfIdEntries, # 4w
652
- :entries # manual
653
- class IMAGE_RESOURCE_DIRECTORY
654
- class << self
655
- attr_accessor :base
656
- alias :read_without_children :read
657
- def read f, root=true
658
- if root
659
- @@loopchk1 = Hash.new(0)
660
- @@loopchk2 = Hash.new(0)
661
- @@loopchk3 = Hash.new(0)
662
- elsif (@@loopchk1[f.tell] += 1) > 1
663
- PEdump.logger.error "[!] #{self}: loop1 detected at file pos #{f.tell}" if @@loopchk1[f.tell] < 2
611
+ def tls f=nil
612
+ @tls ||= pe(f) && pe(f).ioh && f &&
613
+ begin
614
+ dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::TLS]
615
+ return nil if !dir || dir.va == 0
616
+ return nil unless file_offset = va2file(dir.va)
617
+ f.seek file_offset
618
+ if f.eof?
619
+ logger.info "[?] TLS info beyond EOF"
664
620
  return nil
665
621
  end
666
- read_without_children(f).tap do |r|
667
- nToRead = r.NumberOfNamedEntries.to_i + r.NumberOfIdEntries.to_i
668
- r.entries = []
669
- nToRead.times do |i|
670
- if f.eof?
671
- PEdump.logger.error "[!] #{self}: #{nToRead} entries in directory, but got EOF on #{i}-th."
672
- break
673
- end
674
- if (@@loopchk2[f.tell] += 1) > 1
675
- PEdump.logger.error "[!] #{self}: loop2 detected at file pos #{f.tell}" if @@loopchk2[f.tell] < 2
676
- next
677
- end
678
- r.entries << IMAGE_RESOURCE_DIRECTORY_ENTRY.read(f)
679
- end
680
- #r.entries.uniq!
681
- r.entries.each do |entry|
682
- entry.name =
683
- if entry.Name.to_i & 0x8000_0000 > 0
684
- # Name is an address of unicode string
685
- f.seek base + entry.Name & 0x7fff_ffff
686
- nChars = f.read(2).to_s.unpack("v").first.to_i
687
- begin
688
- f.read(nChars*2).force_encoding('UTF-16LE').encode!('UTF-8')
689
- rescue
690
- PEdump.logger.error "[!] #{self} failed to read entry name: #{$!}"
691
- "???"
692
- end
693
- else
694
- # Name is a numeric id
695
- "##{entry.Name}"
696
- end
697
- if entry.OffsetToData && f.checked_seek(base + entry.OffsetToData & 0x7fff_ffff)
698
- if (@@loopchk3[f.tell] += 1) > 1
699
- PEdump.logger.error "[!] #{self}: loop3 detected at file pos #{f.tell}" if @@loopchk3[f.tell] < 2
700
- next
701
- end
702
- entry.data =
703
- if entry.OffsetToData & 0x8000_0000 > 0
704
- # child is a directory
705
- IMAGE_RESOURCE_DIRECTORY.read(f,false)
706
- else
707
- # child is a resource
708
- IMAGE_RESOURCE_DATA_ENTRY.read(f)
709
- end
710
- end
711
- end
712
- @@loopchk1 = @@loopchk2 = @@loopchk3 = nil if root # save some memory
713
- end
714
- end
715
- end
716
- end
717
-
718
- IMAGE_RESOURCE_DIRECTORY_ENTRY = create_struct 'V2',
719
- :Name, :OffsetToData,
720
- :name, :data
721
622
 
722
- IMAGE_RESOURCE_DATA_ENTRY = create_struct 'V4',
723
- :OffsetToData, :Size, :CodePage, :Reserved
623
+ start_offset = f.tell
724
624
 
725
- def va2file va
726
- return nil if va.nil?
727
-
728
- sections.each do |s|
729
- if (s.VirtualAddress...(s.VirtualAddress+s.VirtualSize)).include?(va)
730
- return va - s.VirtualAddress + s.PointerToRawData
731
- end
732
- end
733
-
734
- # not found with regular search. assume any of VirtualSize was 0, and try with RawSize
735
- sections.each do |s|
736
- if (s.VirtualAddress...(s.VirtualAddress+s.SizeOfRawData)).include?(va)
737
- return va - s.VirtualAddress + s.PointerToRawData
738
- end
739
- end
740
-
741
- # still not found, bad/zero VirtualSizes & RawSizes ?
742
-
743
- # a special case - PE without sections
744
- return va if sections.empty?
745
-
746
- # check if only one section
747
- if sections.size == 1 || sections.all?{ |s| s.VirtualAddress.to_i == 0 }
748
- s = sections.first
749
- return va - s.VirtualAddress + s.PointerToRawData
750
- end
751
-
752
- # TODO: not all VirtualAdresses == 0 case
753
-
754
- logger.error "[?] can't find file_offset of VA 0x#{va.to_i.to_s(16)}"
755
- nil
756
- end
757
-
758
- def _read_resource_directory_tree f
759
- return nil unless pe(f) && pe(f).ioh && f
760
- res_dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::RESOURCE]
761
- return [] if !res_dir || (res_dir.va == 0 && res_dir.size == 0)
762
- res_va = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::RESOURCE].va
763
- res_section = @pe.section_table.find{ |t| t.VirtualAddress == res_va }
764
- unless res_section
765
- logger.warn "[?] can't find resource section for va=0x#{res_va.to_s(16)}"
766
- return []
767
- end
768
- f.seek res_section.PointerToRawData
769
- IMAGE_RESOURCE_DIRECTORY.base = res_section.PointerToRawData
770
- #@resource_data_base = res_section.PointerToRawData - res_section.VirtualAddress
771
- IMAGE_RESOURCE_DIRECTORY.read(f)
772
- end
773
-
774
- class Resource < Struct.new(:type, :name, :id, :lang, :file_offset, :size, :cp, :reserved, :data, :valid)
775
- def bitmap_hdr
776
- bmp_info_hdr = data.find{ |x| x.is_a?(BITMAPINFOHEADER) }
777
- raise "no BITMAPINFOHEADER for #{self.type} #{self.name}" unless bmp_info_hdr
778
-
779
- bmp_info_hdr.biHeight/=2 if %w'ICON CURSOR'.include?(type)
780
-
781
- colors_used = bmp_info_hdr.biClrUsed
782
- colors_used = 2**bmp_info_hdr.biBitCount if colors_used == 0 && bmp_info_hdr.biBitCount < 16
783
-
784
- # XXX: one byte in each color is unused!
785
- @palette_size = colors_used * 4 # each color takes 4 bytes
786
-
787
- # scanlines are DWORD-aligned and padded to DWORD-align with zeroes
788
- # XXX: some data may be hidden in padding bytes!
789
- scanline_size = bmp_info_hdr.biWidth * bmp_info_hdr.biBitCount / 8
790
- scanline_size += (4-scanline_size%4) if scanline_size % 4 > 0
791
-
792
- @imgdata_size = scanline_size * bmp_info_hdr.biHeight
793
- "BM" + [
794
- BITMAPINFOHEADER::SIZE + 14 + @palette_size + @imgdata_size,
795
- 0,
796
- BITMAPINFOHEADER::SIZE + 14 + @palette_size
797
- ].pack("V3") + bmp_info_hdr.pack
798
- ensure
799
- bmp_info_hdr.biHeight*=2 if %w'ICON CURSOR'.include?(type)
800
- end
801
-
802
- # only valid for types BITMAP, ICON & CURSOR
803
- def restore_bitmap src_fname
804
- File.open(src_fname, "rb") do |f|
805
- parse f
806
- if data.first == "PNG"
807
- "\x89PNG" +f.read(self.size-4)
808
- else
809
- bitmap_hdr + f.read(@palette_size + @imgdata_size)
810
- end
811
- end
812
- end
813
-
814
- def bitmap_mask src_fname
815
- File.open(src_fname, "rb") do |f|
816
- parse f
817
- bmp_info_hdr = bitmap_hdr
818
- bitmap_size = BITMAPINFOHEADER::SIZE + @palette_size + @imgdata_size
819
- return nil if bitmap_size >= self.size
820
-
821
- mask_size = self.size - bitmap_size
822
- f.seek file_offset + bitmap_size
823
-
824
- bmp_info_hdr = BITMAPINFOHEADER.new(*bmp_info_hdr[14..-1].unpack(BITMAPINFOHEADER::FORMAT))
825
- bmp_info_hdr.biBitCount = 1
826
- bmp_info_hdr.biCompression = bmp_info_hdr.biSizeImage = 0
827
- bmp_info_hdr.biClrUsed = bmp_info_hdr.biClrImportant = 2
828
-
829
- palette = [0,0xffffff].pack('V2')
830
- @palette_size = palette.size
831
-
832
- "BM" + [
833
- BITMAPINFOHEADER::SIZE + 14 + @palette_size + mask_size,
834
- 0,
835
- BITMAPINFOHEADER::SIZE + 14 + @palette_size
836
- ].pack("V3") + bmp_info_hdr.pack + palette + f.read(mask_size)
837
- end
838
- end
839
-
840
- # also sets the file position for restore_bitmap next call
841
- def parse f
842
- raise "called parse with type not set" unless self.type
843
- #return if self.data
844
-
845
- self.data = []
846
- case type
847
- when 'BITMAP','ICON'
848
- f.seek file_offset
849
- if f.read(4) == "\x89PNG"
850
- data << 'PNG'
851
- else
852
- f.seek file_offset
853
- data << BITMAPINFOHEADER.read(f)
854
- end
855
- when 'CURSOR'
856
- f.seek file_offset
857
- data << CURSOR_HOTSPOT.read(f)
858
- data << BITMAPINFOHEADER.read(f)
859
- when 'GROUP_CURSOR'
860
- f.seek file_offset
861
- data << CUR_ICO_HEADER.read(f)
862
- nRead = CUR_ICO_HEADER::SIZE
863
- data.last.wNumImages.to_i.times do
864
- if nRead >= self.size
865
- PEdump.logger.error "[!] refusing to read CURDIRENTRY beyond resource size"
866
- break
867
- end
868
- data << CURDIRENTRY.read(f)
869
- nRead += CURDIRENTRY::SIZE
870
- end
871
- when 'GROUP_ICON'
872
- f.seek file_offset
873
- data << CUR_ICO_HEADER.read(f)
874
- nRead = CUR_ICO_HEADER::SIZE
875
- data.last.wNumImages.to_i.times do
876
- if nRead >= self.size
877
- PEdump.logger.error "[!] refusing to read ICODIRENTRY beyond resource size"
625
+ klass = @pe.x64? ? IMAGE_TLS_DIRECTORY64 : IMAGE_TLS_DIRECTORY32
626
+ r = []
627
+ while !f.eof? && (entry = klass.read(f))
628
+ if entry.AddressOfCallBacks.to_i == 0
629
+ logger.warn "[?] non-empty TLS terminator: #{entry.inspect}" unless entry.empty?
878
630
  break
879
631
  end
880
- data << ICODIRENTRY.read(f)
881
- nRead += ICODIRENTRY::SIZE
882
- end
883
- when 'STRING'
884
- f.seek file_offset
885
- 16.times do
886
- break if f.tell >= file_offset+self.size
887
- nChars = f.read(2).to_s.unpack('v').first.to_i
888
- t =
889
- if nChars*2 + 1 > self.size
890
- # TODO: if it's not 1st string in table then truncated size must be less
891
- PEdump.logger.error "[!] string size(#{nChars*2}) > stringtable size(#{self.size}). truncated to #{self.size-2}"
892
- f.read(self.size-2)
893
- else
894
- f.read(nChars*2)
895
- end
896
- data <<
897
- begin
898
- t.force_encoding('UTF-16LE').encode!('UTF-8')
899
- rescue
900
- t.force_encoding('ASCII')
901
- tt = t.size > 0x10 ? t[0,0x10].inspect+'...' : t.inspect
902
- PEdump.logger.error "[!] cannot convert #{tt} to UTF-16"
903
- [nChars,t].pack('va*')
904
- end
632
+ r << entry
633
+ break if dir.size > 0 && f.tell >= (start_offset+dir.size)
905
634
  end
906
- # XXX: check if readed strings summary length is less than resource data length
907
- when 'VERSION'
908
- require 'pedump/version_info'
909
- f.seek file_offset
910
- data << PEdump::VS_VERSIONINFO.read(f)
911
- end
912
-
913
- data.delete_if do |x|
914
- valid = !x.respond_to?(:valid?) || x.valid?
915
- PEdump.logger.warn "[?] ignoring invalid #{x.class}" unless valid
916
- !valid
635
+ r
917
636
  end
918
- ensure
919
- validate
920
- end
921
-
922
- def validate
923
- self.valid =
924
- case type
925
- when 'BITMAP','ICON','CURSOR'
926
- data.any?{ |x| x.is_a?(BITMAPINFOHEADER) && x.valid? } || data.first == 'PNG'
927
- else
928
- true
929
- end
930
- end
931
637
  end
932
638
 
933
- STRING = Struct.new(:id, :lang, :value)
934
-
935
- def strings f=nil
936
- r = []
937
- Array(resources(f)).find_all{ |x| x.type == 'STRING'}.each do |res|
938
- res.data.each_with_index do |string,idx|
939
- r << STRING.new( ((res.id-1)<<4) + idx, res.lang, string ) unless string.empty?
940
- end
941
- end
942
- r
943
- end
944
-
945
- # see also http://www.informit.com/articles/article.aspx?p=1186882 about icons format
946
-
947
- class BITMAPINFOHEADER < create_struct 'V3v2V6',
948
- :biSize, # BITMAPINFOHEADER::SIZE
949
- :biWidth,
950
- :biHeight,
951
- :biPlanes,
952
- :biBitCount,
953
- :biCompression,
954
- :biSizeImage,
955
- :biXPelsPerMeter,
956
- :biYPelsPerMeter,
957
- :biClrUsed,
958
- :biClrImportant
959
-
960
- def valid?
961
- self.biSize == 40
962
- end
963
- end
964
-
965
- # http://www.devsource.com/c/a/Architecture/Resources-From-PE-I/2/
966
- CUR_ICO_HEADER = create_struct('v3',
967
- :wReserved, # always 0
968
- :wResID, # always 2
969
- :wNumImages # Number of cursor images/directory entries
970
- )
971
-
972
- CURDIRENTRY = create_struct 'v4Vv',
973
- :wWidth,
974
- :wHeight, # Divide by 2 to get the actual height.
975
- :wPlanes,
976
- :wBitCount,
977
- :dwBytesInImage,
978
- :wID
979
-
980
- CURSOR_HOTSPOT = create_struct 'v2', :x, :y
981
-
982
- ICODIRENTRY = create_struct 'C4v2Vv',
983
- :bWidth,
984
- :bHeight,
985
- :bColors,
986
- :bReserved,
987
- :wPlanes,
988
- :wBitCount,
989
- :dwBytesInImage,
990
- :wID
991
-
992
- ROOT_RES_NAMES = [nil] + # numeration is started from 1
993
- %w'CURSOR BITMAP ICON MENU DIALOG STRING FONTDIR FONT ACCELERATORS RCDATA' +
994
- %w'MESSAGETABLE GROUP_CURSOR' + [nil] + %w'GROUP_ICON' + [nil] +
995
- %w'VERSION DLGINCLUDE' + [nil] + %w'PLUGPLAY VXD ANICURSOR ANIICON HTML MANIFEST'
639
+ ##############################################################################
640
+ # resources
641
+ ##############################################################################
996
642
 
997
643
  def resources f=nil
998
644
  @resources ||= _scan_resources(f)
@@ -1002,48 +648,9 @@ class PEdump
1002
648
  resources(f) && resources(f).find_all{ |res| res.type == 'VERSION' }.map(&:data).flatten
1003
649
  end
1004
650
 
1005
- def _scan_resources f=nil, dir=nil
1006
- dir ||= resource_directory(f)
1007
- return nil unless dir
1008
- dir.entries.map do |entry|
1009
- case entry.data
1010
- when IMAGE_RESOURCE_DIRECTORY
1011
- if dir == @resource_directory # root resource directory
1012
- entry_type =
1013
- if entry.Name & 0x8000_0000 == 0
1014
- # root resource directory & entry name is a number
1015
- ROOT_RES_NAMES[entry.Name] || entry.name
1016
- else
1017
- entry.name
1018
- end
1019
- _scan_resources(f,entry.data).each do |res|
1020
- res.type = entry_type
1021
- res.parse f
1022
- end
1023
- else
1024
- _scan_resources(f,entry.data).each do |res|
1025
- res.name = res.name == "##{res.lang}" ? entry.name : "#{entry.name} / #{res.name}"
1026
- res.id ||= entry.Name if entry.Name.is_a?(Numeric) && entry.Name < 0x8000_0000
1027
- end
1028
- end
1029
- when IMAGE_RESOURCE_DATA_ENTRY
1030
- Resource.new(
1031
- nil, # type
1032
- entry.name,
1033
- nil, # id
1034
- entry.Name, # lang
1035
- #entry.data.OffsetToData + @resource_data_base,
1036
- va2file(entry.data.OffsetToData),
1037
- entry.data.Size,
1038
- entry.data.CodePage,
1039
- entry.data.Reserved
1040
- )
1041
- else
1042
- logger.error "[!] invalid resource entry: #{entry.data.inspect}"
1043
- nil
1044
- end
1045
- end.flatten.compact
1046
- end
651
+ ##############################################################################
652
+ # packer / compiler detection
653
+ ##############################################################################
1047
654
 
1048
655
  def packer f = nil
1049
656
  @packer ||= pe(f) && @pe.ioh &&
@@ -1,5 +1,6 @@
1
1
  require 'pedump'
2
2
  require 'pedump/packer'
3
+ require 'pedump/version_info'
3
4
  require 'optparse'
4
5
 
5
6
  unless Object.instance_methods.include?(:try)
@@ -14,7 +15,7 @@ class PEdump::CLI
14
15
  attr_accessor :data, :argv
15
16
 
16
17
  KNOWN_ACTIONS = (
17
- %w'mz dos_stub rich pe data_directory sections' +
18
+ %w'mz dos_stub rich pe data_directory sections tls' +
18
19
  %w'strings resources resource_directory imports exports version_info packer web packer_only'
19
20
  ).map(&:to_sym)
20
21
 
@@ -400,6 +401,8 @@ class PEdump::CLI
400
401
  dump_packers data
401
402
  when PEdump::VS_VERSIONINFO
402
403
  dump_version_info data
404
+ when PEdump::IMAGE_TLS_DIRECTORY32, PEdump::IMAGE_TLS_DIRECTORY64
405
+ dump_tls data
403
406
  else
404
407
  puts "[?] don't know how to dump: #{data.inspect[0,50]}" unless data.empty?
405
408
  end
@@ -412,6 +415,20 @@ class PEdump::CLI
412
415
  end
413
416
  end
414
417
 
418
+ def dump_tls data
419
+ fmt = "%10x %10x %8x %8x %8x %8x\n"
420
+ printf fmt.tr('x','s'), *%w'RAW_START RAW_END INDEX CALLBKS ZEROFILL FLAGS'
421
+ data.each do |tls|
422
+ printf fmt,
423
+ tls.StartAddressOfRawData,
424
+ tls.EndAddressOfRawData,
425
+ tls.AddressOfIndex,
426
+ tls.AddressOfCallBacks,
427
+ tls.SizeOfZeroFill,
428
+ tls.Characteristics
429
+ end
430
+ end
431
+
415
432
  def dump_version_info data
416
433
  if @options[:format] != :table
417
434
  File.open(@file_name,'rb') do |f|
@@ -0,0 +1,91 @@
1
+ require 'logger'
2
+ require 'pedump/version'
3
+
4
+ class String
5
+ def xor x
6
+ if x.is_a?(String)
7
+ r = ''
8
+ j = 0
9
+ 0.upto(self.size-1) do |i|
10
+ r << (self[i].ord^x[j].ord).chr
11
+ j+=1
12
+ j=0 if j>= x.size
13
+ end
14
+ r
15
+ else
16
+ r = ''
17
+ 0.upto(self.size-1) do |i|
18
+ r << (self[i].ord^x).chr
19
+ end
20
+ r
21
+ end
22
+ end
23
+ end
24
+
25
+ class File
26
+ def checked_seek newpos
27
+ @file_range ||= (0..size)
28
+ @file_range.include?(newpos) && (seek(newpos) || true)
29
+ end
30
+ end
31
+
32
+ class PEdump
33
+ class Logger < ::Logger
34
+ def initialize *args
35
+ super
36
+ @formatter = proc do |severity,_,_,msg|
37
+ # quick and dirty way to remove duplicate messages
38
+ if @prevmsg == msg && severity != 'DEBUG' && severity != 'INFO'
39
+ ''
40
+ else
41
+ @prevmsg = msg
42
+ "#{msg}\n"
43
+ end
44
+ end
45
+ @level = Logger::WARN
46
+ end
47
+ end
48
+
49
+ module Readable
50
+ def read file, size = nil
51
+ size ||= const_get 'SIZE'
52
+ data = file.read(size).to_s
53
+ if data.size < size && PEdump.logger
54
+ PEdump.logger.error "[!] #{self.to_s} want #{size} bytes, got #{data.size}"
55
+ end
56
+ new(*data.unpack(const_get('FORMAT')))
57
+ end
58
+ end
59
+
60
+ class << self
61
+ def logger; @@logger; end
62
+ def logger= l; @@logger=l; end
63
+
64
+ def create_struct fmt, *args
65
+ size = fmt.scan(/([a-z])(\d*)/i).map do |f,len|
66
+ [len.to_i, 1].max *
67
+ case f
68
+ when /[aAC]/ then 1
69
+ when 'v' then 2
70
+ when 'V' then 4
71
+ when 'Q' then 8
72
+ else raise "unknown fmt #{f.inspect}"
73
+ end
74
+ end.inject(&:+)
75
+
76
+ Struct.new( *args ).tap do |x|
77
+ x.const_set 'FORMAT', fmt
78
+ x.const_set 'SIZE', size
79
+ x.class_eval do
80
+ def pack
81
+ to_a.pack self.class.const_get('FORMAT')
82
+ end
83
+ def empty?
84
+ to_a.all?{ |t| t == 0 || t.nil? || t.to_s.tr("\x00","").empty? }
85
+ end
86
+ end
87
+ x.extend Readable
88
+ end
89
+ end
90
+ end
91
+ end
@@ -1,3 +1,5 @@
1
+ require 'pedump/composite_io'
2
+
1
3
  class PEdump
2
4
  class PE < Struct.new(
3
5
  :signature, # "PE\x00\x00"
@@ -0,0 +1,364 @@
1
+ class PEdump
2
+
3
+ def resource_directory f=nil
4
+ @resource_directory ||= _read_resource_directory_tree(f)
5
+ end
6
+
7
+ def _read_resource_directory_tree f
8
+ return nil unless pe(f) && pe(f).ioh && f
9
+ res_dir = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::RESOURCE]
10
+ return [] if !res_dir || (res_dir.va == 0 && res_dir.size == 0)
11
+ res_va = @pe.ioh.DataDirectory[IMAGE_DATA_DIRECTORY::RESOURCE].va
12
+ res_section = @pe.section_table.find{ |t| t.VirtualAddress == res_va }
13
+ unless res_section
14
+ logger.warn "[?] can't find resource section for va=0x#{res_va.to_s(16)}"
15
+ return []
16
+ end
17
+ f.seek res_section.PointerToRawData
18
+ IMAGE_RESOURCE_DIRECTORY.base = res_section.PointerToRawData
19
+ #@resource_data_base = res_section.PointerToRawData - res_section.VirtualAddress
20
+ IMAGE_RESOURCE_DIRECTORY.read(f)
21
+ end
22
+
23
+ class Resource < Struct.new(:type, :name, :id, :lang, :file_offset, :size, :cp, :reserved, :data, :valid)
24
+ def bitmap_hdr
25
+ bmp_info_hdr = data.find{ |x| x.is_a?(BITMAPINFOHEADER) }
26
+ raise "no BITMAPINFOHEADER for #{self.type} #{self.name}" unless bmp_info_hdr
27
+
28
+ bmp_info_hdr.biHeight/=2 if %w'ICON CURSOR'.include?(type)
29
+
30
+ colors_used = bmp_info_hdr.biClrUsed
31
+ colors_used = 2**bmp_info_hdr.biBitCount if colors_used == 0 && bmp_info_hdr.biBitCount < 16
32
+
33
+ # XXX: one byte in each color is unused!
34
+ @palette_size = colors_used * 4 # each color takes 4 bytes
35
+
36
+ # scanlines are DWORD-aligned and padded to DWORD-align with zeroes
37
+ # XXX: some data may be hidden in padding bytes!
38
+ scanline_size = bmp_info_hdr.biWidth * bmp_info_hdr.biBitCount / 8
39
+ scanline_size += (4-scanline_size%4) if scanline_size % 4 > 0
40
+
41
+ @imgdata_size = scanline_size * bmp_info_hdr.biHeight
42
+ "BM" + [
43
+ BITMAPINFOHEADER::SIZE + 14 + @palette_size + @imgdata_size,
44
+ 0,
45
+ BITMAPINFOHEADER::SIZE + 14 + @palette_size
46
+ ].pack("V3") + bmp_info_hdr.pack
47
+ ensure
48
+ bmp_info_hdr.biHeight*=2 if %w'ICON CURSOR'.include?(type)
49
+ end
50
+
51
+ # only valid for types BITMAP, ICON & CURSOR
52
+ def restore_bitmap src_fname
53
+ File.open(src_fname, "rb") do |f|
54
+ parse f
55
+ if data.first == "PNG"
56
+ "\x89PNG" +f.read(self.size-4)
57
+ else
58
+ bitmap_hdr + f.read(@palette_size + @imgdata_size)
59
+ end
60
+ end
61
+ end
62
+
63
+ def bitmap_mask src_fname
64
+ File.open(src_fname, "rb") do |f|
65
+ parse f
66
+ bmp_info_hdr = bitmap_hdr
67
+ bitmap_size = BITMAPINFOHEADER::SIZE + @palette_size + @imgdata_size
68
+ return nil if bitmap_size >= self.size
69
+
70
+ mask_size = self.size - bitmap_size
71
+ f.seek file_offset + bitmap_size
72
+
73
+ bmp_info_hdr = BITMAPINFOHEADER.new(*bmp_info_hdr[14..-1].unpack(BITMAPINFOHEADER::FORMAT))
74
+ bmp_info_hdr.biBitCount = 1
75
+ bmp_info_hdr.biCompression = bmp_info_hdr.biSizeImage = 0
76
+ bmp_info_hdr.biClrUsed = bmp_info_hdr.biClrImportant = 2
77
+
78
+ palette = [0,0xffffff].pack('V2')
79
+ @palette_size = palette.size
80
+
81
+ "BM" + [
82
+ BITMAPINFOHEADER::SIZE + 14 + @palette_size + mask_size,
83
+ 0,
84
+ BITMAPINFOHEADER::SIZE + 14 + @palette_size
85
+ ].pack("V3") + bmp_info_hdr.pack + palette + f.read(mask_size)
86
+ end
87
+ end
88
+
89
+ # also sets the file position for restore_bitmap next call
90
+ def parse f
91
+ raise "called parse with type not set" unless self.type
92
+ #return if self.data
93
+
94
+ self.data = []
95
+ case type
96
+ when 'BITMAP','ICON'
97
+ f.seek file_offset
98
+ if f.read(4) == "\x89PNG"
99
+ data << 'PNG'
100
+ else
101
+ f.seek file_offset
102
+ data << BITMAPINFOHEADER.read(f)
103
+ end
104
+ when 'CURSOR'
105
+ f.seek file_offset
106
+ data << CURSOR_HOTSPOT.read(f)
107
+ data << BITMAPINFOHEADER.read(f)
108
+ when 'GROUP_CURSOR'
109
+ f.seek file_offset
110
+ data << CUR_ICO_HEADER.read(f)
111
+ nRead = CUR_ICO_HEADER::SIZE
112
+ data.last.wNumImages.to_i.times do
113
+ if nRead >= self.size
114
+ PEdump.logger.error "[!] refusing to read CURDIRENTRY beyond resource size"
115
+ break
116
+ end
117
+ data << CURDIRENTRY.read(f)
118
+ nRead += CURDIRENTRY::SIZE
119
+ end
120
+ when 'GROUP_ICON'
121
+ f.seek file_offset
122
+ data << CUR_ICO_HEADER.read(f)
123
+ nRead = CUR_ICO_HEADER::SIZE
124
+ data.last.wNumImages.to_i.times do
125
+ if nRead >= self.size
126
+ PEdump.logger.error "[!] refusing to read ICODIRENTRY beyond resource size"
127
+ break
128
+ end
129
+ data << ICODIRENTRY.read(f)
130
+ nRead += ICODIRENTRY::SIZE
131
+ end
132
+ when 'STRING'
133
+ f.seek file_offset
134
+ 16.times do
135
+ break if f.tell >= file_offset+self.size
136
+ nChars = f.read(2).to_s.unpack('v').first.to_i
137
+ t =
138
+ if nChars*2 + 1 > self.size
139
+ # TODO: if it's not 1st string in table then truncated size must be less
140
+ PEdump.logger.error "[!] string size(#{nChars*2}) > stringtable size(#{self.size}). truncated to #{self.size-2}"
141
+ f.read(self.size-2)
142
+ else
143
+ f.read(nChars*2)
144
+ end
145
+ data <<
146
+ begin
147
+ t.force_encoding('UTF-16LE').encode!('UTF-8')
148
+ rescue
149
+ t.force_encoding('ASCII')
150
+ tt = t.size > 0x10 ? t[0,0x10].inspect+'...' : t.inspect
151
+ PEdump.logger.error "[!] cannot convert #{tt} to UTF-16"
152
+ [nChars,t].pack('va*')
153
+ end
154
+ end
155
+ # XXX: check if readed strings summary length is less than resource data length
156
+ when 'VERSION'
157
+ require 'pedump/version_info'
158
+ f.seek file_offset
159
+ data << PEdump::VS_VERSIONINFO.read(f)
160
+ end
161
+
162
+ data.delete_if do |x|
163
+ valid = !x.respond_to?(:valid?) || x.valid?
164
+ PEdump.logger.warn "[?] ignoring invalid #{x.class}" unless valid
165
+ !valid
166
+ end
167
+ ensure
168
+ validate
169
+ end
170
+
171
+ def validate
172
+ self.valid =
173
+ case type
174
+ when 'BITMAP','ICON','CURSOR'
175
+ data.any?{ |x| x.is_a?(BITMAPINFOHEADER) && x.valid? } || data.first == 'PNG'
176
+ else
177
+ true
178
+ end
179
+ end
180
+ end
181
+
182
+ STRING = Struct.new(:id, :lang, :value)
183
+
184
+ def strings f=nil
185
+ r = []
186
+ Array(resources(f)).find_all{ |x| x.type == 'STRING'}.each do |res|
187
+ res.data.each_with_index do |string,idx|
188
+ r << STRING.new( ((res.id-1)<<4) + idx, res.lang, string ) unless string.empty?
189
+ end
190
+ end
191
+ r
192
+ end
193
+
194
+ # see also http://www.informit.com/articles/article.aspx?p=1186882 about icons format
195
+
196
+ class BITMAPINFOHEADER < create_struct 'V3v2V6',
197
+ :biSize, # BITMAPINFOHEADER::SIZE
198
+ :biWidth,
199
+ :biHeight,
200
+ :biPlanes,
201
+ :biBitCount,
202
+ :biCompression,
203
+ :biSizeImage,
204
+ :biXPelsPerMeter,
205
+ :biYPelsPerMeter,
206
+ :biClrUsed,
207
+ :biClrImportant
208
+
209
+ def valid?
210
+ self.biSize == 40
211
+ end
212
+ end
213
+
214
+ # http://www.devsource.com/c/a/Architecture/Resources-From-PE-I/2/
215
+ CUR_ICO_HEADER = create_struct('v3',
216
+ :wReserved, # always 0
217
+ :wResID, # always 2
218
+ :wNumImages # Number of cursor images/directory entries
219
+ )
220
+
221
+ CURDIRENTRY = create_struct 'v4Vv',
222
+ :wWidth,
223
+ :wHeight, # Divide by 2 to get the actual height.
224
+ :wPlanes,
225
+ :wBitCount,
226
+ :dwBytesInImage,
227
+ :wID
228
+
229
+ CURSOR_HOTSPOT = create_struct 'v2', :x, :y
230
+
231
+ ICODIRENTRY = create_struct 'C4v2Vv',
232
+ :bWidth,
233
+ :bHeight,
234
+ :bColors,
235
+ :bReserved,
236
+ :wPlanes,
237
+ :wBitCount,
238
+ :dwBytesInImage,
239
+ :wID
240
+
241
+ ROOT_RES_NAMES = [nil] + # numeration is started from 1
242
+ %w'CURSOR BITMAP ICON MENU DIALOG STRING FONTDIR FONT ACCELERATORS RCDATA' +
243
+ %w'MESSAGETABLE GROUP_CURSOR' + [nil] + %w'GROUP_ICON' + [nil] +
244
+ %w'VERSION DLGINCLUDE' + [nil] + %w'PLUGPLAY VXD ANICURSOR ANIICON HTML MANIFEST'
245
+
246
+ IMAGE_RESOURCE_DIRECTORY_ENTRY = create_struct 'V2',
247
+ :Name, :OffsetToData,
248
+ :name, :data
249
+
250
+ IMAGE_RESOURCE_DATA_ENTRY = create_struct 'V4',
251
+ :OffsetToData, :Size, :CodePage, :Reserved
252
+
253
+ IMAGE_RESOURCE_DIRECTORY = create_struct 'V2v4',
254
+ :Characteristics, :TimeDateStamp, # 2dw
255
+ :MajorVersion, :MinorVersion, :NumberOfNamedEntries, :NumberOfIdEntries, # 4w
256
+ :entries # manual
257
+ class IMAGE_RESOURCE_DIRECTORY
258
+ class << self
259
+ attr_accessor :base
260
+ alias :read_without_children :read
261
+ def read f, root=true
262
+ if root
263
+ @@loopchk1 = Hash.new(0)
264
+ @@loopchk2 = Hash.new(0)
265
+ @@loopchk3 = Hash.new(0)
266
+ elsif (@@loopchk1[f.tell] += 1) > 1
267
+ PEdump.logger.error "[!] #{self}: loop1 detected at file pos #{f.tell}" if @@loopchk1[f.tell] < 2
268
+ return nil
269
+ end
270
+ read_without_children(f).tap do |r|
271
+ nToRead = r.NumberOfNamedEntries.to_i + r.NumberOfIdEntries.to_i
272
+ r.entries = []
273
+ nToRead.times do |i|
274
+ if f.eof?
275
+ PEdump.logger.error "[!] #{self}: #{nToRead} entries in directory, but got EOF on #{i}-th."
276
+ break
277
+ end
278
+ if (@@loopchk2[f.tell] += 1) > 1
279
+ PEdump.logger.error "[!] #{self}: loop2 detected at file pos #{f.tell}" if @@loopchk2[f.tell] < 2
280
+ next
281
+ end
282
+ r.entries << IMAGE_RESOURCE_DIRECTORY_ENTRY.read(f)
283
+ end
284
+ #r.entries.uniq!
285
+ r.entries.each do |entry|
286
+ entry.name =
287
+ if entry.Name.to_i & 0x8000_0000 > 0
288
+ # Name is an address of unicode string
289
+ f.seek base + entry.Name & 0x7fff_ffff
290
+ nChars = f.read(2).to_s.unpack("v").first.to_i
291
+ begin
292
+ f.read(nChars*2).force_encoding('UTF-16LE').encode!('UTF-8')
293
+ rescue
294
+ PEdump.logger.error "[!] #{self} failed to read entry name: #{$!}"
295
+ "???"
296
+ end
297
+ else
298
+ # Name is a numeric id
299
+ "##{entry.Name}"
300
+ end
301
+ if entry.OffsetToData && f.checked_seek(base + entry.OffsetToData & 0x7fff_ffff)
302
+ if (@@loopchk3[f.tell] += 1) > 1
303
+ PEdump.logger.error "[!] #{self}: loop3 detected at file pos #{f.tell}" if @@loopchk3[f.tell] < 2
304
+ next
305
+ end
306
+ entry.data =
307
+ if entry.OffsetToData & 0x8000_0000 > 0
308
+ # child is a directory
309
+ IMAGE_RESOURCE_DIRECTORY.read(f,false)
310
+ else
311
+ # child is a resource
312
+ IMAGE_RESOURCE_DATA_ENTRY.read(f)
313
+ end
314
+ end
315
+ end
316
+ @@loopchk1 = @@loopchk2 = @@loopchk3 = nil if root # save some memory
317
+ end
318
+ end
319
+ end
320
+ end
321
+
322
+ def _scan_resources f=nil, dir=nil
323
+ dir ||= resource_directory(f)
324
+ return nil unless dir
325
+ dir.entries.map do |entry|
326
+ case entry.data
327
+ when IMAGE_RESOURCE_DIRECTORY
328
+ if dir == @resource_directory # root resource directory
329
+ entry_type =
330
+ if entry.Name & 0x8000_0000 == 0
331
+ # root resource directory & entry name is a number
332
+ ROOT_RES_NAMES[entry.Name] || entry.name
333
+ else
334
+ entry.name
335
+ end
336
+ _scan_resources(f,entry.data).each do |res|
337
+ res.type = entry_type
338
+ res.parse f
339
+ end
340
+ else
341
+ _scan_resources(f,entry.data).each do |res|
342
+ res.name = res.name == "##{res.lang}" ? entry.name : "#{entry.name} / #{res.name}"
343
+ res.id ||= entry.Name if entry.Name.is_a?(Numeric) && entry.Name < 0x8000_0000
344
+ end
345
+ end
346
+ when IMAGE_RESOURCE_DATA_ENTRY
347
+ Resource.new(
348
+ nil, # type
349
+ entry.name,
350
+ nil, # id
351
+ entry.Name, # lang
352
+ #entry.data.OffsetToData + @resource_data_base,
353
+ va2file(entry.data.OffsetToData),
354
+ entry.data.Size,
355
+ entry.data.CodePage,
356
+ entry.data.Reserved
357
+ )
358
+ else
359
+ logger.error "[!] invalid resource entry: #{entry.data.inspect}"
360
+ nil
361
+ end
362
+ end.flatten.compact
363
+ end
364
+ end
@@ -0,0 +1,17 @@
1
+ class PEdump
2
+ IMAGE_TLS_DIRECTORY32 = create_struct 'V6',
3
+ :StartAddressOfRawData,
4
+ :EndAddressOfRawData,
5
+ :AddressOfIndex,
6
+ :AddressOfCallBacks,
7
+ :SizeOfZeroFill,
8
+ :Characteristics
9
+
10
+ IMAGE_TLS_DIRECTORY64 = create_struct 'Q4V2',
11
+ :StartAddressOfRawData,
12
+ :EndAddressOfRawData,
13
+ :AddressOfIndex,
14
+ :AddressOfCallBacks,
15
+ :SizeOfZeroFill,
16
+ :Characteristics
17
+ end
@@ -2,7 +2,7 @@ class PEdump
2
2
  module Version
3
3
  MAJOR = 0
4
4
  MINOR = 4
5
- PATCH = 3
5
+ PATCH = 4
6
6
  BUILD = nil
7
7
 
8
8
  STRING = [MAJOR, MINOR, PATCH, BUILD].compact.join('.')
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "pedump"
8
- s.version = "0.4.3"
8
+ s.version = "0.4.4"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Andrey \"Zed\" Zaikin"]
@@ -36,9 +36,12 @@ Gem::Specification.new do |s|
36
36
  "lib/pedump.rb",
37
37
  "lib/pedump/cli.rb",
38
38
  "lib/pedump/composite_io.rb",
39
+ "lib/pedump/core.rb",
39
40
  "lib/pedump/packer.rb",
40
41
  "lib/pedump/pe.rb",
42
+ "lib/pedump/resources.rb",
41
43
  "lib/pedump/sig_parser.rb",
44
+ "lib/pedump/tls.rb",
42
45
  "lib/pedump/version.rb",
43
46
  "lib/pedump/version_info.rb",
44
47
  "pedump.gemspec",
@@ -51,6 +54,7 @@ Gem::Specification.new do |s|
51
54
  "spec/foldedhdr_spec.rb",
52
55
  "spec/imports_badterm_spec.rb",
53
56
  "spec/imports_vterm_spec.rb",
57
+ "spec/manyimportsW7_spec.rb",
54
58
  "spec/pe_spec.rb",
55
59
  "spec/pedump_spec.rb",
56
60
  "spec/resource_spec.rb",
@@ -0,0 +1,22 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+ require File.expand_path(File.dirname(__FILE__) + '/../lib/pedump')
3
+
4
+ describe "corkami/manyimportsW7.exe" do
5
+ before :all do
6
+ @sample = sample
7
+ end
8
+
9
+ it "should have 2 imports" do
10
+ @sample.imports.size.should == 2
11
+ @sample.imports.map(&:module_name).should == %w'kernel32.dll msvcrt.dll'
12
+ @sample.imports.map do |iid|
13
+ (iid.original_first_thunk + iid.first_thunk).uniq.map(&:name)
14
+ end.flatten.should == ["ExitProcess", "printf"]
15
+ end
16
+
17
+ it "should have 1 TLS" do
18
+ @sample.tls.size.should == 1
19
+ @sample.tls.first.AddressOfIndex.should == 0x401148
20
+ @sample.tls.first.AddressOfCallBacks.should == 0x401100
21
+ end
22
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pedump
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.3
4
+ version: 0.4.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -13,7 +13,7 @@ date: 2011-12-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: multipart-post
16
- requirement: &70155235875660 !ruby/object:Gem::Requirement
16
+ requirement: &70245146676060 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ~>
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: 1.1.4
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *70155235875660
24
+ version_requirements: *70245146676060
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: progressbar
27
- requirement: &70155235891480 !ruby/object:Gem::Requirement
27
+ requirement: &70245146675500 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ~>
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 0.9.2
33
33
  type: :runtime
34
34
  prerelease: false
35
- version_requirements: *70155235891480
35
+ version_requirements: *70245146675500
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec
38
- requirement: &70155235890940 !ruby/object:Gem::Requirement
38
+ requirement: &70245146674820 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ~>
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: 2.3.0
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *70155235890940
46
+ version_requirements: *70245146674820
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: bundler
49
- requirement: &70155235890060 !ruby/object:Gem::Requirement
49
+ requirement: &70245146674020 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ~>
@@ -54,10 +54,10 @@ dependencies:
54
54
  version: 1.0.0
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *70155235890060
57
+ version_requirements: *70245146674020
58
58
  - !ruby/object:Gem::Dependency
59
59
  name: jeweler
60
- requirement: &70155235889180 !ruby/object:Gem::Requirement
60
+ requirement: &70245146673180 !ruby/object:Gem::Requirement
61
61
  none: false
62
62
  requirements:
63
63
  - - ~>
@@ -65,10 +65,10 @@ dependencies:
65
65
  version: 1.6.4
66
66
  type: :development
67
67
  prerelease: false
68
- version_requirements: *70155235889180
68
+ version_requirements: *70245146673180
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: rcov
71
- requirement: &70155235888640 !ruby/object:Gem::Requirement
71
+ requirement: &70245146672260 !ruby/object:Gem::Requirement
72
72
  none: false
73
73
  requirements:
74
74
  - - ! '>='
@@ -76,10 +76,10 @@ dependencies:
76
76
  version: '0'
77
77
  type: :development
78
78
  prerelease: false
79
- version_requirements: *70155235888640
79
+ version_requirements: *70245146672260
80
80
  - !ruby/object:Gem::Dependency
81
81
  name: awesome_print
82
- requirement: &70155235888120 !ruby/object:Gem::Requirement
82
+ requirement: &70245146671740 !ruby/object:Gem::Requirement
83
83
  none: false
84
84
  requirements:
85
85
  - - ! '>='
@@ -87,7 +87,7 @@ dependencies:
87
87
  version: '0'
88
88
  type: :development
89
89
  prerelease: false
90
- version_requirements: *70155235888120
90
+ version_requirements: *70245146671740
91
91
  description: dump headers, sections, extract resources of win32 PE exe,dll,etc
92
92
  email: zed.0xff@gmail.com
93
93
  executables:
@@ -115,9 +115,12 @@ files:
115
115
  - lib/pedump.rb
116
116
  - lib/pedump/cli.rb
117
117
  - lib/pedump/composite_io.rb
118
+ - lib/pedump/core.rb
118
119
  - lib/pedump/packer.rb
119
120
  - lib/pedump/pe.rb
121
+ - lib/pedump/resources.rb
120
122
  - lib/pedump/sig_parser.rb
123
+ - lib/pedump/tls.rb
121
124
  - lib/pedump/version.rb
122
125
  - lib/pedump/version_info.rb
123
126
  - pedump.gemspec
@@ -130,6 +133,7 @@ files:
130
133
  - spec/foldedhdr_spec.rb
131
134
  - spec/imports_badterm_spec.rb
132
135
  - spec/imports_vterm_spec.rb
136
+ - spec/manyimportsW7_spec.rb
133
137
  - spec/pe_spec.rb
134
138
  - spec/pedump_spec.rb
135
139
  - spec/resource_spec.rb
@@ -152,7 +156,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
152
156
  version: '0'
153
157
  segments:
154
158
  - 0
155
- hash: 2848967461238967885
159
+ hash: -576155968655045053
156
160
  required_rubygems_version: !ruby/object:Gem::Requirement
157
161
  none: false
158
162
  requirements: