pedump 0.4.3 → 0.4.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: